- Today
- Total
- νλ‘κ·Έλλ¨Έμ€
- PS
- μλ°
- Algorithm
- OOP
- μλ£κ΅¬μ‘°
- dp
- CS
- Graph
- MST
- λ°±μλ
- ꡬν
- μμμ λ ¬
- μλ°μμ μ
- leetcode
- spring
- μΈν΄
- BFS
- database
- 벨λ§ν¬λ
- 그리λ
- tree
- λ°±μ€
- array
- pytorch
- μ‘Έμ μν
- λ€μ΅μ€νΈλΌ
- λ¬Έλ²
- λ°μ΄ν°λ² μ΄μ€
- java
Partially Committed
μ²μ ꡬνν΄λ³΄λ access, refresh token λ°κΈ λ‘μ§(Spring) λ³Έλ¬Έ
μ²μ ꡬνν΄λ³΄λ access, refresh token λ°κΈ λ‘μ§(Spring)
WonderJay 2023. 8. 17. 22:20
|| Couphone, μΏ ν°μ ν° μμ!
λ°±μλ κ°λ°μκ° λκΈ°λ‘ λ§μλ¨Ήμ λ€λ‘
μ²μμΌλ‘ μ°Έμ¬ν νλ‘μ νΈμ΄λ€.
λλ Spring κ²½νμ΄ μμ£Ό μ μκ³
λ³Έ νλ‘μ νΈ κΈ°κ°μ΄ 1κ°μ λ° μ λλ‘ μ§§μκΈ° λλ¬Έμ
μ 체μ μΈ μμ±λκ° λ§μ΄ λ¨μ΄μ§λ κ² κ°μ λ§μ΄ μμ¬μ λ€.
μ²μμΌλ‘ νλ‘μ νΈμ μ°Έμ¬νλ€ λ³΄λ λͺ¨λ λΆλΆμμ μλΉν λ§λ§ν κ°μ μ λλ μ λ°μ μμλλ°,
μ΄μ΄ μ’κ²λ μ΄κ²μ κ² μ§μ ꡬνν΄λ³Ό μ μκ² λμ΄μ
κ°μΈμ μΌλ‘λ μ»μ΄κ°λ μ μ΄ κ½€ λ§μλ€κ³ μκ°νλ€.
μ¬νΌ,
μ²μμΌλ‘ νλ‘μ νΈμ μ°Έμ¬νλ©° λκΌλ λ§λ§ν¨κ³Ό ν΄κ²° κ³Όμ μ λμκΈ°λ©°
" μ²μμΌλ‘ ꡬνν΄λ³΄λ " μ΄λΌλ μ리μ¦λ‘ λΈλ‘κ·Έμ μ½μ§ν κΈ°λ‘μ λ¨κ²¨λ³Ό μκ°μ΄λ€.
첫 λ²μ§Έ μ£Όμ λ μ²μμΌλ‘ ꡬνν΄λ³΄λ access token, refresh token λ°κΈ λ‘μ§μ΄λ€.
( λλ΅ 7 κ° μ λμ μ£Όμ λ₯Ό κ³ννκ³ μλ€. )
1. μΈμ κΈ°λ° μΈμ¦ λ°©μμ΄λ?
μ΄μ μ μ€ν°λμμ μΈμ¦/μΈκ°μ λν΄μ 곡λΆν΄λ³΄μλ€.
μΈμ¦ λ°©μμλ ν¬κ² 2 κ°μ§κ° μ‘΄μ¬νλ€.
λ°±μλμμλ μΈμ κΈ°λ°μ μΈμ¦ μμ€ν μ ꡬμΆν μλ μκ³ , ν ν° κΈ°λ°μ μΈμ¦ μμ€ν μ ꡬμΆν μλ μκ² λ€.
μΈμ κΈ°λ°μ μΈμ¦ λ°©μμ μ¬μ©μμ μ 보λ₯Ό μλ²μ μΈμ μ μ₯μμ μ μ₯νλ κ²μ΄λ€.
μ¬μ©μκ° λ‘κ·ΈμΈμ νλ©΄, κ΄λ ¨ μΈμ¦ μ 보λ₯Ό μΈμ μ μ₯μμ μ μ₯νκ³
μ¬μ©μμκ² Session ID λ₯Ό λ°κΈνλ€.
Session ID λ λΈλΌμ°μ μ μΏ ν€ ννλ‘ μ μ₯λμ΄ κ΄λ¦¬λλ€.
λΈλΌμ°μ μμλ μΈμ¦ μ μ°¨λ₯Ό λ§μΉ 맀 μμ²λ§λ€ HTTP cookie ν€λμ Session ID λ₯Ό ν¬ν¨νμ¬ μλ²μ μ μ‘νλ€.
μλ²λ μ λ¬λ°μ Session ID μ ν΄λΉνλ μΈμ μ λ³΄κ° μΈμ μ μ₯μμ μ‘΄μ¬νλ€λ©΄ ν΄λΉ μ¬μ©μκ° μΈμ¦λμλ€κ³ νλ¨νλ€.
μ΄λ¬ν μΈμ κΈ°λ°μ μμ€ν μ ν΄λΌμ΄μΈνΈλ‘λΆν°μ μ 보λ₯Ό κΈ°μ΅νκΈ° μν΄ μΈμ μ κ³μ μ μ§ν΄μΌ νλ€. (Stateful νλ€.)
μ΄λ¬ν νΉμ§μ μλ²λ₯Ό νμ₯νλ λ°μ μμ΄ λΆλ¦¬νλ€.
λν μλ²μμ ν΄λΌμ΄μΈνΈ μΈμ¦ μ 보λ₯Ό λͺ¨λ μ μ₯νκ³ μμΌλ €λ©΄ ν΄λΌμ΄μΈνΈ μκ° λμ΄λ¨μ λ°λΌ
μ 보λ₯Ό μ΄λμ μ΄λ»κ² μ μ₯ν΄μΌ ν μ§μ λν μΆκ°μ μΈ λ Όμκ° λ°μνκ² λ κ²μ΄λ€.
μΆκ°μ μΌλ‘ μ€κ°μ μ 3 μμ μν΄ session id κ° νμ·¨λλ©΄,
μΈμ¦λ ν΄λΌμ΄μΈνΈκ° μλμλ κ·Έλ° κ² μ²λΌ μλ²μ μ κ·Όν μ μλ€λ κ² λν λ¬Έμ μ μ΄ λλ€.
μ΄λ¬ν λ¨μ μ 극볡νκΈ° μν΄ ν ν° κΈ°λ°μ μΈμ¦ λ°©μμ΄ λ리 μ¬μ©λκ³ μλ€.
2. ν ν°κΈ°λ° μΈμ¦
ν ν° κΈ°λ° μΈμ¦ λ°©μμ μΈμ¦ μ 보λ₯Ό ν΄λΌμ΄μΈνΈ ν ν°μ ννλ‘ κ°μ§κ³ μλλ€.
λνμ μΌλ‘ μ¬μ©λλ ν ν°μ JWT μΈλ°,
μ¬μ©μκ° κ°μ§κ³ μλ ν ν°μ HTTP μ Authorization ν€λμ ν¬ν¨μμΌ λ³΄λ΄λ©΄
μλ² μΈ‘μμλ ν΄λΉ ν ν°μ΄ μ ν¨νμ§, κ·Έλ¦¬κ³ λ§λ£λμ§ μμλμ§λ₯Ό νμΈν λ€
ν ν°μ μνΈνλμ΄ λ΄κ²¨μ Έ μλ μ¬μ©μ μΈμ¦ μ 보λ₯Ό νμΈνμ¬ μΈμ¦/μΈκ°λ₯Ό μννλ€.
JWT μ ννλ μλμ κ°μΌλ©° νμμ λ°λΌ 컀μ€ν°λ§μ΄μ§ ν μ μλ€.
μΈμ κΈ°λ° λ°©μμμλ session id λ§ λκΈ°λ©΄ λμλ€λ©΄
ν ν° κΈ°λ° λ°©μμμλ μμ κ°μ΄ κΈ΄ λ¬Έμμ΄μ λκ²¨μΌ νλ―λ‘
λ λ§μ λ€νΈμν¬ νΈλν½μ μ¬μ©νλ€.
ν ν°μ΄ νμ·¨λλ©΄ μ¬μ©μ μ λ³΄κ° λ ΈμΆλ μ°λ € λν μ‘΄μ¬νλ€. (μ΄λ refresh token μ λμ νλ©° κ°μ κ°λ₯)
κ·ΈλΌμλ ν ν° κΈ°λ° μΈμ¦ λ°©μμ΄ νμ¬ λμΈ μΈ μ΄μ λ λ°λ‘ νμ₯μ± λλ¬Έμ΄λΌκ³ ν μ μλ€.
λμ©λ λ°μ΄ν°λ₯Ό μ²λ¦¬νκΈ° μν΄μ λ¨μΌ μλ²μ μ±λ₯μ λμ΄λ λ°μλ νκ³κ° μκΈ° λλ¬Έμ μλ²μ κ°μλ₯Ό λ리λ λ°©ν₯(Scale out)μΌλ‘ νλνλλ°,
ν ν° κΈ°λ° μΈμ¦ λ°©μμ μΈμ κΈ°λ°μ μΈμ¦ λ°©μκ³Ό λ¬λ¦¬ μΈμ¦ μ λ³΄κ° ν ν°μ μ μ₯λλ―λ‘ Scale out μ μ 리νλ€.
μΈμ κΈ°λ°μ μΈμ¦ λ°©μμ μλ² λ΄λΆμ μΈμ μ μ₯μμ μΈμ¦ μ 보λ₯Ό μ μ₯νμλλ°, Scale out λ¨μ λ°λΌ μλ²κ° λμ΄λλ©΄ μΈμ μ 보λ₯Ό μ΄λ»κ² κ΄λ¦¬ν μ§μ λν ν° λ²κ±°λ‘μμ΄ μ‘΄μ¬νλ€.
3. JWT
Header
- JWT ν€λμλ ν ν°μ νμ κ³Ό ν΄μ± μκ³ λ¦¬μ¦ λ°©μμ΄ μ μ₯λλ€.
PayLoad
- PayLoad μμλ ν ν°μμ μ¬μ©ν μ 보 μ‘°κ°μΈ Claim μ΄ λ΄κ²¨μ Έμλ€.
- κΈ°λ³Έμ μΌλ‘ sub(ν ν° μ λͺ©), exp(λ§λ£ μκ°), iat(λ°κΈ μκ°) λ±μ΄ λ΄κ²¨μ Έ μλ€.
- νμνλ€λ©΄ μΆκ°μ μΈ μ 보λ₯Ό Claim μ λ£μ΄λ μ μμΌλ, ν ν°μ κΈΈμ΄κ° κΈΈμ΄μ§μ λ°λΌ λ€νΈμν¬ λΆνκ° μ¦κ°νκΈ° λ§λ ¨μ΄λ―λ‘ λΆνμν μ 보λ₯Ό μ΅λν μ§μνλ κ²μ΄ μ’λ€.
Signature
- Signature μλ ν ν° μΈμ½λ©/μ ν¨μ± κ²μ¦ μ μ¬μ©νλ κ³ μ ν μνΈν μ½λκ° λ΄κ²¨μ Έ μμΌλ©°,
- μμλλ©΄ μ ν¨νμ§ μμ ν ν°μΌλ‘ νλ¨νλ€.
4. Access Token, Refresh Token
ν ν° μμ νμ·¨λλ©΄ μΈμ¦/μΈκ°λμ§ μμ μ 3μλ‘λΆν° μλ² λ¦¬μμ€κ° λ ΈμΆλ μ μλ μνμ±μ΄ μ‘΄μ¬νλ€.
μ΄λ₯Ό μ΅λν μλ°©νκΈ° μν΄ ν ν°μ μ’ λ₯λ₯Ό 2 κ°μ§λ‘ ꡬλΆνμ¬ μμ±νλ λ°©λ²μ΄ μλ€.
Access token
- μ ν¨κΈ°κ°μ΄ λ§€μ° μ§§μ ν ν°(30λΆ μ΄ν)μΌλ‘ λ‘κ·ΈμΈ μ μλ²μμ ν΄λΌμ΄μΈνΈμΌλ‘ λ°ννλ€.
Refresh token
- μ ν¨κΈ°κ°μ΄ κΈ΄ ν ν°(7μΌ μ λ)μΌλ‘ ν΄λΌμ΄μΈνΈλ‘ λ°ννμ§ μκ³ , μλ² λ΄λΆ DB μμ κ΄λ¦¬νλ€.
ν ν° νμ·¨μ μνμ μλ² - ν΄λΌμ΄μΈνΈ κ° ν΅μ κ³Όμ μμ μ‘΄μ¬νλλ°,
ν΄λΌμ΄μΈνΈλ‘ μλ΅νλ Access token μ λ§λ£κΈ°κ°μ λ§€μ° μ§§κ² μ€μ ν¨μΌλ‘μ¨
νμ·¨λλλΌλ μνμ±μ΄ μ λλ‘ νλ λ°©λ²μ΄λ€.
λ³Έ νλ‘μ νΈμμλ Access token , Refresh token λ°©μμΌλ‘ μΈμ¦/μΈκ° μμ€ν μ ꡬμΆνμλ€.
μΈμ¦ κ΄λ ¨νμ¬ μ 체μ μΈ Flow λ μλ μν€ν μ³μμ μ²λΌ μλΉν 볡μ‘νλ€.
μ¬μ€ μ΄λ² νλ‘μ νΈμμλ μ 체μ μΈ λ΄μ© μ 체λ₯Ό μ΄ν΄νμ§λ λͺ»νκ³ κ΅¬ννκΈ΄ νμλ€..
μλ ꡬ쑰λ μ‘°λ§κ° μ€νλ§ μν리ν°λ₯Ό 곡λΆνλ©° μλ‘μ΄ ν¬μ€ν μμ μ 리ν΄λ³΄κ³ μ νλ€..!
λ³Έ νλ‘μ νΈμμ ꡬνν κ°λ¨ν λ΄μ©λ§ μκ°ν΄λ³΄λλ‘ νκ² λ€.
λ¨Όμ , μ¬μ©μ Login μμ²μ΄ λ€μ΄μ€λ©΄
μ κ· νμμ΄λ©΄ νμ κ°μ μ²λ¦¬νκ³ , κΈ°μ‘΄ νμμ΄λ©΄ λ‘κ·ΈμΈμ μννλ€.
ν΄λΉ API λ Token κ²μ¦μ νμ§ μλλ€.
signin μ΄ νΈμΆλλ©΄ UsernamePasswordAuthenticationToken μ λ°κΈλ°λλ€.
( λ³΄ν΅ email κ³Ό password λ₯Ό μ¬μ©νμ§λ§, λ³Έ νλ‘μ νΈμμ password λ₯Ό μ¬μ©μλ‘λΆν° μ λ ₯λ°μ§ μμμ λ°λΌ email κ³Ό name μ μ¬μ©νκ² λμλ€. )
κ·Έλ¦¬κ³ AuthenticationToken μ AuthenticationManager μκ² μ λ¬νμ¬ μΈμ¦ μ 보λ₯Ό λ§λ λ€.
곧μ΄μ΄ SecurityContextHolder μ μ¬μ©μμ μΈμ¦ μ 보λ₯Ό μ μ₯νλ€.
μ΄ν JwtProvider μ μ¬μ μ ꡬνν΄λ generateAccessToken, generateRefreshToken λ©μλμ μΈμ¦ μ 보λ₯Ό λ겨주μ΄
accessToken κ³Ό refreshToken μ μμ±νλ€.
refresh token μ DB μ μ μ₯νκ³ ,
accessToken μ μ¬μ©μμκ² λ°ννλ€.
μ€μν κ²μ μλ μ½λμ΄λ€.
λ‘κ·ΈμΈμ μ μΈν λͺ¨λ Http μμ²μ TokenAuthenticationFilter μ μ λ¬λλλ‘ κ΅¬ννμλ€.
ν΄λΉ νν°μμλ access token / refresh token κ΄λ ¨ λ‘μ§μ μννκ³
νμ μ μ¬λ°κΈ λ±μ μννλ€.
@Slf4j
@RequiredArgsConstructor
public class TokenAuthenticationFilter extends OncePerRequestFilter {
private final JwtTokenProvider jwtProvider;
private final RefreshTokenService tokenService;
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 {
if (permitAllUrl.of(request.getRequestURI())) {
filterChain.doFilter(request, response);
return;
}
String accessToken = resolveToken(request, HEADER_AUTHORIZATION); // 1. μ¬μ©μκ° λ³΄λΈ ν ν°μ νμΈ
if (StringUtils.hasText(accessToken) && jwtProvider.validateToke(accessToken) == JwtCode.ACCESS) {
// Access token μ΄ μ ν¨νλ©΄
Authentication authentication = jwtProvider.getAuthentication(accessToken);
SecurityContextHolder.getContext().setAuthentication(authentication); // security context μ μΈμ¦ μ 보 μ μ₯
log.info("Access Token μ μμ§ μ ν¨ν©λλ€.");
} else if (StringUtils.hasText(accessToken) && jwtProvider.validateToke(accessToken) == JwtCode.EXPIRED) {
// 2-1. Access token μ΄ λ§λ£λμλ€λ©΄
log.info("Authorization νλμ λ΄κ²¨μ§ Access Token μ΄ Expired λμμ΅λλ€!");
String refreshToken = null;
// 2-2. jwt token μΌλ‘λΆν° userId λ₯Ό μ°Ύκ³ , ν΄λΉ userId μ λν refreshToken μ νμ
Long userId = jwtProvider.getUserIdFromExpiredToken(accessToken);
log.warn(userId.toString());
refreshToken = jwtProvider.getRefreshToken(userId);
// refresh token μ΄ μ‘΄μ¬νκ³ μ ν¨νλ€λ©΄
if (StringUtils.hasText(refreshToken) && jwtProvider.validateToke(refreshToken) == JwtCode.ACCESS) {
// access token μ¬λ°κΈ
log.info("ν΄λΉ νμ ID μ λν Refresh token μ΄ μ‘΄μ¬νκ³ μ ν¨ν©λλ€.");
log.info("Access token μ μ¬λ°κΈνμ¬ λ°νν©λλ€.");
Authentication authentication = jwtProvider.getAuthentication(refreshToken);
String newAccessToken = jwtProvider.generateAccessToken(authentication);
SecurityContextHolder.getContext().setAuthentication(authentication);
response.setHeader(HttpHeaders.AUTHORIZATION, newAccessToken);
} else if (StringUtils.hasText(refreshToken) && jwtProvider.validateToke(refreshToken) == JwtCode.EXPIRED) {
// refresh token μ΄ μ‘΄μ¬νμ§λ§ λ§λ£λμλ€λ©΄
log.warn("ν΄λΉ νμ ID μ λν Refresh token μ΄ μ‘΄μ¬νμ§λ§, λ§λ£λμμ΅λλ€.");
throw new JwtExpiredTokenException(EXPIRED_TOKEN);
}
if (refreshToken == null) {
// refresh token μ΄ μ‘΄μ¬νμ§ μμΌλ©΄
log.warn("ν΄λΉ νμ ID μ λν Refresh token μ΄ μ‘΄μ¬νμ§ μμ΅λλ€.");
throw new JwtNoTokenException(TOKEN_NOT_FOUND);
}
}
filterChain.doFilter(request, response);
}
public String resolveToken(HttpServletRequest request, String header) {
String bearerToken = request.getHeader(header);
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith(TOKEN_PREFIX)) {
return bearerToken.substring(TOKEN_PREFIX.length());
}
return null;
}
}
https://github.com/KUIT-Couphone/Couphone-Server/pull/18
token κ²μ¦μ΄ νμνμ§ μλ path (e.g. /auth/login ) μ λν΄μλ token μ ν¨μ±μ κ²μ¬νμ§ μκ³ λμ΄κ°λ€.
access token μ΄ μ ν¨νλ©΄ (μ ν¨νμ§ μμΌλ©΄ InvalidTokenException)
μΈμ¦ μ 보λ₯Ό μ μ₯νλ€. (λ)
access token μ΄ μ ν¨νμ§λ§ λ§λ£λμλ€λ©΄
- 3.1 jwtμμ userId λ₯Ό κΊΌλΈλ€.
- 3.2 ν΄λΉ userId μ λν refresh token μ΄ μ ν¨νλ©΄ access token μ μ¬λ°κΈνκ³ Authorization header μ λ΄μμ λ°ννλ€.
- 3.3 ν΄λΉ userId μ λν refresh token μ΄ μ‘΄μ¬νμ§ μμΌλ©΄ JwtNoTokenException
- 3.4 ν΄λΉ userId μ λν refresh token μ΄ μ‘΄μ¬νμ§λ§ λ§λ£λμλ€λ©΄ JwtExpiredTokenException
References
1. https://do5do.tistory.com/14
2. https://goldenrabbit.co.kr/product/springboot3java/
3. https://product.kyobobook.co.kr/detail/S000001019679
4. https://mangkyu.tistory.com/57