728x90
반응형
😂 문제 상황
Spring Security + JWT 를 사용하여 현재 로그인이 되어 있는 사용자에 대한 정보를 얻기 위해
@AuthenticationPrincipal 어노테이션을 사용했다.
하지만 해당 사용자에 대한 정보는 넘어오지 않고 Null 값만 넘어왔다.
문제의 그 Controller 이다.
내가 구현한 코드는 아래와 같다.
JwtAuthenticationFilter.java
@RequiredArgsConstructor
@Component
public class JwtAuthenticationFilter extends GenericFilterBean {
private final JwtTokenProvider jwtTokenProvider;
public static final String TOKEN_HEADER = "Authorization";
public static final String TOKEN_PREFIX = "Bearer";
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
// 1. Request Header 에서 JWT 토큰 추출
String token = resolveToken((HttpServletRequest) request);
// 2. validateToken 으로 토큰 유효성 검사
if (token != null && jwtTokenProvider.validateToken(token)) {
Authentication authentication = jwtTokenProvider.getAuthentication(token);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
chain.doFilter(request, response);
}
// RequestHeader 에서 토큰 정보 추출
private String resolveToken(HttpServletRequest request) {
String bearerToken = request.getHeader(TOKEN_HEADER);
if(!ObjectUtils.isEmpty(bearerToken) && bearerToken.startsWith(TOKEN_PREFIX)) {
return bearerToken.substring(TOKEN_PREFIX.length());
}
return null;
}
}
JwtTokenProvider.java
@Component
@Slf4j
public class JwtTokenProvider {
private static final String AUTHORITIES_KEY = "auth";
private static final String BEARER_TYPE = "Bearer";
//유효시간 = 1시간
private static final long ACCESS_TOKEN_EXPIRE_TIME = 60 * 60 * 1000L;
// 유효시간 = 7일
private static final long REFRESH_TOKEN_EXPIRE_TIME = 7 * 24 * 60 * 60 * 1000L;
private final Key key;
public JwtTokenProvider(@Value("${jwt.secretKey}") String secretKey) {
byte[] keyBytes = Decoders.BASE64.decode(secretKey);
this.key = Keys.hmacShaKeyFor(keyBytes);
}
/**
* 유저 정보로 생성된 Authenticaiton으로 AccessToken, RefreshToken 생성
*
* @param authentication
* @return
*/
public JwtToken createToken(Authentication authentication) {
// 권한 가져오기
String authorities = authentication.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.joining(","));
long now = (new Date()).getTime();
// AccessToken 생성
Date accessTokenExpiratedTime = new Date(now + ACCESS_TOKEN_EXPIRE_TIME);
String accessToken = Jwts.builder()
.setSubject(authentication.getName())
.claim(AUTHORITIES_KEY, authorities)
.setExpiration(accessTokenExpiratedTime)
.signWith(key, SignatureAlgorithm.HS256)
.compact();
// refreshToken 생성
String refreshToken = Jwts.builder()
.setExpiration(new Date(now + REFRESH_TOKEN_EXPIRE_TIME))
.signWith(key, SignatureAlgorithm.HS256)
.compact();
return JwtToken.builder()
.grantType(BEARER_TYPE)
.accessToken(accessToken)
.refreshToken(refreshToken)
.build();
}
// Jwt 토큰을 복호화하여 토큰에 들어있는 인증 정보를 꺼내는 메서드
public Authentication getAuthentication(String accessToken) {
// Jwt 토큰 복호화
Claims claims = parseClaims(accessToken);
if (claims.get(AUTHORITIES_KEY) == null) {
throw new RuntimeException("권한 정보가 없는 토큰입니다.");
}
// 해당 사용자의 권한 정보 가져오기
Collection<? extends GrantedAuthority> authorities =
Arrays.stream(claims.get(AUTHORITIES_KEY).toString().split(","))
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
// UserDetails 객체 생성해서 Authentication 리턴
UserDetails principal = new User(claims.getSubject(), "", authorities);
return new UsernamePasswordAuthenticationToken(principal, "", authorities);
}
/**
* 토큰 정보 검증
* @param token
* @return
*/
public boolean validateToken(String token) {
try {
Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token);
return true;
} catch (io.jsonwebtoken.security.SecurityException | MalformedJwtException e) {
log.info("Invalid JWT Token", e);
} catch (ExpiredJwtException e) {
log.info("Expired JWT Token", e);
} catch (UnsupportedJwtException e) {
log.info("Unsupported JWT Token", e);
} catch (IllegalArgumentException e) {
log.info("JWT claims string is empty.", e);
}
return false;
}
private Claims parseClaims(String accessToken) {
try {
return Jwts.parserBuilder()
.setSigningKey(key)
.build()
.parseClaimsJws(accessToken)
.getBody();
} catch (ExpiredJwtException e) {
return e.getClaims();
}
}
}
문제가 발생했던 부분은 이 부분이였다.
❗ 인증 객체를 저장하는 과정에서 DB에서 해당 User 정보를 가져오는 것이 아닌 새로 생성함으로써 발생하는 문제였다.
😁 해결
// Jwt 토큰을 복호화하여 토큰에 들어있는 인증 정보를 꺼내는 메서드
public Authentication getAuthentication(String accessToken) {
// Jwt 토큰 복호화
Claims claims = parseClaims(accessToken);
if (claims.get(AUTHORITIES_KEY) == null) {
throw new RuntimeException("권한 정보가 없는 토큰입니다.");
}
// 해당 사용자의 권한 정보 가져오기
Collection<? extends GrantedAuthority> authorities =
Arrays.stream(claims.get(AUTHORITIES_KEY).toString().split(","))
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
// UserDetails 객체 생성해서 Authentication 리턴
UserDetails principal = customUserDetailsService.loadUserByUsername(claims.getSubject());
return new UsernamePasswordAuthenticationToken(principal, accessToken, authorities);
}
새로 User 객체를 생성하는 것이 아니라, 해당 사용자의 정보를 토대로 UserDetails 객체를 생성하였다.
반응형
'Knowledge > Trouble Shooting' 카테고리의 다른 글
웹소켓 STOMP 에러 트러블 슈팅 (0) | 2024.05.27 |
---|---|
Inferred type 'S' for type parameter 'S' is not within its bound; (0) | 2024.05.05 |
MySQL 테이블 'order' 생성 오류 (0) | 2024.03.31 |
Intellij [HY000][1130] Host 'ip주소' is not allowed to connect to this MariaDB server. (1) | 2024.03.28 |
Controller Get, Post 오류 (0) | 2024.03.26 |