Spring Security - JWT를 검증하는 필터 구현 , @AuthenticationPrincipal

TopOfTheHead·2025년 11월 23일

Spring Security

목록 보기
20/21

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 TokenRequest Authorization HeaderBearer {token} 형식으로 저장

  • JWT 검증순서
  1. Http Request에서 authorization header 가져오기
  2. Bearer 키워드 제거 후 토큰만 가져오기
  3. token유효 여부 검사
  4. 유효하면 id값 꺼내서 SecurityContextHolder인가된 객체로 저장

클라이언트가 전송한 JWT가 해당 서버 발급된 토큰인지에 대한 검증을 수행하는 필터 구현체 정의
OncePerRequestFilter상속하여 요청어플리케이션 내에서 여러번 forward하더라도 해당 필터 구현체는 단 한번만 동작하도록 설정
OncePerRequestFilterGenericFilterBean 구현체이므로 상속동일하게 Filter 구현체가 됨
▶ 일반 Filter 인터페이스구현한 경우 요청forward 할때마다 해당 필터 구현체도 각각 검증을 수행하게됨.
Spring Security - Filter

Spring Filter로서 사용 시 Web Security ConfigurationSecurityFilterChainHttpSecurity.addFilterBefore(필터구현체)를 통해 해당 커스텀 필터 구현체를 포함
▶ 이후 SecurityFilterChainServlet 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")을 통해 HttpRequestAuthorization Header 가져오기
▶ 이때 해당 "Authorization"는 이미 Enum으로 구현됨. ( HttpHeaders.AUTHORIZATION )

RequestAuthorization Header가 미설정된 경우 JWT 검증을 수행하지않고 다음 필터로 전달

JWT 검증의 경우 JwtDecoder객체.decode(토큰)를 통해 수행
▶ 해당 서버Public Key로 발급한 JWT토큰이 아닌 경우 다음 필터로 전달
JWT 발급 / 검증

검증이 모두 완료된 경우 JWT Token ID를 기준으로 JwtService 클래스 내 구현된 메서드를 통해 UserDetails를 생성 후 이를 기반으로 위에서 정의한 인증AuthenticationToken 클래스 객체 생성 후 SecurityContextHolder에 등록
AuthenticationToken 클래스AbstractAuthenticationToken 클래스를 상속하여 정의한 Authentication 인터페이스 구현체이므로 SecurityContextHolder에 등록 가능
JwtFilter 에서 SecurityContextHoldersetAuthentication 할 때 Authentication.getPrincipal() 으로 세팅한 DefaultCurrentUser@AuthenticationPrincipal UserDetails객체주입 SecurityContextHolder/SecurityContext/Authentication

이후 정의된 해당 커스텀 JWT 필터 구현체@Configuration 클래스에 추가 정의
Web Security Configuration

@AuthenticationPrincipal
。현재 로그인사용자 정보를 가져오기위한 용도로 사용되며 로그인되어 SecurityContext에 저장된 인증Authentication 객체principalUserDetails 구현체주입하는 어노테이션
Controller매개변수UserDeatils 구현체와 함께 선언하여 주입을 수행하도록 설정

JwtFilter 에서 SecurityContextHoldersetAuthentication 할 때 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 정보를 기반으로 출력

profile
공부기록 블로그

0개의 댓글