RefreshToken 재발급 프로세스

2021. 7. 2. 22:52Spring Security/Spring Boot Jwt with JPA and Redis

반응형

RefreshToken의 역할은 AccessToken이 만료되었을 때 AccessToken 재발급 역할을 하게 된다.

RefreshToken의 키 정보는 쿠키, h2db, Redis Server에 저장이 된다.

 

아래 그림은 RefreshToken 재발급 프로세스 이다.

RefreshToken 재발급 프로세스

JwtFilter

// RefreshToken 재발급인 경우
if (isValidAccessToken && !isValidRefreshToken) {
	// RefreshToken 재발급
	refreshToken = reIssueRefreshToken(accessToken);

	// RefreshToken 쿠키 생성
	CookieUtil refreshTokenCookieUtil = new CookieUtil();
	httpServletResponse.addCookie(cookieUtil.createCookie(REFRESH_TOKEN_COOKIE_NAME, AES256Cipher.encrypt(refreshToken), "R"));
}

/**
 * RefreshToken 재발급
 *
 * @param accessToken 유효한 accessToken
 */
private String reIssueRefreshToken(String accessToken) {
	String refreshToken = null;

	HttpHeaders headers = new HttpHeaders();
	headers.setContentType(MediaType.APPLICATION_JSON);

	HttpEntity<String> request = new HttpEntity<>(accessToken, headers);
	ResponseEntity<String> responseEntity = restTemplate.postForEntity( "http://localhost:10001/token/re-issue/refresh-token", request , String.class);

	refreshToken = responseEntity.getBody();

	return refreshToken;
}

RefresToken의 재발급 조건은 AccessToken이 유효하고 RefreshToken이 만료된 경우이다.

위의 코드를 보면 RefreshToken이 재발급된 후 쿠키를 재생성한다.

 

TokenController.reIssueRefreshToken

/**
 * RefreshToken 재발급
 * 
 * @param accessToken 발급요청한 accessToken
 * @return ResponseEntity<String> 재발급된 refreshToken
 */
@PostMapping("/re-issue/refresh-token")
public ResponseEntity<String> reIssueRefreshToken(@RequestBody String accessToken) throws Exception {
	return new ResponseEntity<>(authService.generateRefreshTokenFromAccessToken(accessToken), HttpStatus.OK);
}

api-server에서 전달받은 accessToken 값을 AuthService.generateRefreshTokenFromAccessToken 메서드를 호출한다.

 

AuthService.generateRefreshTokenFromAccessToken

@Override
public String generateRefreshTokenFromAccessToken(String accessToken) throws Exception {
	// 1. AccessToken 유효성 체크
	boolean isAccessToken = validateAccessToken(accessToken);
	logger.debug("AccessToken({}) 유효성 여부 ({})", accessToken, isAccessToken);

	// 2. accessToken 이 유효하고 refreshToken 이 만료된 경우 refreshToken 을 재발급한다.
	RefreshToken refreshTokenResponse = null;

	if (isAccessToken) {
		// 토큰 사용자 정보 조회
		Authentication authentication = tokenProvider.getAuthentication(accessToken);

		// Expired RefreshToken ID 조회
		// Cookie 정보가 삭제 되기 때문에 쿠키에선느 토큰 정보를 알수 없기 때문에 사용자 정보에서 RefreshToken 정보를 조회 한다.
		String expiredRefreshTokenId = AES256Cipher.decrypt(userRepository.findOneWithAuthoritiesByUsername(authentication.getName()).get().getToken());
		logger.debug("Expired RefreshToken({})", expiredRefreshTokenId);
		
		// Expired RefreshToken 조회
		Optional<RefreshToken> refreshToken = refreshTokenRepository.findById(expiredRefreshTokenId);

		// refreshToken 발급
		Calendar c = Calendar.getInstance();

		// 30일후 설정
		// c.add(Calendar.DATE, 30);
		c.add(Calendar.MINUTE, 5);

		RefreshToken refreshTokenRequest = RefreshToken.builder()
				.id(UUID.randomUUID().toString())
				.username(authentication.getName())
				.password(refreshToken.get().getPassword())
				.expiryDate(c.getTime())
				.build();

		refreshTokenResponse = refreshTokenRepository.save(refreshTokenRequest);

		// 재발급 RefreshTokenId 업데이트
		Optional<User> user =  userRepository.findOneWithAuthoritiesByUsername(authentication.getName());
		user.get().setToken(AES256Cipher.encrypt(refreshTokenResponse.getId()));
		userRepository.save(user.get());

		// RefreshToken 재발급이 완료 된 경우 기존키는 삭제 처리한다.
		refreshTokenRepository.deleteById(expiredRefreshTokenId);
		logger.debug("Expired RefreshToken({}) 삭제 처리 되었습니다.", expiredRefreshTokenId);

		logger.debug("RefreshToken({}) 이 정상적으로 재발급 되었습니다.", refreshTokenResponse.getId());
	} else {
		throw new IllegalArgumentException("AccessToken 이 유효 하지 않습니다.");
	}

	return refreshTokenResponse.getId();
}

1. 전달받은 AccessToken의 유효성 체크를 한다.

2. 전달받은 AccessToken으로 토큰 사용자 정보를 조회한다.

3. 만료된 RefreshToken ID를 h2db 사용자 테이블에서 조회한다. 

4. 만료된 RefreshToken 정보를 Redis 서버에서 조회한다.

5. RefreshToken 재발급 처리한다.

6. 재발급된 RefreshToken ID 값을 h2db 사용자 테이블의 token 칼럼을 업데이트한다.

7. 기존에 저장된 RefreshToken 정보를 Redis 서버에서 삭제한다.

 

※ 토큰과 쿠키의 관계

AccessToken 발급을 위해서는 사용자 ID, 비밀번호가 필요하다. 하지만 예제의 로그인 프로세스에서는 AccessToken에 사용자 정보를 관리하게 된다. 그리고 토큰의 키 값들은 쿠키에서 관리된다.

 

하지만 쿠키 정보는 만료시간이 되면 삭제가 되게 된다.

이런 경우 발생하는 경우는 아래와 같다.

 

AccessToken 쿠키가 삭제된 경우

RefreshToken 쿠키는 유효하므로 RefreshToken 쿠키 정보에서 사용자 정보를 조회할 수 있게 된다.

 

RefreshToken 쿠키가 삭제된 경우

RefreshToken의 정보는 Redis 서버에서 관리가 된다. 

사용자 정보는 AccessToken 쿠키가 유효하므로 AccessToken 쿠키에서 조회하면 되지만, RefreshToken 재발급 후 만료된 RefreshToken 정보를 삭제해야 하는데 이 만료 키값은 h2db 사용자 테이블의 token 칼럼에 저장하게 된다.

 

다시 정리하면 AccessToken의 사용자 ID로 h2db 사용자 테이블에서 만료된 RefreshToken ID를 조회하여서 재발급 프로세스를 거치면 된다.

  

반응형

'Spring Security > Spring Boot Jwt with JPA and Redis' 카테고리의 다른 글

RefreshToken 재발급 테스트  (0) 2021.07.03
AccessToken 재발급 테스트  (0) 2021.07.02
AccessToken 재발급 프로세스  (0) 2021.06.28
로그인 테스트  (0) 2021.06.27
로그인 프로세스  (0) 2021.06.27