jwt 인증 프로세스에서 공통 로직을 분리하자
문제
토큰 처리에 필요한 util 구현체 TokenProvider의 로직에서 Bearer prefix를 제거하고 validate가 반복된다.
AS-IS
@Slf4j
@RequiredArgsConstructor
@Component
public class JwtProvider {
private final JwtService jwtService;
public final static String ISSUER = "side-match";
public final static String HEADER_AUTHORIZATION = "Authorization";
public final static String TOKEN_PRIFIX = "Bearer";
public static final Duration REFRESH_TOKEN_DURATION = Duration.ofDays(14);
public static final Duration ACCESS_TOKEN_DURATION = Duration.ofHours(2);
public String generateToken(User user, String role, Duration expiredAt) {
Date now = new Date();
Jwt jwt = jwtService.createJwt(user);
return Jwts.builder()
.issuer(ISSUER)
.subject(String.valueOf(user.getId()))
.id(String.valueOf(jwt.getId()))
.claim("role", role)
.issuedAt(now)
.expiration(new Date(now.getTime() + expiredAt.toMillis()))
.signWith(getKey(jwt.getJwtKey()))
.compact();
}
public Authentication getAuthentication(String authorizationHeader, User user) {
String token = getToken(authorizationHeader);
Long userId = getUserId(authorizationHeader);
String jwtKey = jwtService.loadJwtKeyByUserId(userId);
Claims payload = Jwts.parser()
.verifyWith(getKey(jwtKey))
.build()
.parseSignedClaims(token)
.getPayload();
return new UsernamePasswordAuthenticationToken(
user,
null,
List.of(new SimpleGrantedAuthority((String) payload.get("role")))
);
}
public Long getUserId(String authorizationHeader) throws JwtException {
String token = getToken(authorizationHeader);
String jwtKey = getTokenKey(token);
Claims payload = Jwts.parser()
.verifyWith(getKey(jwtKey))
.build()
.parseSignedClaims(token)
.getPayload();
return Long.parseLong(payload.getSubject());
}
private String getToken(String authorizationHeader) {
if (!authorizationHeader.startsWith(TOKEN_PRIFIX)) {
throw new IllegalArgumentException("잘못된 인증 헤더 요청입니다.");
}
return authorizationHeader.substring(TOKEN_PRIFIX.length());
}
private String getTokenKey(String token) {
return Jwts.parser().build().parseSignedClaims(token).getPayload().getId();
}
private SecretKey getKey(String jwtKey) {
return Keys.hmacShaKeyFor(Decoders.BASE64.decode(jwtKey));
}
}
header에서 토큰을 분리하는 메서드를 매번 호출해야 하고 verifyWith로 매번 검증해야 하는 문제를 해결해보자.
TO-BE
웹 요청의 처리는 필터에서 제공하는 HttpServletRequest과 HttpServletResponse를 활용하면 간단하게 해결할 수 있다.
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtProvider jwtProvider;
private final UserService userService;
@Override
protected void doFilterInternal(@NonNull HttpServletRequest request,
@NonNull HttpServletResponse response,
@NonNull FilterChain filterChain) throws ServletException, IOException {
try {
String authorizationHeader = getAuthorizationHeader(request);
String token = getToken(authorizationHeader);
jwtProvider.validateToken(token);
request.setAttribute("token", token);
Long userId = jwtProvider.getUserId(token);
User user = userService.loadUserById(userId);
Authentication authentication = getAuthentication(token, user);
SecurityContextHolder.getContext().setAuthentication(authentication);
filterChain.doFilter(request, response);
} catch (JwtException jwtException) {
log.info("잘못된 토큰으로 접근했습니다.");
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "유효하지 않은 토큰입니다.");
}
}
}
request의 header에서 Authorization을 가져와 Bearer을 제거한다.
TokenProvider의 모든 메서드에서 진행한 validateToken을 한 번만 실행할 수 있도록 필터에서 구현한다.
public void validateToken(String token) throws JwtException{
SecretKey key = getJwtKeyFromToken(token);
Jwts.parser()
.verifyWith(key)
.build()
.parseSignedClaims(token);
}
결과
컨트롤러에서 Authorization 헤더의 토큰 값을 꺼내와 TokenProvider를 통해 사용자의 식별자를 꺼내는 로직이 반복되던 것을
필터에서 일괄적으로 처리할 수 있게 구현했다. 이제는 컨트롤러에서 간편하게 Authentication 인터페이스를 통해
SecurityContextHolder에 저장된 인증 객체를 사용하여 사용자를 식별할 수 있다.
'프로젝트' 카테고리의 다른 글
[소마] 스프링에서 리다이렉트했는데 자꾸 로그인 페이지로 넘어갈 때 (0) | 2024.01.27 |
---|---|
[소마] JWT 검증하기 전에 parsing 해보자 (0) | 2024.01.26 |
[소마] 로그인 페이지 서버사이드 mvc 구현에서 endpoint 호출로 수정 (0) | 2024.01.17 |
[소마] application.properties 에 노출되면 안되는 값을 암호화하자 (0) | 2024.01.15 |
[소마] mysql에 저장할 때, 아이콘, 이모지가 저장 안 되는 문제 해결 (0) | 2023.11.14 |