-----

마샬링

------

세션서버

리액트 있는 조는 이렇게 만들면 안됨 - jwt 세팅해야함

템플릿을 이용하는 조는 세션서버를 만들어야 한다 - csrf-token 비활성화 등 세팅

----

옵저버 패턴이 적용 되어 있는 리액티브 스프링

----

스프링 시큐리티 보안 공부

https://spring.io/guides/gs/securing-web/

 

Securing a Web Application

this guide is designed to get you productive as quickly as possible and using the latest Spring project releases and techniques as recommended by the Spring team

spring.io

------

 

 

 

 

 

 

spring security / spring data JPA 로 세팅

 

 

com.jaybon.securityEx01 아래에 만들어야 컴포넌트 스캔이 된다

 

 

DataSource (DB 풀링 해주는 객체)

 

 

yml파일로 변경

 

 

포트와 컨텍스트패스 캐릭터셋을 설정하고 enable force

server:
  port: 8080
  servlet:
    context-path: /
    encoding:
      charset: utf-8
      enabled: true
      force: true

 

 

 

 

jdbc:mysql://localhost:3306/security?serverTimezone=Asia/Seoul

(security라는 데이터베이스에 접속한다는 뜻)

 

 

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/security?serverTimezone=Asia/Seoul
    username: cos
    password: cos1234

 

 

(mysql db연결)

https://blog.naver.com/getinthere/221708608489

 

JSP AWS 배포 하기 - MySQL + Tomcat

https://youtu.be/BH7De5EEoU4​만약에 AWS에 배포한 프로젝트 내부의 META-INF의 context...

blog.naver.com

 

 

 

 

실행 해보면 잠겨있다

인터셉터에서 막힌 것

각종 공격을 다 막아 준다

 

 

필터 - 디스패쳐 - 인터셉터 - 컨트롤러

 

 

로그에 찍힌 패스워드를 입력

비밀번호는 (해시)로 되어 있다

 

 

 

 

세션

Security-Context (key) 밑에 방대한 값(value)들이 있다

그 중에

Authentication( 인증에 필요한 권한, 유효한 이용자인지 등 모든 필드가 정해져 있다 )

필드들이 정해져 있다는 것은 만들어서 넣어야 한다는 뜻

Authentication Manager의 도움을 받아서 Authentication을 만든다

Authentication의 필수 입력 값은 (username, password)

Authentication의 구성은 정해진 필드를 따라 만들어야 한다 ( User, UserDetails )

UserDetails ( 유저네임, 패스워드, 권한 )를 만들 때 컴포지션하거나

내 커스텀 유저정보 java 파일에 UserDetails을 extends 하면 된다

접근할 때 DI를 사용하면 된다

 

 

로그아웃 하려면

http://localhost:8080/logout

 

 

----------

로그인 페이지 회원가입 페이지를 커스텀 할 것

 

 

설정파일 하나 생성

 

필터 - 디스패쳐 - 인터셉터 - 컨트롤러

 

필터와 디스패쳐 인터셉터 사이에

시큐리티 필터 체인 ( 많은 필터가 모여있다, xss, 주소, ip, 인증 등) - 체인이 있다는 것을 기억

필터를 낚아채려면 오버라이딩 또는 extends로 재정의

 

 

전체 필터를 관리할 수 있는 클래스 (@EnableWebSecurity)

 

어댑터가 있다는 것은 일부 원하는 것만 오버라이딩 할 수 있다는 것

 

 

 

 

http요청을 제어

 

 

 

 

로그인 없이 바로 들어가진다

 

 

 

 

 

 

다른페이지로 이동하면 권한이 없어서 페이지가 뜨지 않는다(403)

 

 

위의 방식이 파즈티브 방식(열것만 열고 나머지는 다 잠그기)이고 아래는 네거티브 방식(다열고 잠글것만 잠그기)

 

 

 

 

http://localhost:8080/admin -> http://localhost:8080/login

(파일이 없어서 404)

 

 

로그인을 리턴해주는 라우팅을 만들면

 

 

 

 

머스태치 

 

메이븐 리파지토리에서 spring mustache 검색

 

 

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-mustache</artifactId>
		</dependency>

 

 

추가해주고 서버끄고 메이븐리파지토리 업데이트 무조건!

 

 

또는 yml 파일에

  mvc:
    view:
      prefix: /templates/
      suffix: .mustache

 

 

 

 

템플릿에서 name을 잘 적어줘야한다

 

 

loginProc는 컨트롤러에서 낚아채는 것이 아니라 loginProcessingUrl에서 낚아채서 수행

(formLogin이라면 템플릿에서 form타입으로 날려야한다)

 

 

성공하면 해당주소로 이동 defaultSuccessUrl (Ajax와 같이 쓰지말 것 : 응답이 두번되기 때문에 꼬임)

로그인만! Ajax 쓰지말자 (회원가입은 써도됨)

 

 

컨트롤러에 회원가입 라우팅 추가

 

 

템플릿에 회원가입 추가 (회원가입은 ajax로 바꾸어도 무방)

 

 

 

 

컨트롤러에 회원가입 프로세스 라우팅 추가

(시큐리티 컨피그를 안만들었기 때문)

 

 

@RequestBody는 제이슨 일때만!

 

 

유저모델추가

 

 

입력 테스트

 

 

포비든이 뜬다 무엇이 문제일까

 

 

POST일때만 폼태그로 요청 토큰(csrf-token)이 필요하다!

(csrf = 해커가 임의대로 요청을 해서 공격)

csrf-token을 임시로 만들어줘서 줘야한다

(스프링에서 csrf 토큰 만들기 검색 공부)

 

방법(1. csrf토큰 비활성화 // 2. 토큰 만들기)

 

현대 프로그래밍은 자바스크립트로 하기 때문에 csrf를 잘 당하지 않는다 (수업에선 비활성화)

SecurityConfig.java

 

 

회원가입을 하면 인덱스페이지로 이동된다

 

 

-------

 

JPA

연습 용이라 서비스 안만들고 레파지토리로 끝냄

 

 

모델에서 @Entity를 추가(데이터베이스의 모델)

 

 

IDENTITY로 설정

 

 

 

 

모델 만들어 질 때 테이블 생성

(생성 create 업뎃만 update 아무것도안하려면 none)

 

 

mysql에서 셀렉트해보면 만들지도 않은 테이블이 생성되어 있다

 

 

모델에서 createDate를 만들고 저장만 하면!

@CreationTimestamp 세팅 (시간 자동입력)

 

 

DB에서 속성이 자동으로 생성되어있다

 

 

 

 

회원가입 해보면

 

 

서버를 켜보면 하이버네이트 Hibernate 

(서버를 켤때 마다 지워지고 다시 만들어진다)

디폴트는 언더바 전략

 

네이밍 전략

 

 

커멜 표기법 전략으로 변경

  jpa:
    hibernate:
      ddl-auto: update #create update none 3개 중 하나 선택
      naming:
        physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
    show-sql: true

 

 

레파지토리

jpa는 어노테이션이나 컨피그 데이터 소스가 아니라 JpaRepository를 상속

 

 

컨트롤러에서 DI

 

 

한줄추가

 

 

입력해보면 들어가있다

(로그인시 오류가 날것이다 암호화가 되어있지않음)

 

BCryptPasswordEncoder (안좋은방법 다른방법 쓸 것)

encode

 

 

SecurityConfig.java

 

 

IndexController.java

 

 

bCryptPasswordEncoder를 이용해 해시화 시켜줌

 

 

로그인

10. Authentication 객체 생성

Authentication은 AuthenticationFilter의 자식

AuthenticationFilter는 loginProc가 호출될 때 작동

UsernamePasswordAuthenticationToken 토큰이 있어야 로그인됨

 

https://getinthere.tistory.com/29

 

스프링부트 with JPA 블로그 13강 - 시큐리티 동작 원리

1. 스프링 mvc request life cycle 2. Filter와 Interceptor는 실행 시점이 다르다. Filter는 WebApplication에 등록 - web.xml Interceptor는 Spring의 Context에 등록 필터체인 예제와 인터셉터 예제하기 3. In..

getinthere.tistory.com

 

 

1아이디패스워드받아서

2토큰을받고

3유저디테일서비스가 받음

4유저디테일서비스는 유저디테일즈를 리턴

5어썬티케이션매니저가 받아서 어썬티케이션에 넣는다

6유저디테일은 시큐리티에 꼭 필요한 필드들이 있기 때문에 무조건 유저디테일로

7내가 만든 유저 모델을 유저디테일 타입으로 바꿔줘야한다

8어썬티케이션은 세션에 담긴다
(Map<String, Authentication> 객체이름 = new HashMap<>();)
(aa.put("Security-Context", auth))
(session.setAttribute("Security-Context-Holder", 해시맵))
쉽게 접근 못하도록 싸매져 있다

AuthenticationManager를 이용해서 Authentication를 저장할 수 있다(강제로 저장)

 

 

패키지 생성

 

 

유저디테일즈를 리턴해주기 위해서 클래스 하나 생성

모델 유저에 유저디테일즈를 상속받으면 유저모델이 망가지기 때문에 새로만들어줌

 

 

UserDetails를 임플리먼트해주고 오버라이딩

Authentication 객체에 저장할 수 있는 유일한 타입 - UserDetails

시큐리티 입장에서 관리해줄 수 있게 정해져 있다

 

 

User 모델 콤포지션

더보기
package com.jaybon.securityEx01.config.auth;

import java.util.Collection;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import com.jaybon.securityEx01.model.User;

import lombok.Data;


//Authentication 객체에 저장할 수 있는 유일한 타입
@Data
public class PrincipalDetails implements UserDetails{
	
	private User user;
	
	public PrincipalDetails(User user) {
		super();
		this.user = user;
	}
	
	

	@Override // 사용자의 비밀번호를 알고 싶으면 호출
	public String getPassword() {
		return user.getPassword();
	}

	@Override // 사용자의 유저네임를 알고 싶으면 호출
	public String getUsername() {
		return user.getUsername();
	}

	@Override // 사용자가 만료된 지를 알고 싶으면 호출
	public boolean isAccountNonExpired() { // 만료안됐니?
		//접속시간확인하여 true false 리턴
		return true;
	}

	@Override 
	public boolean isAccountNonLocked() { // 락 안걸렸니?
		return true;
	}

	@Override
	public boolean isCredentialsNonExpired() {
		// TODO Auto-generated method stub
		return true;
	}

	@Override
	public boolean isEnabled() { // 계정활성화 되어있니?
		return true;
	}
	
	@Override // 어떤 권한을 가졌니?
	public Collection<? extends GrantedAuthority> getAuthorities() {
		// TODO Auto-generated method stub
		return null;
	}

}

 

 

 

 

UserDetailsService를 만들어야함

 

 

JPA 쿼리 짜는법

 

jpa 쿼리 생성룰

https://papababo.tistory.com/272

 

[Spring Data Rest] Query creation - 쿼리 생성 룰

Spring Data Rest 사용시, 또는 JPA 기본 검색기능활용시, 아래의 네이밍 룰 따르면 끝! https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#jpa.query-methods.query-creation Table 4. Suppo..

papababo.tistory.com

 

 

로그인 해보면 성공

 

-------

권한리턴하기

유저 권한 리턴하기 (주석은 Arrays)

스프링 내부에서 원하는 GrantedAuthority 타입으로만 설정해서 리스트에 저장하여 리턴

 

 

----------

 

구조

 

PrincipalDetails.java

더보기
package com.jaybon.securityEx01.config.auth;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import com.jaybon.securityEx01.model.User;

import lombok.Data;


//Authentication 객체에 저장할 수 있는 유일한 타입
@Data
public class PrincipalDetails implements UserDetails{
	
	private User user;
	
	public PrincipalDetails(User user) {
		super();
		this.user = user;
	}
	
	

	@Override // 사용자의 비밀번호를 알고 싶으면 호출
	public String getPassword() {
		return user.getPassword();
	}

	@Override // 사용자의 유저네임를 알고 싶으면 호출
	public String getUsername() {
		return user.getUsername();
	}

	@Override // 사용자가 만료된 지를 알고 싶으면 호출
	public boolean isAccountNonExpired() { // 만료안됐니?
		//접속시간확인하여 true false 리턴
		return true;
	}

	@Override 
	public boolean isAccountNonLocked() { // 락 안걸렸니?
		return true;
	}

	@Override
	public boolean isCredentialsNonExpired() {
		// TODO Auto-generated method stub
		return true;
	}

	@Override
	public boolean isEnabled() { // 계정활성화 되어있니?
		return true;
	}
	
	// Arrays.asList(new SimpleGrantedAuthority(user.getRole()));
	@Override // 어떤 권한을 가졌니?
	public Collection<? extends GrantedAuthority> getAuthorities() {
		
		Collection<GrantedAuthority> authList = new ArrayList<>();
		authList.add(new SimpleGrantedAuthority(user.getRole()));
		return authList;
		
	}

}

PrincipalDetailsService.java

더보기
package com.jaybon.securityEx01.config.auth;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import com.jaybon.securityEx01.model.User;
import com.jaybon.securityEx01.repository.UserRepository;

// UserDetailsService는  IoC로 찾음

@Service // UserDetailsService타입으로 메모리에 뜬다 (덮어씌워짐)
public class PrincipalDetailsService implements UserDetailsService{
	
	@Autowired
	private UserRepository userRepository;

	@Override
	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
		// 어썬티케이션 매니저가 낚아챔
		// JPA는 기본적인 CRUD만 있어서 다른걸 쓰려면 만들어줘야함 
		
		User user = userRepository.findByUsername(username);
		
		if(user == null) {
			return null;
		}
		
		return new PrincipalDetails(user);
	}

}

SecurityConfig.java

더보기
package com.jaybon.securityEx01.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

@Configuration // IoC 빈(bean, 인스턴스)을 등록
@EnableWebSecurity // 필터 체인 관리 시작 어노테이션
@EnableGlobalMethodSecurity(prePostEnabled = true) // 컨트롤러 접근 전에 낚아챔, 특정 주소 접근시 권한 및 인증 미리체크
public class SecurityConfig extends WebSecurityConfigurerAdapter{
	
	@Bean // IoC에 등록되어 컨피그가 호출될 때 생성, 메서드를 IoC하는 방법
	public BCryptPasswordEncoder enc() { // 마땅히 둘 곳이 없어서 둔 것 Controller를 제외한 곳에 둠
		return new BCryptPasswordEncoder();
	}
	
	@Override
	protected void configure(HttpSecurity http) throws Exception {
		
		http.csrf().disable(); // csrf 비활성화
		
		http.authorizeRequests()
			.antMatchers("/user/**", "/admin**") 
			.authenticated()
			.anyRequest()
			.permitAll()
			.and()
			.formLogin()
			.loginPage("/login") // 인증이 필요한 곳에 /login 으로 리다이렉트
			.loginProcessingUrl("/loginProc") // 필터체인에서 인지하고 있다가 시큐리티가 낚아채서 Authentication Manager
			.defaultSuccessUrl("/"); // 성공하면 해당 주소로 이동 / 슬래시만 달면 이전 주소로 이동
	}
}

IndexController.java

더보기
package com.jaybon.securityEx01.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import com.jaybon.securityEx01.config.auth.PrincipalDetails;
import com.jaybon.securityEx01.model.User;
import com.jaybon.securityEx01.repository.UserRepository;

@Controller
public class IndexController {
	
	@Autowired
	private UserRepository userRepository;
	
	@Autowired
	private BCryptPasswordEncoder bCryptPasswordEncoder;

	@GetMapping({ "", "/" })
	public @ResponseBody String index() {
		return "인덱스 페이지입니다";
	}

	@GetMapping("/user")
	public @ResponseBody String user(@AuthenticationPrincipal PrincipalDetails principalDetails) {
		System.out.println("확인"+principalDetails);
		System.out.println(principalDetails.getUser().getRole());
		System.out.println(principalDetails.getAuthorities()); // 출력 했을 때 사용자의 모든 권한을 리턴
		return "유저 페이지입니다";
	}

	@GetMapping("/admin")
	public @ResponseBody String admin() {
		return "어드민 페이지입니다";
	}

	@GetMapping("/login")
	public String login() {
		return "login"; // 머스태치를 pom에 추가했으니 서픽스는 templates 프리픽스는 .mustatche
	}

	@GetMapping("/join")
	public String join() {
		return "join";
	}

	@PostMapping("/joinProc")
	public String joinProc(User user) {
		System.out.println("회원가입 진행" + user);
		String rawPassword = user.getPassword();
		String encPassword = bCryptPasswordEncoder.encode(rawPassword);
		user.setPassword(encPassword);
		user.setRole("ROLE_USER");
		userRepository.save(user);
		return "redirect:/";
	}

}

User.java

더보기
package com.jaybon.securityEx01.model;

import java.sql.Timestamp;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

import org.hibernate.annotations.CreationTimestamp;

import lombok.Data;

// ORM - Object Relation Mapping - 알아서 데이터베이스 테이블 만들어 준다

@Data
@Entity // 이것을 토대로 데이터베이스 모델을 만들 수 있다.
public class User {
	@Id // primary key를 걸어주는 어노테이션
	@GeneratedValue(strategy = GenerationType.IDENTITY) // 오라클은 시퀀스 전략, mysql은 오토인크리먼트 전략
	private int id;
	private String username;
	private String password;
	private String email;
	private String role; // ROLE_USER / ROLE_ADMIN
	@CreationTimestamp
	private Timestamp createDate;
}

UserRepository.java

더보기
package com.jaybon.securityEx01.repository;

import org.springframework.data.jpa.repository.JpaRepository;

import com.jaybon.securityEx01.model.User;

// JpaRepository를 상속하면 자동 스캔됨.
public interface UserRepository extends JpaRepository<User, Integer>{

	//Jpa Naming 전략
	// SELECT * FROM user WHERE username = ?
	User findByUsername(String username); // 함수이름에 맞게 쿼리가 동작한다
	
//	// SELECT * FROM user WHERE username = ? AND password = ?
//	User findByUsernameAndPassword(String username, String password);
//	
//	@Query(value = "SELECT * FROM user", nativeQuery = true)
//	User 내맘대로();
	
}

join.mustache

더보기
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>회원가입 페이지</title>
</head>
<body>
	<h1>회원가입 페이지</h1>
	<hr/>
	<form action="/joinProc" method="post">
		<input type="text" name="username" placeholder="username">
		<input type="password" name="password" placeholder="password">
		<input type="email" name="email" placeholder="email">
		<button>회원가입</button>
	</form>
</body>
</html>

login.mustache

더보기
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>로그인 페이지</title>
</head>
<body>
	<h1>로그인 페이지</h1>
	<hr/>
	<!-- 시큐리티는 x-www-form-urlencoded 타입만 인식 -->
	<form action="/loginProc" method="post">
		<input type="text" name="username">
		<input type="password" name="password">
		<button>로그인</button>
	</form>
</body>
</html>

application.yml

더보기
server:
  port: 8080
  servlet:
    context-path: /
    encoding:
      charset: utf-8
      enabled: true
      force: true
      
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/security?serverTimezone=Asia/Seoul
    username: cos
    password: cos1234
    
#  mvc:
#    view:
#      prefix: /templates/
#      suffix: .mustache


  jpa:
    hibernate:
      ddl-auto: update #create update none 3개 중 하나 선택
      naming:
        physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
    show-sql: true
      
      
      

 

 

 

'Server > Spring Boot' 카테고리의 다른 글

spring boot // 리플렉션  (0) 2020.08.01
spring boot // 필터 / 인터셉터  (0) 2020.08.01
spring boot // 블로그 연습  (0) 2020.07.27
spring boot // 주의사항  (0) 2020.07.27
spring boot // web.xml  (0) 2020.07.27

+ Recent posts