ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 스프링부트 시큐리티 04 - JWT 토큰 세팅까지의 개념
    스프링/시큐리티 2021. 9. 9. 15:30

    유투버 '데어프로그래밍'님 강의 참조

     

     

    01 - 세션이란?

    • 클라이언트가 어떠한 요청을 최초로 하게되면 서버에서는 응답시에 HTTP 헤더에 쿠키를 담고 그 쿠기가 세션 ID값을 든 채로 클라이언트에게 응답이 가게 된다. 클라이언트는 브라우저상에 이 응답+세션값을 자동으로 들고 머무르게 되는 것이다. 
    • 클라이언트가 최초 이후에 두번째 요청을 하게되면 이 세션값을 포함해서 요청을 하게 된다. 서버는 이 세션값을 보고 유효성을 검사하게 되는 것.
    • 세션은 계속 유지되지 않는다 1) 서버가 세션을 끊을 수도 있고 2) 사용자가 브라우저를 닫게되면 없어 진다. 
      (브라우저가 닫히면 브라우저상의 세션은 날라가고 서버에서는 보통 30분정도 그 값이 유지 된다)

     

    • 세션은 보통 인증(로그인)을 위해 많이 사용 된다. 하지만 단점이 있다. 로드밸런싱이 걸리면 최초 서버와의 세션값은 다른 서버에서 찾을 수 없게 된다. (이런 세션의 단점은 여러가지 해결방법이 있다 - sticky 서버라던지.. 하지만 모든 방법은 많은 비효율성을 가져온다)

     

     

     

     

     

    ※ JWT를 이해하기전 알아야할 몇가지 개념들

     

    1) 통신 : OSI 7계층

     

    IT관련 용어 - [OSI 7계층] 이란? (OSI 7 Layer)

    안녕하세요 탄탄이 입니다. 오늘은 컴퓨터 네트워크 통신을 이야기 할 때 항상 등장하는 OSI 7계층에 대...

    blog.naver.com

    • 여기서 트랜스포트 계층에서 TCP/UDP형태로 나뉘게 된다 
      TCP - 신뢰성 기반 통신 / UDP - 속도 기반 통신 
      웹개발은 TCP통신으로 주로 이루어 진다

     

     

    2) 보안

    • 데이터 통신에 있어서 보안은 아주 중요한 문제인데 이 보안을 설명하는 단어가 'CIA' 이다. 
      C - Confidentiality (기밀의) - 승인받은 사람만 보도록
      I - Integrity (무결성) - 승인 받은 사람만 보는 인증 시스템이 무결점인지
      A - Availability (가용성) - 2중 3중으로 보안장치가 되어도 언제든 정확한 키로 열수 있는지 
      이 3가지가 지켜져야 한다. 
      https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=pentamkt&logNo=221204282722
     

    [하루 3분 IT] 정보보안의 기본 3원칙, CIA

    한 줄 요악 CIA는 정보보안의 기본 3원칙으로, 각각 Confidentiality(기밀성), Integrity(무결성), A...

    blog.naver.com

    • 이 CIA도 2가지 문제가 있다. 1) 정확한 키 전달 문제 2) 데이터를 누가 보냈는지의 인증 문제. 이 두가지 문제만 해결하면 보안에 대한 이슈가 해결이 가능

     

    3) RSA

    • 암호화 중의 하나로 키를 두개 들고 있다 1)Public(공개키) 2)Private(개인키)
    • 데이터를 보낼때 다 공개되는 공개키로 보내고 받는 사람은 자기가 들고있는 개인키로만 공개키로 걸려있는 데이터를 열 수 있는 특징이 있다. 즉 다른사람이 데이터를 가로채려고 했을때 받는 사람의 개인키 없이는 못 여니 정확한 키 전달 문제는 여기서 해결
    • 두번째로는 데이터를 보낼때 개인키로 잠구면 공개키로 열린다. 즉 A가 한 데이터를 A의 개인키로 데이터를 암호화하고 B에게 보면 B는 A의 공개키로만 열수 있기때문에 누가 보냈는지의 대한 인증문제가 해결 되는 것
      (보통 전자문서의 서명에 쓰인다)
    • 즉 공개키 → 개인키 (전자서명) / 개인키 → 공개키(암호화) 의 특징을 가지는것이 RSA, 즉 JWT는 RSA기반의 방식
    • 또한 RSA 방식에서는 공개키로 데이터를 잠구고 개인키로 한번더 암호화를 시켜 중간에서 데이터가 가로채져도 받는사람이 보낸사람의 공개키로 열었을때 열리면 OK, 안열리면 볼 필요가 없는 데이터라고 쉽게 알 수 있다. 

     

     

    4) RFC7519 문서

    • 통신에 있어서 양자간의 약속된 규칙을 정의한 문서 (프로토콜)
    • 현재까지 통신에 있어서 정의된 규칙문서 엄청나게 많고 JWT가 RFC7519규칙문서를 기반으로 정의 됨

     

     

     

     

    02 - JWT란?

    • 현대 웹서비스에서는 토큰을 사용하여 사용자들의 인증 작업을 처리하는것이 가장 좋은 방법이다. 그 토큰 인증 작업중 하나인 JWT(Json Web Token)는 JSON 포맷형식을 이용하여 사용자에대한 속성을 저장하는 Claim 기반의 서명이 이루어진 웹 토큰이다. 토큰 자체를 정보로 사용하는 Self-Contained 방식으로 정보를 안전하게 전달 한다.
    • JWT는 세션의 고질적 단점을 해결하기위해 고안 된 것. 

     

    • JWT의 구조는 점(.) 으로 구분 된 세 부분으로 구성
      헤더
      페이로드
      시그니쳐(서명)
      → xxxxx.yyyyy.zzzz 으로 생김 그리고 세부분 다 Base64Url 로 인코딩 시킴
    • 헤더 - 알고리즘/타입을 설정하며 JSON-Base64Url 기반으로 암호화/복구화를 가능하게 함 → 보통 HS256방식으로 암호화가 됨(H=HMAC(시크릿키를 가지고있음) S=SHA(해쉬화를 뜻함))
    • 페이로드 - 클래임 기반으로 개인 클레임을 주로 쓰며 사용자동의/정보 공유등에 쓰임
    • 시그니쳐 - 헤더/페이로드를 넣고 개인 키를 설정함 
    • JWT는 보통 웹브라우저의 로컬 스토리지에 저장이 되고 최초 요청시 서버가 JWT를 같이 실어서 응답하게되고 그 이후는 세션 대신에 JWT를 확인해서 서버가 응답을 하는 방식 
    • HS256방식을 쓰기 싫으면 헤더부분에 RSA방식이라고 표시만 해주면 사용 가능하다 

     

     

     

     

    03 - JWT 프로젝트 세팅

    • HS암호화 방식을 쉽게 해주는 메이븐 추가
    <!-- https://mvnrepository.com/artifact/com.auth0/java-jwt -->
    <dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>3.10.3</version>
    </dependency>
    • 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
    
      jpa:
        hibernate:
          ddl-auto: create #create update none
          naming:
            physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
        show-sql: true

    → 간단 테스트

    @RestController
    public class RestApiController {
    	
    	@GetMapping("home")
    	public String home() {
    		return "<h1>home</h1>";
    	}
    }

    굿!

     

    04 - JWT를 위한 모델/세큐리티 설정

    @Data
    @Entity
    public class User {
    	@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    	private long id;
    	private String username;
    	private String password;
    	private String roles; //USER,ADMIN 형식으로 role 부여
    	
    	public List<String> getRoleList(){
    		if(this.roles.length()>0) {
    			return Arrays.asList(this.roles.split(","));
    		}
    		return new ArrayList<String>();
    	}
    }
    • 하나의 유저가 두개의 롤을 가질 수 있다면 List 형식, 아니라면 그냥 게터 세터로 처리

     

     

    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    	@Override
    	protected void configure(HttpSecurity http) throws Exception {
    		http.csrf().disable();
    		http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
    		.and()
    		.formLogin().disable()
    		.httpBasic().disable()
    		.authorizeRequests()
    		.antMatchers("/api/v1/user/**")
    		.access("hasRole('ROLE_USER') or hasRole('ROLE_MANAGER') or hasRole('ROLE_ADMIN')")
    		.antMatchers("/api/v1/manager/**")
    		.access("hasRole('ROLE_MANAGER') or hasRole('ROLE_ADMIN')")
    		.antMatchers("/api/v1/admin/**")
    		.access("hasRole('ROLE_ADMIN')")
    		.anyRequest().permitAll();
    	}
    }
    • http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) → 세션 안만듬
    • .formLogin().disable()/.httpBasic().disable() → 기본적인 HTTP 방식 안씀

     

    • JWT는 JSON을 기본으로하기때문에 내서버가 응답시에 JSON을 JS에서 처리가능하게 하는 등의 필터가 필요
    @Configuration
    public class CorsConfig {
    	@Bean
    	public CorsFilter corsFilter() {
    		UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    		CorsConfiguration config = new CorsConfiguration();
    		config.setAllowCredentials(true);
    		config.addAllowedOrigin("*");
    		config.addAllowedHeader("*");
    		config.addAllowedMethod("*");
    		source.registerCorsConfiguration("/api/**", config);
    		return new CorsFilter(source);
    	}
    }
    • config.setAllowCredentials(true); → 내서버가 응답시 JSON을 JS에서 처리 가능하도록 할지 여부
    • config.addAllowedOrigin("*"); → 모든 IP에 응답 허용
    • config.addAllowedHeader("*"); → 모든 HEADER에 응답 허용
    • config.addAllowedMethod("*"); → 모든 POST/GET/PUT/DELETE/PATCH등의 요청을 허용

     

    • 이 필터를 다시 SecurityConfig에서 쓰도록 코딩
    @Configuration
    @EnableWebSecurity
    @RequiredArgsConstructor
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    	private final CorsFilter corsFilter;
    	@Override
    	protected void configure(HttpSecurity http) throws Exception {
    		http.csrf().disable();
    		http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
    		.and()
    		.addFilter(corsFilter)
    		.formLogin().disable()
    		.httpBasic().disable()
    		.authorizeRequests()
    		.antMatchers("/api/v1/user/**")
    		.access("hasRole('ROLE_USER') or hasRole('ROLE_MANAGER') or hasRole('ROLE_ADMIN')")
    		.antMatchers("/api/v1/manager/**")
    		.access("hasRole('ROLE_MANAGER') or hasRole('ROLE_ADMIN')")
    		.antMatchers("/api/v1/admin/**")
    		.access("hasRole('ROLE_ADMIN')")
    		.anyRequest().permitAll();
    	}
    }
    • 현재까지의 작업으로 모든 요청은 반드시 CorsFilter를 타게 되있고 CrossOrigin 요청도 허용이 되도록 세팅을 한 것
    • @CrossOrigin 어노테이션 방법도 있지만 이 어노테이션은 인증이 필요하지 않은 요청만 허용하므로 따로 필터를 해줘야 한다
    • 요약하자면 이번 강의의 기본은 1) 세션이 없고 2)크로스오리진정책에서 벗어난다(모든 요청 허용) 3)Formlogin을 쓰지 않는다.

    ※ CORS란?

    Cross-Origin Resource Sharing의 약자로 HTTP의 헤더를 사용하여 다른 포트/아이피의 자원 접근 권한을 부여를 하도록 알려주는 체제.

    간단히 말하면 도메인 또는 포트가 다른 서버의 자원을 요청할때 이에대한 권한 부여 및 여부 확인 담당. 보통 개발을 하다보면 리액트와 자바를 연동할때 포트번호가 다르기 때문에 CORS 오류를 일으키는 경우가 자주 발생 한다. 이 오류가 발생하는 이유는 HTTP의 기본 정책이 동일 출저 정책을 따르기 때문. 즉 하나의 웹을 사용할때 자신의 출저와 동일한 리소스만 불러올 수 있으며 다른 출저의 리소르를 불러오면 CORS 오류가 뜬다.(다른 출저의 자원을 보려면 그 출처에 CORS헤더를 포함시켜 응답을 반환 해야함)

    더 자세한 사항은 아래 브로그로!

    https://evan-moon.github.io/2020/05/21/about-cors/

     

    CORS는 왜 이렇게 우리를 힘들게 하는걸까?

    이번 포스팅에서는 웹 개발자라면 한번쯤은 얻어맞아 봤을 법한 정책에 대한 이야기를 해보려고 한다. 사실 웹 개발을 하다보면 CORS 정책 위반으로 인해 에러가 발생하는 상황은 굉장히 흔해서

    evan-moon.github.io

     

     

     

     

    ※JWT Bearer 인증 방식 짚고 넘어 가기

    • .httpBasic().disable() 을 이해할 필요가 있다. http bisic방식은 쿠키 세션 방식을 쓰지 않고 헤더에 Authorization 키값에 ID/PW를 담아서 요청하는 방식을 의미한다. 이렇게 하면 쿠키 세션 인증 방식이 필요없다 왜냐하면 인증시마다 ID/PW를 달고 있기 때문이다. 하지만 이방식도 ID/PW가 노출되는 위험이 있기때문에 Authorization 에 토큰을 넣어서 보내는 방식이 JWT Bearer 방식. 이 방식에서도 토큰이 노출이 되면 안되지만 토큰이 노출되어도 직접적인 ID/PW 노출보다는 훨씬 안전하다. 즉 이 방식을 쓰기 위해서 '.httpBasic().disable()' 걸어 주는 것.

     

     

     

    05 - JWT 토큰 검증 필터 설정

    • 스프링 시큐리티는 기본적으로 서블릿 필터를 가지고 있어 인증이 되지 않으면 보안상 웹 페이지 접근에 제한 처리를 해버린다
    • 따라서, 매번 웹 uri로 자원에 접근할때 WebSecurityConfigure에서 configure()에 설정해둔 uri 매칭에 따라permitAll()이나 Role을 가지고 있는지 체크하는 hasRole을 통해 인증과정을 거친다.
    • .이때, addFilterBefore추가한 필터도 당연히 Filter로 사용하게 되고 JWT 토큰을 검증하여 로그인 인증을 통과 시킨다.
    public class MyFilter1 implements Filter {
    	@Override
    	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
    			throws IOException, ServletException {
    		System.out.println("Filter 1");
    		chain.doFilter(request, response);
    	}
    }
    @Configuration
    public class FilterConfig {
    
    	@Bean
    	public FilterRegistrationBean<MyFilter1> filter1(){
    		FilterRegistrationBean<MyFilter1> bean = new FilterRegistrationBean<MyFilter1>(new MyFilter1());
    		bean.addUrlPatterns("/**");
    		bean.setOrder(0);
    		return bean;
    	}
    }
    • bean.setOrder(0); → 낮은 번호가 필터중 가장 먼저 실행 됨

     

     

     

    06 - 임시 토큰 검증 테스트 

    @RestController
    public class RestApiController {
    	...
        	@PostMapping("token")
    	public String token() {
    		return "<h1>token</h1>";
    	}
    }
    public class MyFilter1 implements Filter {
    	@Override
    	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
    			throws IOException, ServletException {
    
    		HttpServletRequest req = (HttpServletRequest) request;
    		HttpServletResponse res = (HttpServletResponse) response;
    
    		if (req.getMethod().equals("POST")) {
    			System.out.println("POST 요청됨");
    			String headerAuth = req.getHeader("Authorization");
    			System.out.println(headerAuth);
    		}
    		System.out.println("Filter 1");
    		chain.doFilter(req, res);
    	}
    }

     

    • 현재 인증이 되던 안되던 컨트롤러를 타고 있는데 토큰값을 정확히 주고 그 값이 맞지 않으면 아예 컨트롤러도 타지 못하고 계속 인증을 하도록 만들어 보자

    굿!

    • 즉 JWT 토큰 인증은 반드시 세큐리티 전에 돌아야 한다! 
    public class MyFilter1 implements Filter {
    	@Override
    	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
    			throws IOException, ServletException {
    		System.out.println("Filter 1");
    		chain.doFilter(request, response);
    	}
    }
    
    public class MyFilter2 implements Filter {
    	@Override
    	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
    			throws IOException, ServletException {
    		HttpServletRequest req = (HttpServletRequest) request;
    		HttpServletResponse res = (HttpServletResponse) response;
    
    		if (req.getMethod().equals("POST")) {
    			System.out.println("POST 요청됨");
    			String headerAuth = req.getHeader("Authorization");
    			System.out.println(headerAuth);
    
    			if (headerAuth.equals("cos")) {
    				chain.doFilter(req, res);
    			} else {
    				PrintWriter out = res.getWriter();
    				out.println("인증부터 하세요!");
    			}
    		}
    	}
    }
    
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    	private final CorsFilter corsFilter;
    	@Override
    	protected void configure(HttpSecurity http) throws Exception {
    		http.addFilterBefore(new MyFilter2(), SecurityContextPersistenceFilter.class);
            .....
            }
    }
    • 이제 로그인을 정상적으로 하게되면 토큰을 만들어서 응답해주고 그 이후 요청마다 Authrization의 value값으로 토큰을 가지고 오게 되는 것. 이때 토큰이 넘어오면 이 토큰이 내가 응답으로 준 토큰이 맞는지 검증만 하면 됨

     

     

     

    07 - JWT를 위한 로그인 시도

     

    • PrincipalDetails 세팅
    @Data
    public class PrincipalDetails implements UserDetails {
    	
    	private User user;
    	
    	public PrincipalDetails(User user) {
    		this.user = user;
    	}
    
    	@Override
    	public Collection<? extends GrantedAuthority> getAuthorities() {
    		Collection<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
    		user.getRoleList().forEach(r -> {
    			authorities.add(()-> r);
    		});
    		return authorities;
    	}
    
    	@Override
    	public String getPassword() {
    		return user.getPassword();
    	}
    
    	@Override
    	public String getUsername() {
    		return user.getUsername();
    	}
    
    	@Override
    	public boolean isAccountNonExpired() {
    		return true;
    	}
    
    	@Override
    	public boolean isAccountNonLocked() {
    		return true;
    	}
    
    	@Override
    	public boolean isCredentialsNonExpired() {
    		return true;
    	}
    
    	@Override
    	public boolean isEnabled() {
    		return true;
    	}
    }

     

    • PrincipalDetailsService 세팅
    @Service
    @RequiredArgsConstructor
    public class PrincipalDetailsService implements UserDetailsService {
    	
    	private final UserRepository userRepository;
    	
    	@Override
    	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    		System.out.println("PrincipalDetailsService의 loadUserByUsername() 호출 됨");
    		User userEntity = userRepository.findByUsername(username);
    		return new PrincipalDetails(userEntity);
    	}
    }
    • 로그인 요청시 실행 됨. 

     

    • UserRepository
    public interface UserRepository extends JpaRepository<User, Long>{
    	public User findByUsername(String username);
    }

     

     

    • 여기까지 세팅을 한 후 포스트맨으로 하면....

    • 당연히 동작 안된다 왜냐하면 시큐리티의 기본 /login 을 막아놨기 때문이다 (.formLogin().disable())
    • 즉 로그인 요청시 실행되는 PrincipalDetailsService 을 직접 실행해주는 필터가 필요하다

     

    • JwtAuthenticationFilter 세팅
    @RequiredArgsConstructor
    public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
    	private final AuthenticationManager authenticationManager;
    	
    	@Override
    	public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
    			throws AuthenticationException {
    		System.out.println("JwtAuthenticationFilter: 로그인 요청 중!");
    		return super.attemptAuthentication(request, response);
    	}
    }
    • 이 필터가 강제로 실행되어야한다. SecurityConfig에 코드 한줄 추가
    		.formLogin().disable()
    		.httpBasic().disable()
    		.addFilter(new JwtAuthenticationFilter(authenticationManager()))

    굿!

    • 이제 필터가 강제로 실행되면서 시큐리티에서의 로그인도 실행이 된다. 이후 JwtAuthenticationFilter 에서 ID/PW 유효성 검사를 하면되는데 이때 authenticationManager로 로그인 시도를 하면 이 함수가 로그인 진행(UsernamePasswordAuthenticationFilter)를 들고 있기때문에 진행이 되는 것.
    • JwtAuthenticationFilter → authenticationManager (로그인 진행) → PrincipalDetailsService 호출 됨 → loadUserByUsername 함수 실행 됨 → PrincipalDetails 세션에 담음 → JWT 토큰 생성 후 응답
    • 기억해야할 부분은 PrincipalDetails 을 세션에 담는데 권한을 필수로 관리를 해야하는 웹이기 때문!!

     

     

     

     

     

    08 - JWT를 위한 강제 로그인 

    • JwtAuthenticationFilter 이 가지고 있는 함수 attemptAuthentication에서 넘어오는 ID/PW 받아보기
      (로그인 요청시에는 대부분 데이터가 JSON형태이다. JSON형태로 파싱을 해야 ID/PW가 읽어진다)
    • 디비에 값부터 넣기
    • IndexController
    @RequiredArgsConstructor
    @RestController
    public class RestApiController {
    	private final UserRepository userRepository;
        ....
        @PostMapping("join")
    	public String join(@RequestBody User user) {
    		user.setPassword(new BCryptPasswordEncoder.encode(user.getPassword()));
    		user.setRoles("ROLE_USER");
    		userRepository.save(user);
    		return "회원가입완료";
    	}
    }

    ※ @Requestbody 가 있으므로 JSON으로 날려야 응답이 된다 기억하자!!!

     

    @RequiredArgsConstructor
    public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilter{
    	
    	private final AuthenticationManager authenticationManager;
    	
    	@Override
    	public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
    			throws AuthenticationException {
    		System.out.println("JwtAuthenticationFilter: 로그인 시도중");
    		
    		try {
    //			BufferedReader br = request.getReader();
    //			
    //			String input = null;
    //			while((input=br.readLine())!=null) {
    //				System.out.println(input);
    //			}
    			ObjectMapper om = new ObjectMapper();
    			User user = om.readValue(request.getInputStream(), User.class);
    			System.out.println(user);
    			
    			UsernamePasswordAuthenticationToken authenticationToken = 
    					new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword());
    			Authentication authentication = authenticationManager.authenticate(authenticationToken);
    			
    			PrincipalDetails principalDetails = (PrincipalDetails) authentication.getPrincipal();
    			System.out.println(principalDetails.getUser().getUsername());
    			
    			return authentication;
    		} catch (IOException e) {
    			e.printStackTrace();
    		}
    		System.out.println("===================================");
    		return null;
    	}
    }

    굿!

    • 제이슨으로 던져도 ObjectMapper라는 클래스가 제이슨을 알아서 파싱을 해주고 request에 JSON형태로 담긴 내용을 파싱해서 보여준다. 
    • 이제 여기에 직접 토큰을 만들어서 로그인 처리를 해보자 (원래라면 formLogin()을 시큐리티에 설정하면 자동으로 해주지만 현재 막아놨기때문에 직접 만들어야한다.)

     

     

     

    • 하지만 시큐리티 특성상 패스워드는 무조건 암호화되야하고 비교시에도 암호화를 시켜야 작동이 된다. 암호화 세팅이 필요하다
    • SecurityConfig
    @Configuration
    @EnableWebSecurity
    @RequiredArgsConstructor
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    	
    	private final CorsFilter corsFilter;
    	
    	@Bean
    	public BCryptPasswordEncoder passwordEncoder() {
    		return new BCryptPasswordEncoder();
    	}
        ...
    • RestApiController에도 BCryptPasswordEncoder() 를 선언해서 바꾸자 (빈으로 등록을 했으니 new하지말고 쓰자)
    	private final BCryptPasswordEncoder bCryptPasswordEncoder;
    	user.setPassword(bCryptPasswordEncoder.encode(user.getPassword()));

    → 포스트맨으로 제 테스트를 해보면

    굿!

    • 아이디가 뜬다는 것은 로그인 처리가 잘 됬다는 것. JwtAuthenticationFilter이 잘 불러지고 리턴으로 Authentication 값이 잘 떨어졌고 세션에 리턴값 'Authentication'이 세션에 잘 저장이 된 것. 

     

     

    * 정리를 하자면

    1. JwtAuthenticationFilter의 attemptAuthentication() 함수에 선언된 
      Authentication authentication = authenticationManager.authenticate(authenticationToken);
      가 토큰을 만들어서 로그인 시도를 함. 정상이면 authentication 이 만들어 짐
      즉 DB 저장된 값과 로그인으로 넣어지는 값이 동일하다 → 인증 끝
    2. 그 아래 코드로 선언된 principalDetails 단순 로그인 확인 용이며 authentication 객체가 세션에 저장이 되어야하는데 그 방법이 return으로 처리하는 것
    3. 리턴으로 굳이 하는 이유는 세큐리티의 권한 처리를 위함. JWT를 만들면 세션을 만들 이유가 없으나 권한을 부여해서 작업하고싶으면 어쩔 수 없음. 

     

     

    • 이제 남은것은 JWT 토큰을 만들어서 부여하는것인데 authentication 세션 처리가 끝나면 따라오는 함수가 있다.
    	@Override
    	protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
    			Authentication authResult) throws IOException, ServletException {
    		System.out.println("successfulAuthentication 실행 됨");
    		super.successfulAuthentication(request, response, chain, authResult);
    	}
    • attemptAuthentication 함수가 끝나면 자동적으로 successfulAuthentication 실행이되고 여기서 JWT토큰을 만들어서 응답시에 사용자에게 JWT토큰을 RESPONSE 해주면 됨

    굿 !

     

     

     

     

     

    09 - JWT 토큰 만들어서 응답하기

    • successfulAuthentication 토큰을 담는다고 했으니 만들어 보자
    	@Override
    	protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
    			Authentication authResult) throws IOException, ServletException {
    		System.out.println("successfulAuthentication 실행 됨");
    		
    		//HS방식
    		PrincipalDetails principalDetails = (PrincipalDetails)authResult.getPrincipal();
    		String jwtToken = JWT.create()
    				.withSubject("Token1")
    				.withExpiresAt(new Date(System.currentTimeMillis()+(60000*10)))
    				.withClaim("id", principalDetails.getUser().getId())
    				.withClaim("username", principalDetails.getUser().getUsername())
    				.sign(Algorithm.HMAC512("cos"));
    		response.addHeader("Authorization", "Bearer "+jwtToken);
    	}
    1. withSubject → 토큰 이름
    2. withExpiresAt → 토큰 만료 시간
    3. withClaim → 비공개 클레임으로 내가 받고 싶은 값을 보도록 하는 것
    4. sign → 내 서버만 아는 고유 비번을 넣음
    5. response.addHeader → 마지막으로 응답 헤더에 들어갈 값 (사용자가 이 값들을 받음)

     

    굿!

    • 위의 값이 JWT 토큰이고 사용자에게 응답이 되고, 사용자는 이걸가지고 최초이후에 같이 요청하는 개념
    • JWT 토큰과 세션 차이는 아래블로그!

    https://jihyun03.tistory.com/52#:~:text=%EB%B0%9C%EC%83%9D%ED%95%98%EA%B2%8C%20%EB%90%A9%EB%8B%88%EB%8B%A4.-,3.,%EB%93%A4%EC%9D%B4%20%EB%84%A3%EB%8A%94%EB%8B%A4%EB%8A%94%20%EC%A0%90%EC%9E%85%EB%8B%88%EB%8B%A4.

     

    쿠키/세션 방식과 JWT

    1. 쿠키/세션 방식 1) 인증 방식 순서 1. 사용자가 로그인을 한다. 2. 서버에서는 계정정보를 읽어 사용자를 확인한 후, 사용자의 고유한 ID값을 부여하여 세션 저장소에 저장한 후, 이와 연결되는

    jihyun03.tistory.com

     

Designed by Tistory.