AbstractAuthenticationToken를 상속한토큰 클래스를 정의
UserDetails 구현체 정의
。해당토큰 클래스는UserDetails 구현체를생성자에 전달하여토큰 클래스 객체를 생성하도록 설정
▶ 아래에서 구현할JWT Filter에서JWT 검증이 끝난 후인증된UserDetails 구현체를 기반으로토큰 클래스 객체를 생성 후SecurityContextHolder에 등록됨.
。로그인한 사용자의UserDetails 구현체를 저장하여Security Context Holder에 등록하는Authentication 구현체public class AuthenticationToken extends AbstractAuthenticationToken { private final CurrentUser currentUser; public AuthenticationToken( CurrentUser currentUser, Collection<? extends GrantedAuthority> authorities ) { super(authorities); super.setAuthenticated(true); this.currentUser = currentUser; } @Override public Object getCredentials() { return currentUser.getId(); } @Override public Object getPrincipal() { return currentUser; } }。 반드시
super.setAuthenticated(true);를 설정해야한다.
▶AbstractAuthenticationToken.Authenticate의 기본값이 false이므로// AbstractAuthenticationToken 에서 상속 @Override public Object getPrincipal() { return currentUser; }。 해당
메서드를 통해토큰의Principal역할의UserDetails 구현체를 반환하도록 설정하여컨트롤러에서@AuthenticationPrincipal를 통해 주입
AbstractAuthenticationToken:
。Authentication 인터페이스를 구현하기위한추상클래스
▶ 해당추상클래스를 상속한구현 클래스를 정의 후 전달된JWT 토큰의 구성요소인principal( =UserDetails 구현체),authorities정보를 포함한인증토큰 구현체를SecurityContextHolder에 등록하여인가설정
JWT Token은Request Authorization Header에Bearer {token}형식으로 저장
JWT검증순서
Http Request에서authorization header가져오기Bearer 키워드제거 후토큰만 가져오기token의유효여부 검사유효하면id값꺼내서SecurityContextHolder에인가된 객체로 저장
클라이언트가 전송한JWT가 해당서버발급된 토큰인지에 대한검증을 수행하는필터 구현체정의
。OncePerRequestFilter를상속하여요청이어플리케이션내에서 여러번forward하더라도 해당필터 구현체는 단 한번만 동작하도록 설정
▶OncePerRequestFilter는GenericFilterBean 구현체이므로상속시 동일하게Filter 구현체가 됨
▶ 일반Filter 인터페이스를구현한 경우요청이forward할때마다 해당필터 구현체도 각각검증을 수행하게됨.
Spring Security - Filter
。Spring Filter로서 사용 시Web Security Configuration의SecurityFilterChain의HttpSecurity.addFilterBefore(필터구현체)를 통해 해당커스텀 필터 구현체를 포함
▶ 이후SecurityFilterChain는Servlet Filter Chain사이로Delegating Filter Proxy를 통해의존성 주입
Spring Security Filter Chain// OncePerRequestFilter를 상속하여 어플리케이션 내 한번만 검증을 수행 @Component @RequiredArgsConstructor public class JwtFilter extends OncePerRequestFilter { // JWT 토큰 접두사 private final String TOKEN_PREFIX = "Bearer "; private final JwtService jwtService; @Override protected void doFilterInternal( HttpServletRequest request, HttpServletResponse response, FilterChain filterChain ) throws ServletException, IOException { // Request의 Authorization Header 가져오기 String authorizationHeader = request.getHeader(HttpHeaders.AUTHORIZATION); // Request에 Authorization Header가 미설정된 경우 JWT 검증을 수행하지않고 다음 필터로 전달 if (authorizationHeader == null) { filterChain.doFilter(request, response); return; // 종료 } // JWT 토큰 가져오기 ( Bearer 접두사 제거 ) String token = authorizationHeader.substring(TOKEN_PREFIX.length()); // JwtDecoder객체.decode(토큰)를 통해 JWT 검증 수행 // 해당 서버의 Public Key로 발급한 JWT토큰이 아닌 경우 다음 필터로 전달 if(!jwtService.validate(token)){ filterChain.doFilter(request, response); return; // 종료 } // 검증이 완료된 경우 해당 JWT ID를 가져온 후 // 해당 JWT ID를 기반으로 UserDetails 구현체를 생성 및 // 위에서 AbstractAuthenticationToken를 상속하여 정의한 // AuthenticationToken 객체의 생성자에 전달하여 생성 Long jwtTokenId = jwtService.parseId(authToken); DefaultCurrentUser currentUser = jwtService.getUserDetailFromToken(authToken); AuthenticationToken token = new AuthenticationToken( currentUser, currentUser.getAuthorities() ); // 해당 AuthenticationToken 객체는 Authentication 인터페이스의 구현체이므로 // SecurityContextHolder에 등록 SecurityContextHolder.getContext().setAuthentication(authToken); // 모든 작업이 끝난 후 다음 필터로 전달 filterChain.doFilter(request, response); } }。
HttpServletRequest객체.getHeader("Authorization")을 통해HttpRequest의Authorization Header가져오기
▶ 이때 해당"Authorization"는 이미Enum으로 구현됨. (HttpHeaders.AUTHORIZATION)
。Request에Authorization Header가 미설정된 경우JWT 검증을 수행하지않고 다음필터로 전달
。JWT 검증의 경우JwtDecoder객체.decode(토큰)를 통해 수행
▶ 해당서버의Public Key로 발급한JWT토큰이 아닌 경우 다음필터로 전달
JWT 발급 / 검증
。검증이 모두 완료된 경우JWT Token ID를 기준으로 JwtService 클래스 내 구현된메서드를 통해UserDetails를 생성 후 이를 기반으로 위에서 정의한인증된AuthenticationToken 클래스 객체생성 후SecurityContextHolder에 등록
▶AuthenticationToken 클래스는AbstractAuthenticationToken 클래스를 상속하여 정의한Authentication 인터페이스 구현체이므로SecurityContextHolder에 등록 가능
▶JwtFilter에서SecurityContextHolder에setAuthentication할 때Authentication.getPrincipal()으로 세팅한DefaultCurrentUser가@AuthenticationPrincipal UserDetails객체로주입SecurityContextHolder/SecurityContext/Authentication
。이후 정의된 해당커스텀 JWT 필터 구현체를@Configuration 클래스에 추가 정의
Web Security Configuration
@AuthenticationPrincipal
。현재로그인한사용자 정보를 가져오기위한 용도로 사용되며로그인되어SecurityContext에 저장된인증된Authentication 객체의principal을UserDetails 구현체에주입하는어노테이션
▶Controller의매개변수에UserDeatils 구현체와 함께 선언하여 주입을 수행하도록 설정
。JwtFilter에서SecurityContextHolder에setAuthentication할 때Authentication.getPrincipal()으로 세팅한DefaultCurrentUser가주입@GetMapping("/duplicate-id") @ResponseStatus(HttpStatus.OK) public ApiResult<Boolean> isDuplicateId( @AuthenticationPrincipal DefaultCurrentUser defaultCurrentUser, @RequestParam(required = false) Long id ){ System.out.println(defaultCurrentUser.getId()); System.out.println(defaultCurrentUser.getUsername()); System.out.println(defaultCurrentUser.getAuthorities()); /// }
▶ 현재SecurityContextHolder에 등록된UserDetails정보를 기반으로 출력