JwtAuthenticationFilter는 JWT 토큰으로 인증하고 SecurityContextHolder에 추가하는 필터를 설정하는 클래스이다.
@RequiredArgsConstructor
@Slf4j
public class JwtTokenFilter extends OncePerRequestFilter { //api 요청을 할 때 한 번만 인증을 거친다??
private final UserService userService;
private final String secretKey;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
//Token에서 Claim 꺼내기
final String authorizationHeader = request.getHeader(HttpHeaders.AUTHORIZATION);
if(authorizationHeader == null || !authorizationHeader.startsWith("Bearer ")){ //header에 AUTHORIZATION이 없거나, Bearer로 시작하지 않으면 filter
log.error("header가 없거나, 형식이 틀립니다. - {}", authorizationHeader);
filterChain.doFilter(request, response);
return;
}
String token;
try {
token = authorizationHeader.split(" ")[1].trim();
} catch (Exception e) {
log.error("토큰을 분리하는데 실패했습니다. - {}", authorizationHeader);
filterChain.doFilter(request, response);
return;
}
log.info("token : {}", token);
//토큰이 Valid한지 확인하기
if(JwtTokenUtil.isExpired(token, secretKey)){
filterChain.doFilter(request, response);
return;
}
//userName 넣기, 문 열어주기
String userName = JwtTokenUtil.getUserName(token, secretKey);
log.info("userName : {}", userName);
User user = userService.getUserByUserName(userName);
//AuthenticationToken 만들기
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user.getUserName(), null, List.of(new SimpleGrantedAuthority(user.getRole().name())));
//디테일 설정하기
authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
filterChain.doFilter(request, response);
}
}
GenericFilterBean
vs OncePerRequestFilter
GenericFilterBean은 기존 필터에서 가져올 수 없는 스프링의 설정 정보를 가져올 수 있게 확장된 추상 클래스이다. 서블릿은 사용자의 요청을 받으면 서블릿을 생성해서 메모리에 저장해두고 동일한 클라이언트의 요청을 받으면 재활용하는 구조여서 GenericFilterBean
을 상속받으면 RequestDispatcher
에 의해 다른 서블릿으로 디스패치되면서 필터가 두 번 실행되는 현상이 발생할 수 있다.
이 같은 문제를 해결하기 위해 등장한 것이 OncePerRequestFilter
이며, 이 클래스도 GenericFilterBean
을 상속받고 있지만, 이 클래스를 상속받아 구현한 필터는 매 요청마다 한 번만 실행되게끔 구현된다.
Filter base class that aims to guarantee a single execution per request dispatch, on any servlet container. It provides a doFilterInternal method with HttpServletRequest and HttpServletResponse arguments.
어느 서블릿 컨테이너에서나 요청 당 한 번의 실행을 보장하는 것을 목표로 한다.doFilterInternal메소드와 HttpServletRequest와 HttpServletResponse인자를 제공한다.
중요한 점은 요청 당 한번의 실행을 보장한다는 것이다.
서블릿이 실행되는 동안 다른 서블릿에 요청이 올 수도 있다.
예를들어, 어느 필터에서 헤더를 확인 한 후 특정 url로 포워딩 시킨다고 가정하자.
이때 예외가 발생하지 않았다면, url로 포워딩 시키는 것 자체가 서블릿 실행 중 요청이 온 것이다.
OncePerRequestFilter를 사용하지 않았다면 앞서 거친 필터들을 또 한번 거칠 것이고, 쓸데없는 자원만 낭비하는 셈이다.
결국 동일한 request안에서 한번만 필터링을 할 수 있게 해주는 것이 OncePerRequestFilter의 역할이고 보통 인증 또는 인가와 같이 한번만 거쳐도 되는 로직에서 사용한다.
인증 또는 인가를 거치고나서 특정 url로 포워딩하면, 요청이 들어왔으니 인증 및 인가필터를 다시 실행시켜야 하지만, OncePerRequestFilter를 사용함으로써 인증이나 인가를 한번만 거치고 다음 로직을 진행할 수 있도록 한다.
출처 : https://emgc.tistory.com/119
OncePerRequestFilter
에서 구현하는 메서드이다. doFilter()는 다음 filter-chain을 실행하는 것이며, 마지막 filter-chain인 경우 Dispatcher Servlet이 실행된다.
참고: https://pgnt.tistory.com/102
UsernamePasswordAuthenticationToken
1) 사용자가 사용자 이름과 암호를 제출하면 UsernamePasswordAuthenticationFilter
는 HttpServletRequest
에서 사용자 이름과 암호를 추출하여 일종의 인증인 UsernamePasswordAuthenticationToken
을 생성합니다.
2) 다음으로 UsernamePasswordAuthenticationToken이 인증을 위해 AuthenticationManager로 전달됩니다. AuthenticationManager의 모양에 대한 세부 정보는 사용자 정보가 저장되는 방식에 따라 다릅니다.
3) 인증이 실패하면,
4) 인증에 성공하면,
SecurityContextHolder
에서 설정됩니다.