유저를 인증하고 식별하기 위한 토큰 기반 인증
세션과 달리 서버가 아닌 클라이언트에 저장됨
인증, 인가에 필요한 모든 정보를 자체적으로 지님
.을 구분자로 헤더, 내용, 서명 3가지의 암호화된 문자열로 이루어짐

헤더(Header)
내용(Payload)
서명(Signature)
단순 액세스 토큰만으로는 토큰 탈취 시 보안이 취약해지기 때문에, 이를 보완하기 위해 두개의 토큰으로 나눠서 사용
Access Token
Refresh Token
@Getter
@Setter
@Component
@ConfigurationProperties("jwt") // 프로퍼티에 경로를 바인딩하여 해당 경로의 값 참조
public class JwtProperties {
private String issuer;
private String secret;
}
@RequiredArgsConstructor
public class TokenAuthenticationFilter extends OncePerRequestFilter {
private final TokenProvider tokenProvider;
private final static String HEADER_AUTHORIZATION = "Authorization";
private final static String TOKEN_PREFIX = "Bearer ";
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
// 요청 헤더의 authorization 키 값 조회
String authorizationHeader = request.getHeader(HEADER_AUTHORIZATION);
// 가져온 값에서 접두사 제거
String token = getAccessToken(authorizationHeader);
// 가져온 토큰값이 유효한지 확인, 유효하다면 인증 정보를 만든다
if(tokenProvider.validToken(token))
{
Authentication authentication = tokenProvider.getAuthentication(token);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
filterChain.doFilter(request, response);
}
private String getAccessToken(String authorizationHeader) {
if(authorizationHeader != null && authorizationHeader.startsWith(TOKEN_PREFIX))
{
return authorizationHeader.substring(TOKEN_PREFIX.length());
}
return null;
}
}
@RequiredArgsConstructor
@Service // 토큰 생성 및 검증 서비스
public class TokenProvider {
private final JwtProperties jwtProperties;
private final RefreshTokenService refreshTokenService;
public Tokeninfo generateToken(User user, Duration accessTokenExpiredAt, Duration refreshTokenExpiredAt) {
Date now = new Date();
String accessToken = makeAccessToken(new Date(now.getTime()+accessTokenExpiredAt.toMillis()), user);
String refreshToken = makeRefreshToken(new Date(now.getTime()+refreshTokenExpiredAt.toMillis()), user);
refreshTokenService.saveToken(user.getEmail(), refreshToken);
return Tokeninfo.builder()
.accessToken(accessToken)
.refreshToken(refreshToken)
.build();
}
// 다형성 ?
public String makeAccessToken(Date expiry, User user){
Date now = new Date();
return Jwts.builder()
.setHeaderParam(Header.TYPE, Header.JWT_TYPE) // 헤더 타입 : JWT
.setIssuer(jwtProperties.getIssuer())
.setIssuedAt(now)
.setExpiration(expiry)
.setSubject(user.getEmail())
.claim("id", user.getId())
.signWith(SignatureAlgorithm.HS256, jwtProperties.getSecret())
.compact();
}
public String reissueAccessToken(User user, String refreshToken, Duration accessTokenExpiredAt){
if(validRefreshToken(user, refreshToken))
{
Date now = new Date();;
return makeAccessToken(new Date(now.getTime()+accessTokenExpiredAt.toMillis()), user);
}
return null;
}
public String makeRefreshToken(Date expiry, User user){
Date now = new Date();
String refreshToken = Jwts.builder()
.setHeaderParam(Header.TYPE, Header.JWT_TYPE) // 헤더 타입 : JWT
.setIssuer(jwtProperties.getIssuer())
.setIssuedAt(now)
.setExpiration(expiry)
.setSubject(user.getEmail())
.claim("id", user.getId())
.signWith(SignatureAlgorithm.HS256, jwtProperties.getSecret())
.compact();
// 저장소에 저장
return refreshToken;
}
public boolean validToken(String token) {
try{
Jwts.parser()
.setSigningKey(jwtProperties.getSecret())
.parseClaimsJws(token);
return true;
} catch (Exception e) {
return false;
}
}
public boolean validRefreshToken(User user, String refreshToken) {
if(validToken(refreshToken)) {
String savedToken = refreshTokenService.findByEmail(user.getEmail()));
if(savedToken == refreshToken) {
return true;
}
}
return false;
}
// 토큰 기반으로 인증 정보를 가져오는 작업
public Authentication getAuthentication(String token) {
Claims claims = getClaims(token);
// Authentication -> principal, credentials, authorities
// collections 보기
Set<SimpleGrantedAuthority> authorities = Collections.singleton(new SimpleGrantedAuthority("ROLE_USER"))
return new UsernamePasswordAuthenticationToken(
new org.springframework.security.core.userdetails.User(
claims.getSubject(), "",authorities
), // principal
token, // credentials
authorities
);
}
public Long getUserId(String token) {
Claims claims = getClaims(token);
return claims.get("id", Long.class);
}
private Claims getClaims(String token) {
return Jwts.parser()
.setSigningKey(jwtProperties.getSecret())
.parseClaimsJws(token)
.getBody();
}
}