JwtUtil 클래스
package org.example.authfilter.config;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.security.Key;
import java.util.Base64;
import java.util.Date;
@Slf4j(topic = "JwtUtil")
@Component
public class JwtUtil {
private static final String BEARER_PREFIX = "Bearer ";
private static final long TOKEN_TIME = 60 * 60 * 1000L; // 60분
@Value("${jwt.secret.key}")
private String secretKey;
private Key key;
private final SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
@PostConstruct
public void init() {
byte[] bytes = Base64.getDecoder().decode(secretKey);
key = Keys.hmacShaKeyFor(bytes);
}
public String createToken(Long userId, String email) {
Date date = new Date();
return BEARER_PREFIX +
Jwts.builder()
.setSubject(String.valueOf(userId))
.claim("email", email)
.setExpiration(new Date(date.getTime() + TOKEN_TIME))
.setIssuedAt(date) // 발급일
.signWith(key, signatureAlgorithm) // 암호화 알고리즘
.compact();
}
public String substringToken(String tokenValue) {
if (StringUtils.hasText(tokenValue) && tokenValue.startsWith(BEARER_PREFIX)) {
return tokenValue.substring(7);
}
log.error("Not Found Token");
throw new NullPointerException("Not Found Token");
}
public Claims extractClaims(String token) {
return Jwts.parserBuilder()
.setSigningKey(key)
.build()
.parseClaimsJws(token)
.getBody();
}
}
JwtFilter 클래스
package org.example.authfilter.config;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.MalformedJwtException;
import io.jsonwebtoken.UnsupportedJwtException;
import jakarta.servlet.FilterConfig;
import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import java.io.IOException;
import java.util.regex.Pattern;
@Slf4j(topic = "JwtFilter")
@RequiredArgsConstructor
public class JwtFilter implements Filter {
private final JwtUtil jwtUtil;
private final Pattern authPattern = Pattern.compile("^/v\\d+/auth.*");
@Override
public void init(FilterConfig filterConfig) throws ServletException {
Filter.super.init(filterConfig);
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
String url = httpRequest.getRequestURI();
// `/v{숫자}/auth`로 시작하는 URL은 필터를 통과하지 않도록 설정
if (authPattern.matcher(url).matches()) {
chain.doFilter(request, response);
return;
}
// NOTE: 위의 방법이 이해가 어려운 분은 이런 방법을 사용하셔도 좋습니다.
// if (url.startsWith("/v1/auth") || url.startsWith("/v2/auth")) {
// chain.doFilter(request, response);
// return;
// }
String bearerJwt = httpRequest.getHeader("Authorization");
if (bearerJwt == null || !bearerJwt.startsWith("Bearer ")) {
// 토큰이 없는 경우 400을 반환합니다.
httpResponse.sendError(HttpServletResponse.SC_BAD_REQUEST, "JWT 토큰이 필요합니다.");
return;
}
String jwt = jwtUtil.substringToken(bearerJwt);
try {
// JWT 유효성 검사와 claims 추출
Claims claims = jwtUtil.extractClaims(jwt);
// 사용자 정보를 ArgumentResolver 로 넘기기 위해 HttpServletRequest 에 세팅
httpRequest.setAttribute("userId", Long.parseLong(claims.getSubject()));
httpRequest.setAttribute("email", claims.get("email", String.class));
chain.doFilter(request, response);
} catch (SecurityException | MalformedJwtException e) {
log.error("Invalid JWT signature, 유효하지 않는 JWT 서명 입니다.", e);
httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED, "유효하지 않는 JWT 서명입니다.");
} catch (ExpiredJwtException e) {
log.error("Expired JWT token, 만료된 JWT token 입니다.", e);
httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED, "만료된 JWT 토큰입니다.");
} catch (UnsupportedJwtException e) {
log.error("Unsupported JWT token, 지원되지 않는 JWT 토큰 입니다.", e);
httpResponse.sendError(HttpServletResponse.SC_BAD_REQUEST, "지원되지 않는 JWT 토큰입니다.");
}
}
@Override
public void destroy() {
Filter.super.destroy();
}
}
FilterConfig 클래스
package org.example.authfilter.config;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@RequiredArgsConstructor
public class FilterConfig {
private final JwtUtil jwtUtil;
// Filter 등록
@Bean
public FilterRegistrationBean<JwtFilter> jwtFilter() {
FilterRegistrationBean<JwtFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new JwtFilter(jwtUtil));
registrationBean.addUrlPatterns("/*");
return registrationBean;
}
}
AuthUserArgumentResolver 클래스
package org.example.authfilter.config;
import jakarta.servlet.http.HttpServletRequest;
import org.example.authfilter.annotation.Auth;
import org.example.authfilter.dto.AuthUser;
import org.springframework.core.MethodParameter;
import org.springframework.lang.Nullable;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
public class AuthUserArgumentResolver implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter parameter) {
boolean hasAuthAnnotation = parameter.getParameterAnnotation(Auth.class) != null;
boolean isAuthUserType = parameter.getParameterType().equals(AuthUser.class);
// @Auth 어노테이션과 AuthUser 타입이 함께 사용되지 않은 경우 예외 발생
if (hasAuthAnnotation != isAuthUserType) {
throw new IllegalArgumentException("@Auth와 AuthUser 타입은 함께 사용되어야 합니다.");
}
return hasAuthAnnotation;
}
@Override
public Object resolveArgument(
@Nullable MethodParameter parameter,
@Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest,
@Nullable WebDataBinderFactory binderFactory
) {
HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest();
// JwtFilter 에서 set 한 userId, email 값을 가져옴
Long userId = (Long) request.getAttribute("userId");
String email = (String) request.getAttribute("email");
return new AuthUser(userId, email);
}
}
WebConfig 클래스
package org.example.authfilter.config;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.List;
@Configuration
@RequiredArgsConstructor
public class WebConfig implements WebMvcConfigurer {
// ArgumentResolver 등록
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(new AuthUserArgumentResolver());
}
}
'TIL(Today I Learned)' 카테고리의 다른 글
[CS스터디] Blocking I/O & Non-Blocking I/O (+ 동기와 비동기) (1) | 2024.09.29 |
---|---|
[내배캠] 아웃소싱 프로젝트 후 KPT 회고 작성 (0) | 2024.09.25 |
[MYSQL] 시간 차이 구하는 방법 3가지 (0) | 2024.09.05 |
[git/github] 중요한 정보가 담긴 내용까지 git push 안 하고 싶을 때 (1) | 2024.09.04 |
[Java/코딩테스트] 문자열 다루기 기본( + 아스키코드에 대하여) (0) | 2024.08.23 |