구현 목적

 

JwtAuthenticationFilter는 Spring Security의 필터 중 하나로, 클라이언트의 요청이나 서버의 응답을 가로채어서 JWT 토큰을 확인하고 인증을 처리하는 역할을 합니다. 클라이언트가 요청을 보낼 때 해당 필터를 거쳐 JWT 토큰을 검증하고, 서버가 응답을 할 때도 해당 필터를 통해 JWT 토큰을 생성하고 응답에 포함시킬 수 있습니다. 이를 통해 보안 및 권한 검사를 수행하고, 사용자 인증 및 권한 부여를 처리할 수 있습니다.

 

public class JwtAuthenticationFilter extends OncePerRequestFilter

 

OncePerRequestFilter

 

OncePerRequestFilter는 Spring Security에서 제공하는 필터 중 하나로, 각 요청당 한 번만 실행되도록 보장하는 필터입니다. 이 필터는 서블릿 필터의 추상 클래스인 GenericFilterBean을 확장하며, doFilterInternal 메서드를 구현하여 필터링 로직을 정의합니다.

OncePerRequestFilter는 보통 Spring Security에서 인증, 권한 부여, 요청 로깅 등과 같은 작업을 수행하는 데 사용됩니다. 요청이 필터 체인을 따라 흐를 때 한 번만 실행되어야 하는 경우에 이 필터를 사용하는 것이 유용합니다.


doFilterInternal (OncePerRequestFilter 의 구현 메서드)
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
    FilterChain filterChain) throws ServletException, IOException

 

OncePerRequestFilter 클래스는 GenericFilterBean을 확장하고 있으며, doFilterInternal 메서드를 오버라이드하여 필터링 로직을 정의합니다. 따라서 개발자는 OncePerRequestFilter를 상속받아 doFilterInternal 메서드를 구현하여 원하는 필터 동작을 지정할 수 있습니다. doFilterInternal 메서드는 각 요청에 대해 한 번 호출되며, 이를 통해 요청을 필터링하고 원하는 처리를 수행할 수 있습니다.

 

서블릿 컨테이너로부터 요청을 받거나 응답을 보내고, 필터체인 내에서 다음 필터로 요청을 전달하는 역할을 수행 합니다.

 

FilterChain

 

필터 체인은 웹 애플리케이션에서 요청을 받고 응답을 보내는 과정 중에 여러 필터가 연속적으로 적용되는 메커니즘을 말합니다. 각각의 필터는 요청을 가로채어 원하는 작업을 수행한 뒤, 다음 필터로 요청을 전달하거나 응답을 수정하여 다음 단계로 전달합니다. 필터 체인은 보안, 로깅, 인코딩 변환 등과 같은 다양한 작업을 수행하기 위해 사용됩니다.

 

각각의 체인은 필터를 의미하고, 해당 필터에선 HTTP 의 요청에 대한 특정 작업을 수행합니다. 해당 작업이 끝나면 다음 체인으로 연결을 시켜줘 작업을 이행할 수 있게 합니다.


doFilterIntenal 에서 사용 될 resolveTokenFromRequest 메서드

private String resolveTokenFromRequest(HttpServletRequest request) {
  String token = request.getHeader(TOKEN_HEADER);

  if (!ObjectUtils.isEmpty(token) && token.startsWith(TOKEN_PREFIX)) {
    return token.substring(TOKEN_PREFIX.length());
  }

  return null;
}

 

1. 토큰은 HTTP 에서 HEADER 에 저장 되기 때문에 어떤 키를 기준으로 토큰을 주고 받을지 정해주기 위해

TOKEN_HEADER 설정. ("Authorization")

 

2. 인증 타입을 나태낼 TOKEN_PREFIX. JWT는 Bearer를 사용한다.

("Bearer ") 이후에 토큰값이 올 수 있게 Bearer 이후 한칸을 띈다.

 

이후 token 값을 가져와 반환해준다. (없으면 null)


다시 doFilterIntenal 
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
    FilterChain filterChain) throws ServletException, IOException {
  String token = this.resolveTokenFromRequest(request);

  if (StringUtils.hasText(token) && this.tokenProvider.validateToken(token)) {
    Authentication auth = this.tokenProvider.getAuthentication(token);
    SecurityContextHolder.getContext().setAuthentication(auth);
  }

  filterChain.doFilter(request, response);
}

 

1. resolveTokenFromRequest를 이용해 토큰 값을 가져온다.

2. 토큰이 존재하고, 유효 하다면 (tokenProvider 의 따로 구현해 놓은 validateToken을 이용해 검증, 토큰의 만료 시간 등을 검증 해준다.) 로직 진행.

 

조건문을 통과한 다음 실행될 로직에 필요한 getAuthentication 메서드 (TokenProvider)
public Authentication getAuthentication(String jwt) {
  UserDetails userDetails =
      this.authenticationService.loadUserByUsername(this.getUsername(jwt));
  return new UsernamePasswordAuthenticationToken(userDetails, "", userDetails.getAuthorities());
}

 

Authentication 더 알아보기

2024.02.29 - [Java/Spring] - Authentication 인증 객체에 대하여

 

UserDetails를 가져오기 위해 사용 된 loadUserByUsername 메서드

(userDetailsService를 구현한 AuthenticationService 에서 Override 구현 돼 있다.)

public class AuthenticationService implements UserDetailsService {

  private final AccountRepository accountRepository;

  @Override
  public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    return this.accountRepository.findByUsername(username)
        .orElseThrow(() -> new UsernameNotFoundException("해당 유저는 존재하지 않습니다."));
  }
}

 

 

getAuthentication 메서드의 UsernamePasswordAuthenticationToken 설명

- 지금은 credentials가 비어있는데 이는 사용자의 자격 증명이 이미 JWT 토큰 안에 포함 되어 있기 때문이다.

 

다시 doFilterIntenal
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
    FilterChain filterChain) throws ServletException, IOException {
  String token = this.resolveTokenFromRequest(request);

  if (StringUtils.hasText(token) && this.tokenProvider.validateToken(token)) {
    Authentication auth = this.tokenProvider.getAuthentication(token);
    SecurityContextHolder.getContext().setAuthentication(auth);
  }

  filterChain.doFilter(request, response);
}

그렇게 가져온 Authentication을 여기서는 변수명 auth로 초기화 시켰고, 이를 SecurityContextHolder에 넣어준다.

 

그렇게 검증 된 SecurityContextHolder 가 존재하게 되고 검증이 되지 않았다면 다음 문구인,

filtetChain.doFilter(request, response)를 통해 다음 체인을 진행하게 된다.

+ Recent posts