
로그인이 성공하면 기존에 단일 토큰만 발급했지만 Access/Refresh에 해당하는 다중 토큰을 발급해아 한다.
따라서 로그인이 성공한 이후 실행되는 successfulAuthentication() 메소드 또는 AuthenticationSuccessHandler를 구현한 클래스에서 2개의 토큰을 발급한다.
JWTUtil
package com.example.securityjwt.jwt;
import io.jsonwebtoken.Jwts;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Date;
@Component
public class JWTUtil {
private SecretKey secretKey;
public JWTUtil(@Value("${spring.jwt.secret}") String secret) {
secretKey = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), Jwts.SIG.HS256.key().build().getAlgorithm());
}
// 토큰의 특정 요소를 검증하는 메서드
public String getCategory(String token) {
return Jwts.parser().verifyWith(secretKey).build().parseSignedClaims(token).getPayload().get("category", String.class);
}
public String getUsername(String token) {
return Jwts.parser().verifyWith(secretKey).build().parseSignedClaims(token).getPayload().get("username", String.class);
}
public String getRole(String token) {
return Jwts.parser().verifyWith(secretKey).build().parseSignedClaims(token).getPayload().get("role", String.class);
}
public Boolean isExpired(String token) {
return Jwts.parser().verifyWith(secretKey).build().parseSignedClaims(token).getPayload().getExpiration().before(new Date());
}
// 토큰을 생성하는 메서드
public String createJWT(String category, String username, String role, Long expiredTime) {
return Jwts.builder()
.claim("category", category)
.claim("username", username)
.claim("role", role)
.issuedAt(new Date(System.currentTimeMillis()))
.expiration(new Date(System.currentTimeMillis() + expiredTime))
.signWith(secretKey)
.compact();
}
}
LoginFilter
package com.example.securityjwt.jwt;
import com.example.securityjwt.dto.CustomUserDetails;
import jakarta.servlet.FilterChain;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import java.util.Collection;
import java.util.Iterator;
@RequiredArgsConstructor
public class LoginFilter extends UsernamePasswordAuthenticationFilter {
private final AuthenticationManager authenticationManager;
private final JWTUtil jwtUtil;
@Override
public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse res) throws AuthenticationException {
// 클라이언트 요청에서 username, password 추출
String username = obtainUsername(req);
String password = obtainPassword(req);
// 스프링 시큐리티에서 username과 password를 검증하기 위해서는 token에 담아야 함
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(username, password, null);
// token을 검증을 위한 AuthenticationManager로 전달
return authenticationManager.authenticate(authToken);
}
// 로그인 성공시 실행하는 메서드 (여기서 JWT를 발급하면 됨)
@Override
protected void successfulAuthentication(HttpServletRequest req, HttpServletResponse res, FilterChain chain, Authentication authentication) {
CustomUserDetails userDetails = (CustomUserDetails) authentication.getPrincipal();
String username = userDetails.getUsername();
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
Iterator<? extends GrantedAuthority> iterator = authorities.iterator();
GrantedAuthority auth = iterator.next();
String role = auth.getAuthority();
// 토큰 생성
String access = jwtUtil.createJWT("access", username, role, 600000L);
String refresh = jwtUtil.createJWT("refresh", username, role, 86400000L);
// 응답 생성
res.setHeader("access", access);
res.addCookie(createCookie("refresh", refresh));
res.setStatus(HttpStatus.OK.value());
}
// 로그인 실패시 실행하는 메서드
@Override
protected void unsuccessfulAuthentication(HttpServletRequest req, HttpServletResponse res, AuthenticationException failed) {
res.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
}
private Cookie createCookie(String key, String value) {
Cookie cookie = new Cookie(key, value);
cookie.setMaxAge(24*60*60);
// cookie.setSecure(true);
// cookie.setPath("/");
cookie.setHttpOnly(true);
return cookie;
}
}
참고자료: 개발자 유미 스프링 JWT 심화