1편 - Local User (https://thcoding.tistory.com/114)
2편 - (현재글)JWT (https://thcoding.tistory.com/115)
3편 - OAuth2 (https://thcoding.tistory.com/116)
4편 - OAuth2 test(https://thcoding.tistory.com/118)
저번 포스팅은 local User이 사용하는 코드에대한 리뷰였고 이번엔 JWT에 관한 코드들을 리뷰해보겠다.
JWT 란, Json Web token의 약자고, 일종의 사용기한이 있는 신분증같은것이다. 예를 들어, 유저가 비밀번호를 업데이트를 하고자 한다 치면, 이 유저는 인증이 되어있어야한다. 이 유저가 로그인할때 우리는 토큰을 주고, 유저는 이 토큰을 갖고 비밀번호를 업데이트하려고 하면 우리는 승인해준다.
ㅇ JwtConfig
@Configuration
public class JwtConfig {
private String secret;
private final UserDetailsService userDetailsService;
@Autowired
public JwtConfig(@Value("${jwt.secret}")String secret, UserDetailsService userDetailsService) {
this.secret = secret;
this.userDetailsService = userDetailsService;
}
@Bean
public AuthTokenProvider jwtProvider() {
return new AuthTokenProvider(secret,userDetailsService);
}
}
configuration 뜻 그대로 구성이다. jwt 토큰과 관련된 구성이다.
ㅇ JwtUtils
@Component
public class JwtUtils {
@Value("${jwt.secret}")
private String secretKey;
@Value("${jwt.expirationMs}")
private int expirationMs;
/**
* 사용자 식별자를 기반으로 JWT 토큰을 생성합니다.
*
* @param userId 사용자 식별자
* @return 생성된 JWT 토큰
*/
public String generateToken(String userId) {
Date now = new Date();
Date expiryDate = new Date(now.getTime() + expirationMs);
return Jwts.builder()
.setSubject(userId)
.setIssuedAt(now)
.setExpiration(expiryDate)
.signWith(SignatureAlgorithm.HS512, secretKey)
.compact();
}
/**
* 주어진 JWT 토큰에서 사용자 식별자를 추출합니다.
*
* @param token JWT 토큰
* @return 사용자 식별자
*/
public Long getUserIdFromToken(String token) {
Claims claims = Jwts.parser()
.setSigningKey(secretKey)
.parseClaimsJws(token)
.getBody();
return Long.parseLong(claims.getSubject());
}
/**
* 주어진 JWT 토큰의 유효성을 검사합니다.
*
* @param token JWT 토큰
* @return 토큰의 유효성 여부
*/
public boolean validateToken(String token) {
try {
Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token);
return true;
} catch (SignatureException ex) {
System.out.println("Invalid JWT signature");
} catch (MalformedJwtException ex) {
System.out.println("Invalid JWT token");
} catch (ExpiredJwtException ex) {
System.out.println("Expired JWT token");
} catch (UnsupportedJwtException ex) {
System.out.println("Unsupported JWT token");
} catch (IllegalArgumentException ex) {
System.out.println("JWT claims string is empty");
}
return false;
}
}
Jwt 토큰을 생성하고 검증한다.
ㅇ TokenValidFailedException
public class TokenValidFailedException extends RuntimeException {
public TokenValidFailedException() {
super("Failed to generate Token.");
}
private TokenValidFailedException(String message) {
super(message);
}
}
예외는 따로 이렇게 빼준다.
ㅇ AuthToken
@Slf4j
@RequiredArgsConstructor
public class AuthToken {
@Getter
private final String token;
private final Key key;
private static final String AUTHORITIES_KEY = "role";
AuthToken(String id, Date expiry, Key key) {
this.key = key;
this.token = createAuthToken(id, expiry);
}
AuthToken(String id, String role, Date expiry, Key key) {
this.key = key;
this.token = createAuthToken(id, role, expiry);
}
private String createAuthToken(String id, Date expiry) {
return Jwts.builder()
.setSubject(id)
.signWith(key, SignatureAlgorithm.HS256)
.setExpiration(expiry)
.compact();
}
private String createAuthToken(String id, String role, Date expiry) {
return Jwts.builder()
.setSubject(id)
.claim(AUTHORITIES_KEY, role)
.signWith(key, SignatureAlgorithm.HS256)
.setExpiration(expiry)
.compact();
}
public boolean validate() {
return this.getTokenClaims() != null;
}
public Claims getTokenClaims() {
try {
return Jwts.parserBuilder()
.setSigningKey(key)
.build()
.parseClaimsJws(token)
.getBody();
} catch (SecurityException e) {
log.info("Invalid JWT signature.");
} catch (MalformedJwtException e) {
log.info("Invalid JWT token.");
} catch (ExpiredJwtException e) {
log.info("Expired JWT token.");
} catch (UnsupportedJwtException e) {
log.info("Unsupported JWT token.");
} catch (IllegalArgumentException e) {
log.info("JWT token compact of handler are invalid.");
}
return null;
}
public Claims getExpiredTokenClaims() {
try {
Jwts.parserBuilder()
.setSigningKey(key)
.build()
.parseClaimsJws(token)
.getBody();
} catch (ExpiredJwtException e) {
log.info("Expired JWT token.");
return e.getClaims();
}
return null;
}
}
ㅇ AuthTokenProvider
@Slf4j
public class AuthTokenProvider {
private final UserDetailsService userDetailsService;
private final Key key;
private static final String AUTHORITIES_KEY = "role";
public AuthTokenProvider(String secret, UserDetailsService userDetailsService) {
this.key = Keys.hmacShaKeyFor(secret.getBytes());
this.userDetailsService = userDetailsService;
}
public AuthToken createAuthToken(String id, Date expiry) {
return new AuthToken(id, expiry, key);
}
public AuthToken createAuthToken(String id, String role, Date expiry) {
return new AuthToken(id, role, expiry, key);
}
public AuthToken convertAuthToken(String token) {
return new AuthToken(token, key);
}
public Authentication getAuthentication(AuthToken authToken) throws TokenValidFailedException {
UserDetails userDetails = userDetailsService.loadUserByUsername(authToken.getTokenClaims().getSubject());
return new UsernamePasswordAuthenticationToken(userDetails, authToken, userDetails.getAuthorities());
// if(authToken.validate()) {
//
// Claims claims = authToken.getTokenClaims();
// Collection<? extends GrantedAuthority> authorities =
// Arrays.stream(new String[]{claims.get(AUTHORITIES_KEY).toString()})
// .map(SimpleGrantedAuthority::new)
// .collect(Collectors.toList());
//
// log.debug("claims subject := [{}]", claims.getSubject());
// User principal = new User(claims.getSubject(), "", authorities);
//
// return new UsernamePasswordAuthenticationToken(principal, authToken, authorities);
// } else {
// throw new TokenValidFailedException();
// }
}
}
여기서 문제가 있었다. 마지막 부분에 주석처리한부분에서 deeplify 포스팅에선 주석처리된 부분으로 되어있었는데, 내 프로젝트에서는 오류가 발생해 블로깅해 바꿔주니 아주 잘 되었다. 진짜 이거때문인지 모르고 뭐때문인지 별걸 다 해봤는데 결국 여기서 문제가 있었던거였다. 한 3일은 날린거같다ㅠ
ㅇ TokenAuthenticationFilter
@Slf4j
@RequiredArgsConstructor
public class TokenAuthenticationFilter extends OncePerRequestFilter {
private final AuthTokenProvider tokenProvider;
@Override
protected void doFilterInternal(
HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException, java.io.IOException {
String tokenStr = HeaderUtil.getAccessToken(request);
AuthToken token = tokenProvider.convertAuthToken(tokenStr);
//토큰이 유효하면 토큰 기반으로 인증 객체 생성함
if (token.validate()) {
Authentication authentication = tokenProvider.getAuthentication(token);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
filterChain.doFilter(request, response);
}
}
사용자의 요청을 필터링하고, HTTP 요청 헤더에 포함된 토큰을 기반으로 사용자를 인증함. OncePerRequestFilter를 extends 했기때문에 한 요청당 한번만 이 필터를 거치게 된다. 그래서 테스트할때 디버깅하다보면, 여기서 어어어어어어어어엄청나게 걸린다.
String tokenStr = HeaderUtil.getAccessToken(request); 여기서 http요청 헤더에서 accessToken을 추출한다. 그다음 convertAuthToken으로 AuthToken객체로 변환한다. 그리고 주석대로 토큰이 유효하면 인증객체를 생성한다.
ㅇ HeaderUtil
public class HeaderUtil {
private final static String HEADER_AUTHORIZATION = "Authorization";
private final static String TOKEN_PREFIX = "Bearer ";
public static String getAccessToken(HttpServletRequest request) {
String headerValue = request.getHeader(HEADER_AUTHORIZATION);
if (headerValue == null) {
return null;
}
if (headerValue.startsWith(TOKEN_PREFIX)) {
return headerValue.substring(TOKEN_PREFIX.length());
}
return null;
}
}
다음 포스팅에선 Oauth2 에 관련한 나머지 코드들을 리뷰해보겠다.
'프로젝트 > 낙낙(KnockKnock)' 카테고리의 다른 글
이메일인증을 구현해보자(JavaMailSender) (0) | 2023.09.15 |
---|---|
Spring Security JWT 유저, Oauth에 대한 대대적인 공사-3(Oauth2) (0) | 2023.09.14 |
Spring Security JWT 유저, Oauth에 대한 대대적인 공사-1(Local User) (0) | 2023.09.12 |
배포 자동화를 해보자-3(Github Actions) (완료) (0) | 2023.09.12 |
배포 자동화를 해보자-2(Github Actions) (0) | 2023.07.31 |