RefreshToken์„ ์ ์šฉํ•œ JWT Filter์˜ ๋กœ์ง ์•Œ์•„๋ณด๊ธฐ

๋‹ด๋‹น์ž๋“ค
์นดํ…Œ๊ณ ๋ฆฌ
Skill
์ฃผ์ œ
Security
์™„๋ฃŒ์œจ%
ํ”„๋กœ์ ํŠธ
์ธ์Šคํƒ€๋€จ๋žจ
์ƒํƒœ
์™„๋ฃŒ

JWT ์„ค์ •๊ณผ ๋ฆฌํ”„๋ ˆ์‰ฌ ํ† ํฐ ์ ์šฉ ํ•ด๋ณด๊ธฐ

ํ˜„์žฌ ์ง„ํ–‰ํ•˜๋Š” ํŒ€ํ”„๋กœ์ ํŠธ์— ์ ์šฉํ•  JWT์— ๋Œ€ํ•ด์„œ ์•Œ์•„๋ณด๊ณ  ์ ์šฉํ•ด๋ด…๋‹ˆ๋‹ค.
์ด ํฌ์ŠคํŒ…์—์„œ๋Š” ๊ตฌํ˜„์— ๋Œ€ํ•œ ์„ธ์„ธํ•œ ์ฝ”๋“œ๋ฅผ ๋ชจ๋‘ ๋ณด์—ฌ๋“œ๋ฆฌ์ง„ ์•Š์Šต๋‹ˆ๋‹ค. JWT๋ฅผ ์‚ฌ์šฉํ•˜๊ณ ์ž ํ•˜์ง€๋งŒ Refresh Token์„ ์•„์ง ์‚ฌ์šฉํ•ด๋ณด์ง€ ๋ชปํ–ˆ๊ฑฐ๋‚˜ ๋กœ์ง์˜ ํ๋ฆ„์„ ์ดํ•ดํ•˜์ง€ ๋ชปํ•œ ์‚ฌ๋žŒ๋“ค์„ ์œ„ํ•ด ๋กœ์ง์˜ ํ๋ฆ„์„ ์„ค๋ช…ํ•˜๊ณ  ์ดํ•ด์‹œํ‚ค๋Š”๊ฒŒ ๋ชฉ์ ์ž…๋‹ˆ๋‹ค! ์ž์„ธํ•œ ์ฝ”๋“œ๋Š” ์ถ”ํ›„์— ๊นƒํ—™ ๋งํฌ๋ฅผ ๊ณต์œ ํ•˜๊ณ ์ž ํ•ฉ๋‹ˆ๋‹ค.

JWT๋ž€?

Json Web Token์˜ ์ค„์ž„๋ง์œผ๋กœ์จ ์„ธ์…˜ ๋ฐฉ์‹์˜ ๋‹จ์ ์„ ๋ณด์™„ํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ๋˜๋Š” ๊ธฐ์ˆ ์ž…๋‹ˆ๋‹ค. ์„œ๋ฒ„ ์ธก์— ์„ธ์…˜ ์ •๋ณด๋ฅผ ๋ชจ๋‘ ๊ฐ€์ง€๊ณ  ๊ด€๋ฆฌ๋˜๋Š” ์„ธ์…˜ ๋ฐฉ์‹์€ ๋ถ„์‚ฐ ์„œ๋ฒ„ ํ™˜๊ฒฝ์—์„œ ์ •ํ•ฉ์„ฑ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ณ , ๋ถ€ํ•˜๋ฅผ ์ค„์ผ ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ• ์ค‘์˜ ํ•˜๋‚˜์ž…๋‹ˆ๋‹ค.
์ž์„ธํ•œ ๋‚ด์šฉ์€ ์˜ˆ์ „์— ์ž‘์„ฑํ•œ ๊ธ€์„ ํ™•์ธ ํ•ด๋ณด๋ฉด ์ข‹์Šต๋‹ˆ๋‹ค. [JWT] Session๊ณผ JWT์— ๋Œ€ํ•ด ์•Œ์•„๋ณด๊ธฐ

๊ณ ๋ ค ์‚ฌํ•ญ

DB

ํ˜„์žฌ ์ง„ํ–‰ํ•˜๋Š” ํŒ€ ํ”„๋กœ์ ํŠธ์—์„œ๋Š” Redis๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ  RDB๋ฅผ ์ด์šฉํ•ด JWT๋ฅผ ์‚ฌ์šฉํ•  ์˜ˆ์ •์ž…๋‹ˆ๋‹ค. (Redis ์ ์šฉ์€ ๋‚˜์ค‘์—)

RefreshToken์˜ ์‚ฌ์šฉ ์ด์œ 

  • JWT๊ฐ€ ๊ฐ€์ง„ ๋‹จ์  ์ค‘ ํ•˜๋‚˜๋Š” ์•ก์„ธ์Šค ํ† ํฐ ํƒˆ์ทจ์˜ ์šฐ๋ ค์™€, ์„œ๋ฒ„ ์ธก์— ์„ธ์…˜ ์ •๋ณด๊ฐ€ ์ €์žฅ๋˜๋Š” ์„ธ์…˜ ๋ฐฉ์‹๊ณผ๋Š” ๋‹ค๋ฅด๊ฒŒ ์„œ๋ฒ„์—์„œ ์ธ์ฆ์„ ์ œ์–ดํ•  ์ˆ˜ ์—†์–ด ๋กœ๊ทธ์•„์›ƒ ๊ธฐ๋Šฅ์˜ ๊ตฌํ˜„์ด ์–ด๋ ต์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ ๋น„๊ต์  ์งง์€ ํ† ํฐ์˜ ๋งŒ๋ฃŒ๊ธฐํ•œ ๋•Œ๋ฌธ์— ์ง€์†์ ์œผ๋กœ ๋กœ๊ทธ์ธ์„ ๋‹ค์‹œ ํ•ด์ค˜์•ผํ•˜๋Š” ๋ฌธ์ œ์ ์ด ์žˆ์Šต๋‹ˆ๋‹ค.
    • ์•ก์„ธ์Šค ํ† ํฐ ํƒˆ์ทจ๊ฐ€ ๋˜์–ด๋„ ์„œ๋ฒ„ ์ธก์—์„œ ์•ก์„ธ์Šค ํ† ํฐ์„ ์ œ์–ดํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.
    • ๋กœ๊ทธ์ธ์„ ์ž๋™์œผ๋กœ ํ•ด์ค„ ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.
    • ์„œ๋ฒ„ ์ธก์—์„œ ํด๋ผ์ด์–ธํŠธ์˜ ์ธ์ฆ ์ •๋ณด๋ฅผ ๋ถ€๋ถ„์ ์œผ๋กœ ์ œ์–ดํ•  ์ˆ˜ ์žˆ์–ด์•ผํ•ฉ๋‹ˆ๋‹ค.
์ด๋Ÿฌํ•œ ๋ฌธ์ œ์ ๋“ค์„ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด RefreshToken์ด๋ผ๋Š” ๊ฒƒ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. RefreshToken์— ๋Œ€ํ•œ ์ถ”๊ฐ€์ ์ธ ์„ค๋ช…์€ [JWT] Session๊ณผ JWT์— ๋Œ€ํ•ด ์•Œ์•„๋ณด๊ธฐ์— ๋‚˜์™€์žˆ์Šต๋‹ˆ๋‹ค.

ํ† ํฐ์˜ ์ €์žฅ ์œ„์น˜

์ œ๊ฐ€ ๋ณธ JWT ๊ฐ•์˜์—์„œ๋Š” ํ† ํฐ์„ ์ƒ์„ฑํ•˜๊ณ  ๋ณ„๋„๋กœ ํ† ํฐ์„ ์ €์žฅํ•˜์ง€ ์•Š๊ณ  ๊ทธ๋ƒฅ ๋ณต์‚ฌ ๋ถ™์—ฌ๋„ฃ๊ธฐ๋กœ Postman์˜ Request Header์— ํ† ํฐ์„ ์ง์ ‘ ์ž…๋ ฅํ–ˆ์Šต๋‹ˆ๋‹ค.
์‹ค์ œ ์‚ฌ์šฉ์—์„œ๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ์ผ์ผ์ด ํ† ํฐ์„ Request์— ์ง์ ‘ ๋„ฃ์–ด์ค„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.
ํ† ํฐ ์ •๋ณด๋ฅผ ๋ธŒ๋ผ์šฐ์ €์˜ storage์— ์ €์žฅํ•˜๊ฑฐ๋‚˜ ์ฟ ํ‚ค์— ์ €์žฅํ•ด์•ผ๋ฉ๋‹ˆ๋‹ค.
์ €๋Š” ํ† ํฐ์„ ์ฟ ํ‚ค์— ์ €์žฅํ•˜๊ธฐ๋กœ ํ–ˆ์Šต๋‹ˆ๋‹ค. ๊ทธ ์ด์œ ๋„ ์—ญ์‹œ ์œ„ ๋งํฌ์— ์ž์„ธํžˆ ์ ํ˜€์žˆ์Šต๋‹ˆ๋‹ค.

JwtAuthenticationFilter(์ปค์Šคํ…€ ํ•„ํ„ฐ) ๊ตฌํ˜„

JWT accessToken๋งŒ ์‚ฌ์šฉํ•˜๋Š” ํ•„ํ„ฐ์˜ ๊ตฌํ˜„์€ ์•„์ฃผ ๊ฐ„๋‹จํ•ฉ๋‹ˆ๋‹ค. ์•„๋ž˜๋Š” JWT ๊ฐ•์˜๋ฅผ ๋ณด๊ณ  ์‹ค์Šตํ•œ ์ฝ”๋“œ ์ž…๋‹ˆ๋‹ค. ๊ฐ•์˜์—์„œ๋Š” AccessToken๋งŒ ์‚ฌ์šฉํ•˜๊ณ  ๋กœ๊ทธ์•„์›ƒ, ํ† ํฐ์ด ๋งŒ๋ฃŒ๋์„ ๋•Œ ์‚ฌ์šฉ์ž๊ฐ€ ๋‹ค์‹œ ๋กœ๊ทธ์ธํ•ด์•ผ ํ•˜๋Š” ์ƒํ™ฉ์— ๋Œ€ํ•ด์„œ๋Š” ๊ตฌํ˜„ํ•˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.

AccessToken๋งŒ ์‚ฌ์šฉ

๋กœ์ง์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.
  1. ์ธ์ฆ์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ Request์˜ Header์—์„œ ํ† ํฐ์„ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.
  1. ํ† ํฐ์ด ์žˆ๋‹ค๋ฉด ํ† ํฐ์„ ๊ฒ€์ฆํ•˜๊ณ  ๋””์ฝ”๋”ฉํ•ฉ๋‹ˆ๋‹ค.
  • ํ† ํฐ์ด ์˜ฌ๋ฐ”๋ฅด๋‹ค๋ฉด JwtAuthenticationToken์„ ์ƒ์„ฑํ•ด SecurityContext์— ์ธ์ฆ ์ •๋ณด๋ฅผ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.
  • ํ† ํฐ์ด ์˜ฌ๋ฐ”๋ฅด์ง€ ์•Š๋‹ค๋ฉด ์˜ˆ์™ธ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ต๋‹ˆ๋‹ค.
@Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { if (SecurityContextHolder.getContext().getAuthentication() == null) { String token = getToken((HttpServletRequest)request); if (token != null) { try { Jwt.Claims claims = verify(token); log.debug("Jwt parse result: {}", claims); String username = claims.username; List<GrantedAuthority> authorities = getAuthorities(claims); if (isNotEmpty(username) && authorities.size() > 0) { JwtAuthenticationToken authenticationToken = new JwtAuthenticationToken(new JwtAuthentication(token,username) , null, authorities); authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails( (HttpServletRequest)request)); SecurityContextHolder.getContext().setAuthentication(authenticationToken); } } catch (JWTVerificationException e) { log.warn("Jwt processing failed: {}", e.getMessage()); } } } else { log.debug("SecurityContextHolder not populated with security token, as it already contained: {}", SecurityContextHolder.getContext().getAuthentication()); } chain.doFilter(request, response); }

RefreshToken ์ถ”๊ฐ€ ์‚ฌ์šฉ

RefreshToken์„ ์‚ฌ์šฉํ•  ๋•Œ์—๋Š” ์กฐ๊ธˆ ๋” ๋ณต์žกํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์ถ”๊ฐ€์ ์œผ๋กœ ์•ž์—์„œ ๊ณ ๋ ค์‚ฌํ•ญ์—์„œ ๋งํ–ˆ๋“ฏ์ด ์ฟ ํ‚ค๋ฅผ ์‚ฌ์šฉํ•  ์˜ˆ์ •์ž…๋‹ˆ๋‹ค.
์ œ๊ฐ€ ๋ณธ ๊ฐ•์˜์—์„œ๋Š” GenericFilterBean์„ ์ƒ์† ๋ฐ›์•„์„œ ์ปค์Šคํ…€ ํ•„ํ„ฐ๋ฅผ ๊ตฌํ˜„ํ–ˆ๋Š”๋ฐ ์ €๋Š” OncePerRequestFilter๋ฅผ ์ƒ์† ๋ฐ›์•˜์Šต๋‹ˆ๋‹ค.
์ด์œ ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.
  1. GenericFilterBean์€ ๋งค ์„œ๋ธ”๋ฆฟ๋งˆ๋‹ค doFilter๋ฅผ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค. ์‹ค์ œ๋กœ GenericFilterBean์„ ์ƒ์†๋ฐ›์•„์„œ ๊ตฌํ˜„ํ•˜๋ฉด ํ•œ๋ฒˆ Request๋ฅผ ๋ณด๋‚ผ ๋•Œ ์—ฌ๋Ÿฌ๋ฒˆ JwtAuthenticationFilter๋ฅผ ๊ฑฐ์นฉ๋‹ˆ๋‹ค. ๋งค ์„œ๋ธ”๋ฆฟ ๋งˆ๋‹ค ๊ฐ™์€ ํ•„ํ„ฐ๋ฅผ ๊ฑฐ์น  ํ•„์š”๋Š” ์—†์Šต๋‹ˆ๋‹ค. ์ธ์ฆ์€ ๋‹จ ํ•œ๋ฒˆ๋งŒ ์ˆ˜ํ–‰ํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.
  1. ShouldNotFilter ํ•จ์ˆ˜๋ฅผ ์ œ๊ณตํ•ด์ค๋‹ˆ๋‹ค. ํšŒ์› ๊ฐ€์ž…, ๋กœ๊ทธ์ธ๊ฐ™์€ ๊ฒฝ์šฐ์—๋Š” JWT Authentication Filter๋ฅผ ๊ฑฐ์น  ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. OncePerRequestFilter๋Š” ShouldNotFilter() ๋ผ๋Š” ํŠน์ • ์กฐ๊ฑด์— ๋”ฐ๋ผ Filter๋ฅผ ์Šคํ‚ตํ•˜๋Š” ์œ ์šฉํ•œ ๋ฉ”์„œ๋“œ๋ฅผ ์ œ๊ณต ํ•ฉ๋‹ˆ๋‹ค. ํ•„์š” ์‹œ์— JWT Filter๋งŒ ์Šคํ‚ตํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

JwtAuthenticationFilter.java

package com.kdt.instakyuram.security.jwt; import java.io.IOException; import java.util.Arrays; import java.util.List; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; import org.springframework.web.filter.OncePerRequestFilter; import com.auth0.jwt.exceptions.JWTDecodeException; import com.auth0.jwt.exceptions.JWTVerificationException; import com.auth0.jwt.exceptions.TokenExpiredException; import com.kdt.instakyuram.auth.dto.TokenResponse; import com.kdt.instakyuram.auth.service.TokenService; import com.kdt.instakyuram.exception.EntityNotFoundException; public class JwtAuthenticationFilter extends OncePerRequestFilter { private final Jwt jwt; private final TokenService tokenService; private final Logger log = LoggerFactory.getLogger(getClass()); public JwtAuthenticationFilter(Jwt jwt, TokenService tokenService) { this.jwt = jwt; this.tokenService = tokenService; } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { logRequest(request); try { authenticate(getAccessToken(request), request, response); } catch (JwtTokenNotFoundException e) { this.log.warn(e.getMessage()); } filterChain.doFilter(request, response); } private void logRequest(HttpServletRequest request) { log.info(String.format( "[%s] %s %s", request.getMethod(), request.getRequestURI().toLowerCase(), request.getQueryString() == null ? "" : request.getQueryString()) ); } private String getAccessToken(HttpServletRequest request) { if (request.getCookies() != null) { return Arrays.stream(request.getCookies()) .filter(cookie -> cookie.getName().equals(this.jwt.accessTokenProperties().header())) .findFirst() .map(Cookie::getValue) .orElseThrow(() -> new JwtAccessTokenNotFoundException("AccessToken is not found")); } else { throw new JwtAccessTokenNotFoundException("AccessToken is not found."); } } private void authenticate(String accessToken, HttpServletRequest request, HttpServletResponse response) { try { Jwt.Claims claims = verify(accessToken); JwtAuthenticationToken authentication = createAuthenticationToken(claims, request, accessToken); SecurityContextHolder.getContext().setAuthentication(authentication); this.log.info("set Authentication"); } catch (TokenExpiredException exception) { Cookie cookie = new Cookie(jwt.accessTokenProperties().header(), ""); cookie.setPath("/"); cookie.setMaxAge(0); cookie.setHttpOnly(true); response.addCookie(cookie); this.log.warn(exception.getMessage()); refreshAuthentication(accessToken, request, response); } catch (JWTVerificationException exception) { log.warn(exception.getMessage()); } } private JwtAuthenticationToken createAuthenticationToken(Jwt.Claims claims, HttpServletRequest request, String accessToken) { List<GrantedAuthority> authorities = this.jwt.getAuthorities(claims); if (claims.memberId != null && !authorities.isEmpty()) { JwtAuthentication authentication = new JwtAuthentication(accessToken, claims.memberId, claims.username); JwtAuthenticationToken authenticationToken = new JwtAuthenticationToken(authentication, null, authorities); authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); return authenticationToken; } else { throw new JWTDecodeException("Decode Error"); } } private void refreshAuthentication(String accessToken, HttpServletRequest request, HttpServletResponse response) { try { String refreshToken = getRefreshToken(request); if (isValidRefreshToken(refreshToken, accessToken)) { String reIssuedAccessToken = accessTokenReIssue(accessToken); Jwt.Claims reIssuedClaims = verify(reIssuedAccessToken); JwtAuthenticationToken authentication = createAuthenticationToken(reIssuedClaims, request, reIssuedAccessToken); SecurityContextHolder.getContext().setAuthentication(authentication); Cookie cookie = new Cookie(this.jwt.accessTokenProperties().header(), reIssuedAccessToken); cookie.setHttpOnly(true); cookie.setPath("/"); cookie.setMaxAge(this.jwt.accessTokenProperties().expirySeconds()); response.addCookie(cookie); } else { log.warn("refreshToken expired"); } } catch (JwtTokenNotFoundException | JWTVerificationException e) { this.log.warn(e.getMessage()); } } private String getRefreshToken(HttpServletRequest request) { if (request.getCookies() != null) { return Arrays.stream(request.getCookies()) .filter(cookie -> cookie.getName().equals(this.jwt.refreshTokenProperties().header())) .findFirst() .map(Cookie::getValue) .orElseThrow(() -> new JwtRefreshTokenNotFoundException("RefreshToken is not found.")); } else { throw new JwtRefreshTokenNotFoundException(); } } private boolean isValidRefreshToken(String refreshToken, String accessToken) { try { TokenResponse foundRefreshToken = this.tokenService.findByToken(refreshToken); Long memberId = this.jwt.decode(accessToken).memberId; if (memberId.equals(foundRefreshToken.memberId())) { this.jwt.verify(foundRefreshToken.token()); return true; } } catch (EntityNotFoundException | JWTVerificationException e) { log.warn(e.getMessage()); return false; } return false; } private String accessTokenReIssue(String accessToken) { return jwt.generateAccessToken(this.jwt.decode(accessToken)); } private Jwt.Claims verify(String token) { return jwt.verify(token); } }
๋กœ์ง์˜ ํ๋ฆ„์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.
  1. getAccessToken()
  • ์•ก์„ธ์Šค ํ† ํฐ์„ ์ฟ ํ‚ค๋กœ๋ถ€ํ„ฐ ๊ฐ€์ ธ ์˜ต๋‹ˆ๋‹ค. ํ† ํฐ์ด ์—†๋‹ค๋ฉด ์˜ˆ์™ธ๋ฅผ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.
  1. authenticate()
  • accessToken์ด ์žˆ๋‹ค๋ฉด ํ† ํฐ์„ ๊ฒ€์ฆ๊ณผ ๋™์‹œ์— ๋””์ฝ”๋”ฉ ํ•ฉ๋‹ˆ๋‹ค. (์œ„๋ณ€์กฐ ๋˜์ง„ ์•Š์•˜๋Š”์ง€, ํ† ํฐ์ด ๋งŒ๋ฃŒ๋˜์ง„ ์•Š์•˜๋Š”์ง€ ๋“ฑ)
  • ํ† ํฐ์ด ๋งŒ๋ฃŒ๋˜๋ฉด ๋ฆฌํ”„๋ ˆ์‰ฌ ํ† ํฐ์„ ํ†ตํ•ด AccessToken์„ ์žฌ๋ฐœ๊ธ‰ ๋ฐ›์Šต๋‹ˆ๋‹ค.
  • ๊ทธ ์™ธ ํ† ํฐ ๊ฒ€์ฆ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ์˜ˆ์™ธ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ค๊ณ  ์ข…๋ฃŒํ•ฉ๋‹ˆ๋‹ค.
  • ๊ฒ€์ฆ์— ํ†ต๊ณผ ํ•œ๋‹ค๋ฉด ๋””์ฝ”๋”ฉ๋œ ๋ฐ์ดํ„ฐ๋ฅผ ํ† ๋Œ€๋กœ authenticationToken์„ ๋งŒ๋“ค๊ณ  SecurityContextHolder์— ์ธ์ฆ ์ •๋ณด๋ฅผ ์„ธํŒ… ํ•ฉ๋‹ˆ๋‹ค. (createAuthenticationToken(), setAuthentication())
2-a. AccessToken์ด ๋งŒ๋ฃŒ๋˜์—ˆ์„ ๊ฒฝ์šฐ (refreshAuthentication())
  • ์ฟ ํ‚ค๋กœ๋ถ€ํ„ฐ RefreshToken์„ ์ฐพ์Šต๋‹ˆ๋‹ค. ์—†๋‹ค๋ฉด ์˜ˆ์™ธ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ค๊ณ  ์ข…๋ฃŒํ•ฉ๋‹ˆ๋‹ค.
  • ํ† ํฐ์ด ์žˆ๋‹ค๋ฉด ๊ฒ€์ฆ์„ ํ•ฉ๋‹ˆ๋‹ค. (isValidRefreshToken()) DB์— ์ €์žฅ๋œ RefreshToken์˜ userID์™€ AccessToken๋‚ด์˜ userID๊ฐ€ ๊ฐ™์€์ง€ ํ™•์ธ์„ ํ•ฉ๋‹ˆ๋‹ค.
  • ํ† ํฐ์ด ์žˆ๋‹ค๋ฉด ๋งŒ๋ฃŒ๋œ accessToken์„ ๋””์ฝ”๋”ฉํ•ด ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ค๊ณ  ์ƒˆ accessToken์„ ๋งŒ๋“œ๋Š” ๋ฐ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. (accessTokenReIssue())
  • ์žฌ๋ฐœ๊ธ‰๋œ ํ† ํฐ์„ ๋‹ค์‹œ SecurityContextHolder์— ์ธ์ฆ ์ •๋ณด๋ฅผ ์„ธํŒ…ํ•ด์ค๋‹ˆ๋‹ค.

๋กœ๊ทธ์•„์›ƒ ๊ธฐ๋Šฅ์— ๋Œ€ํ•œ ์ƒ๊ฐ

JWT๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ ๋กœ๊ทธ์•„์›ƒ ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•˜๋Š” ๋Œ€ํ‘œ์ ์ธ ๋ฐฉ๋ฒ•์ด ๋กœ๊ทธ์•„์›ƒํ•œ AccessToken์„ DB์— ์ €์žฅํ•ด ํ•ด๋‹น AccessToken์œผ๋กœ ์š”์ฒญ์ด ๋“ค์–ด์˜ค๋ฉด DB ํ™•์ธ์„ ํ†ตํ•ด์„œ ์ธ์ฆ์„ ๊ฑฐ๋ถ€ํ•˜๋Š” ๋ฐฉ์‹์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
JWT ์žฅ์  ์ค‘์— ํ•˜๋‚˜๋Š” ๋งค๋ฒˆ Session Storage๋ฅผ ์กฐํšŒํ•˜๋Š” ์„ธ์…˜ ๋ฐฉ์‹๊ณผ๋Š” ๋‹ค๋ฅด๊ฒŒ ํ† ํฐ์— ๋Œ€ํ•œ ์ •๋ณด๋ฅผ ํ†ตํ•ด ์ธ์ฆ์„ ํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ์ ์ด์—ˆ๊ณ  RefreshToken์„ ์‚ฌ์šฉํ•˜๋ฉด์„œ DB๋ฅผ ์‚ฌ์šฉํ•˜๋”๋ผ๋„ ํ† ํฐ์ด ๋งŒ๋ฃŒ๋˜์—ˆ์„ ๋•Œ๋งŒ ์‚ฌ์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์„ธ์…˜ ๋ฐฉ์‹๋ณด๋‹ค ํฐ ์ด์ ์„ ๊ฐ€์ ธ๊ฐˆ ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.
ํ•˜์ง€๋งŒ ๋ธ”๋ž™๋ฆฌ์ŠคํŠธ-๋กœ๊ทธ์•„์›ƒ ๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ํ•œ๋‹ค๋ฉด AccessToken์ด ๋ธ”๋ž™๋ฆฌ์ŠคํŠธ์— ๋“ฑ๋ก์ด ๋˜์—ˆ๋Š”์ง€ ํ™•์ธํ•˜๊ธฐ์œ„ํ•ด ๋งค๋ฒˆ DB๋ฅผ ์กฐํšŒํ•˜๋ฏ€๋กœ JWT๊ฐ€ ๊ฐ€์ง„ ์ด๋Ÿฌํ•œ ์žฅ์ ์ด ํ‡ด์ƒ‰๋  ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒฐ๋ก ์ด ๋‚ฌ์Šต๋‹ˆ๋‹ค.
์ฟ ํ‚ค์—์„œ ํ† ํฐ์„ ์‚ญ์ œํ•˜๋”๋ผ๋„ ํ† ํฐ ์ž์ฒด๋Š” ์—ฌ์ „ํžˆ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์ƒํƒœ์ด๊ธฐ ๋•Œ๋ฌธ์— ์ค‘๊ฐ„์— ํƒˆ์ทจ๋œ๋‹ค๋ฉด ์•„๋ฌด๋Ÿฐ ์กฐ์ทจ๋ฅผ ํ•  ์ˆ˜ ์—†๋‹ค๋Š”๊ฒƒ์ด ๊ฑฑ์ •๋˜์—ˆ์ง€๋งŒ ํ† ํฐ์˜ ์œ ํšจ์‹œ๊ฐ„์„ ์งง๊ฒŒ ์„ค์ •ํ•˜๊ณ  ์–ด๋А์ •๋„์˜ Trade-off๋ฅผ ๊ฐ์ˆ˜ํ•˜๋ฉด์„œ JWT์˜ ์žฅ์ ์„ ์ตœ๋Œ€ํ•œ ์‚ด๋ฆฌ๋Š”๊ฒŒ ์ข‹๋‹ค๊ณ  ํŒ๋‹จ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.