TokenProvider
의 claim을 통해 Authentication
을 생성하는 함수를 만들어 보겠습니다.
위 사진에서 2번 과정에 해당합니다.
jwt token이 유효하다면 claim을 분석해서 Authentication
객체인 UsernamePasswordAuthenticationToken
을 만들고 이를 SecurityContext
에 넣어주는 작업입니다. 이걸 해야 Spring Security가 인증된 사용자의 요청이라고 판단하고 이후의 필터를 통과할 수 있게 해줍니다.
먼저 UsernamePasswordAuthenticationToken
객체를 조립하는 함수입니다.
TokenProvider
...
public Authentication getAuthentication(String token, Claims claims) {
Collection<? extends GrantedAuthority> authorities = Arrays.stream(claims.get(AUTHORITIES_KEY).toString().split(","))
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
UserPrinciple principle = new UserPrinciple(claims.getSubject(), claims.get(USERNAME_KEY, String.class), authorities);
return new UsernamePasswordAuthenticationToken(principle, token, authorities);
}
...
이제 Provider도 Config파일에 등록해서 Bean으로 등록해줍니다.
JwtConfig
package com.jwt.domain.config;
@Configuration
@RequiredArgsConstructor
@EnableConfigurationProperties(JwtProperties.class)
public class JwtConfig {
@Bean
public TokenProvider jwtTokenProvider(JwtProperties jwtProperties) {
return new TokenProvider(jwtProperties.getSecret(), jwtProperties.getAccessTokenValidityInSeconds());
}
...
}
이제 jwtFilter
를 구현하겠습니다. 기본적으로 jwtFilter는 OncePerRequestFilter
를 extends합니다. OncePerRequestFilter는 왜 쓸까요?
OncePerRequestFilter vs GenericFilterBean
OncePerRequestFilter
은 GenericFilterBean
를 extends하여 한번 만 실행되도록 보장합니다.
(GenericFilterBean
은 여러 번 실행될 수 있습니다.)
Jwt유효성 검증은 요청당 한 번만 실행이 보장되어야 하므로 OncePerRequestFilter
를 상속하는 것이 적절합니다.
JwtFilter
먼저 resolveToken입니다. 이 함수는 요청의 헤더에서 Autherization 헤더를 가지고 와서 앞에 bearer
가 있는지 확인하고, 있다면 이 부분을 떼주는 역할을 합니다.
package com.jwt.domain.login.jwt;
@Slf4j
@Component
@RequiredArgsConstructor
public class JwtFilter extends OncePerRequestFilter {
public static final String AUTHORIZATION_HEADER = "Authorization";
private static final String BEARER_REGEX = "Bearer ([a-zA-Z0-9_\\-\\+\\/=]+)\\.([a-zA-Z0-9_\\-\\+\\/=]+)\\.([a-zA-Z0-9_.\\-\\+\\/=]*)";
private static final Pattern BEARER_PATTERN = Pattern.compile(BEARER_REGEX);
private final TokenProvider tokenProvider;
...
private String resolveToken(HttpServletRequest request) {
String bearerToken = request.getHeader(AUTHORIZATION_HEADER);
if(bearerToken != null && BEARER_PATTERN.matcher(bearerToken).matches()) {
return bearerToken.substring(7);
}
return null;
}
...
}
토큰을 상황에 따라 어떻게 처리할 지 구분한 private함수를 다음과 같이 구현해주겠습니다.
private void handleValidToken(String token, TokenValidationResult tokenValidationResult) {
Authentication authentication = tokenProvider.getAuthentication(token, tokenValidationResult.claims());
SecurityContextHolder.getContext().setAuthentication(authentication);
log.info("AUTH SUCCESS : {}", authentication.getName());
}
private void handleWrongToken(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain,
TokenValidationResult tokenValidationResult) throws IOException, ServletException {
request.setAttribute("result", tokenValidationResult);
filterChain.doFilter(request, response);
}
private void handleMissingToken(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws IOException, ServletException {
request.setAttribute("result",
new TokenValidationResult(TokenStatus.WRONG_AUTH_HEADER, null, null, null));
filterChain.doFilter(request, response);
}
이제 doFilterInternal
를 Override해줘야 합니다. doFilterInternal
에서 필터의 실제 로직을 구현해야 합니다.
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String token = resolveToken(request);
if(!StringUtils.hasText(token)) {
handleMissingToken(request, response, filterChain);
return;
}
TokenValidationResult tokenValidationResult = tokenProvider.validateToken(token);
if(!tokenValidationResult.isValid()) {
handleWrongToken(request, response, filterChain, tokenValidationResult);
return;
}
handleValidToken(token, tokenValidationResult);
filterChain.doFilter(request, response);
}
- 토큰이 없다.
- 토큰이 있는데 유효하지 않다.
- 토큰이 있고 유효하다.
3가지 경우로 나눠져 있습니다.