@Slf4j
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtTokenProvider jwtTokenProvider;
@Override
protected void doFilterInternal(
HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
// Access Token 추출
String accessToken = resolveToken(request);
try { // 정상 토큰인지 검사
if (accessToken != null && jwtTokenProvider.validateAccessToken(accessToken)) {
Authentication authentication = jwtTokenProvider.getAuthentication(accessToken);
SecurityContextHolder.getContext().setAuthentication(authentication);
log.debug("Save authentication in SecurityContextHolder.");
}
} catch (IncorrectClaimException e) { // 잘못된 토큰일 경우
SecurityContextHolder.clearContext();
log.debug("Invalid JWT token.");
response.sendError(403);
} catch (UsernameNotFoundException e) { // 회원을 찾을 수 없을 경우
SecurityContextHolder.clearContext();
log.debug("Can't find user.");
response.sendError(403);
}
filterChain.doFilter(request, response);
}
// HTTP Request 헤더로부터 토큰 추출
public String resolveToken(HttpServletRequest httpServletRequest) {
String bearerToken = httpServletRequest.getHeader("Authorization");
if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7);
}
return null;
}
}
SecurityConfig.class
에서 UsernamePasswordAuthenticationFilter
이전에 통과할 Filter이다. 인증(Authentication)이 필요한 요청(Request)이 오면 요청의 헤더(Header)에서 Access Token을 추출하고 정상 토큰인지 검사한다.
jwtTokenProvider.validateToken(accessToken)
메서드를 통해 유효 기간을 제외하고 정상적인 Access Token인지 검사한다. 유효 기간만 제외하고 정상적인 토큰일 경우, 유저 모르게 Access Token을 재발급하고 로그인을 연장시킴과 동시에 다시 요청을 처리하도록 하기 위함이다.(Silent refresh) 이 작업을 진행하기 위해서는 프론트엔드에서 추가 작업이 필요하다. 재발급 API에 접근한 후, 응답에 따라 원래 처리할 요청 or 로그인으로 리다이렉트되게끔 해야 한다.
logout 처리된 Access Token은 더이상 사용하지 못하게 하기 위해 Redis에 저장할 예정이기 때문에 Redis에 logout 여부를 확인하는 로직을 추가했다.
@Slf4j
@Component
public class JwtAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request,
HttpServletResponse response,
AccessDeniedException accessDeniedException) throws IOException {
response.setCharacterEncoding("utf-8");
response.sendError(403, "권한이 없습니다.");
}
}
SecurityConfig에 예외 처리를 위해 설정해놓은 클래스이다. 인증(Authentication)이 실패했을 때 실행된다. 예를 들자면, ROLE_ADMIN 권한이 있는 사용자가 필요한 요청에 ROLE_USER 권한을 가진 사용자가 접근했을 때 실행된다.
@Slf4j
@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException authException) throws IOException, ServletException {
response.setCharacterEncoding("utf-8");
response.sendError(401, "잘못된 접근입니다.");
}
}
인가(Authorization)가 실패했을 때 실행된다. 예를 들자면, 로그인 된 사용자가 필요한 요청에 로그인하지 않은 사용자가 접근했을 때 실행된다.