Interceptor에서 preHandle로 request를 잡아서 refresh token을 재 생성하여 response의 cookie를 변경할경우 cookie write 완료 전에 들어온 request들은 새로 변한 값이 아닌 이전 값으로 올때가 있다. 즉 http request가 동시에 여러개가 올 경우에는 reponse 변경값이 적용되기 전에 요청이 올수가 있다.
이럴 경우에는 refresh token 변조 확인을 위해 DB의 token 값을 읽을 때 생성된지 얼마 안된 토큰은 skip하고 일정 threshold (예: 2초) 지난후 token부터 진위여부를 체크한다.
public ApiResponse refreshToken(HttpServletRequest request, HttpServletResponse response) throws IOException {
// access token 확인
String accessToken = HeaderUtil.getAccessTokenCookie(request);
String refreshToken = CookieUtil.getCookie(request, REFRESH_TOKEN)
.map(Cookie::getValue)
.orElse((null));
if (accessToken == null || refreshToken == null) {
return new ApiResponse(new ApiResponseHeader(ApiResponse.FAILED, ApiResponse.EMPTY_TOKEN), null);
}
if (tokenProvider.validateToken(accessToken) == null) {
return ApiResponse.invalidAccessToken();
}
String userId = tokenProvider.getUserNameFromToken(accessToken);
// refresh token
if (tokenProvider.validateToken(refreshToken) == null) {
return ApiResponse.invalidRefreshToken();
}
Date now = new Date();
long tokenExpirationMsec = appProperties.getAuth().getTokenExpirationMsec();
String newAccessToken = tokenProvider.createToken(userId, tokenExpirationMsec);
int cookieMaxAge = (int) tokenExpirationMsec / 60;
CookieUtil.deleteCookie(request, response, ACCESS_TOKEN);
CookieUtil.addCookie(response, ACCESS_TOKEN, newAccessToken, cookieMaxAge);
long validTime = tokenProvider.validateToken(refreshToken).getExpiration().getTime() - now.getTime();
// refresh 토큰 기간이 3일 이하로 남은 경우, refresh 토큰 갱신
if (validTime <= THREE_DAYS_MSEC) {
// userId refresh token 으로 DB 확인
SecurityUserVO oneUserInfo = securityUserMapper.getOneUserInfo(userId, null);
long tokenCreationAging = Math.abs(now.getTime() - (tokenProvider.validateToken(oneUserInfo.getRefreshToken()).getExpiration().getTime() - appProperties.getAuth().getRefreshTokenExpirationMsec()));
// DB의 userInfo에 저장된 최신 token을 읽어서 2초 (2000msec) 이후 지난 경우에 진위 체크를 진행한다.
////////////////////////////////////////////////////////////////////////////////////////
if ((tokenCreationAging > 2000) && !refreshToken.equals(oneUserInfo.getRefreshToken())) {
// 클라이언트에서 넘어온 refreshToken이 DB에 저장되어 있지 않다면 위조로 판단하고 로그인으로 Redirect 시킨다.
return ApiResponse.invalidRefreshToken();
}
////////////////////////////////////////////////////////////////////////////////////////
// refresh 토큰 설정
long refreshTokenExpirationMsec = appProperties.getAuth().getRefreshTokenExpirationMsec();
String newRefreshToken = tokenProvider.createToken(userId, refreshTokenExpirationMsec);
// DB에 refresh 토큰 업데이트
Map<String, Object> paramMap = new HashMap<>();
paramMap.put("userId", userId);
paramMap.put("refreshToken", newRefreshToken);
securityUserMapper.updateRefreshToken(paramMap);
cookieMaxAge = (int) refreshTokenExpirationMsec / 60;
CookieUtil.deleteCookie(request, response, REFRESH_TOKEN);
CookieUtil.addCookie(response, REFRESH_TOKEN, newRefreshToken, cookieMaxAge);
}
return ApiResponse.success("access_token", newAccessToken);
}
'Backend Development > Spring boot' 카테고리의 다른 글
[Spring boot] Spring Security 분석 - Start 시퀀스 (0) | 2022.03.13 |
---|---|
[Spring boot] Spring Security 분석 - 메소드 Security (0) | 2022.03.11 |
[Spring boot] @ConfigurationProperties 사용하기 (0) | 2022.03.08 |
[Spring boot] JSP embedded tomcat (tomcat-embedded-jasper) 동작 원리 (0) | 2022.03.08 |
[Spring boot] Spring Security Authentication 개념 (0) | 2022.03.04 |