- 지난 글에서 Jwt
AccessToken
,RefreshToken
을 발급받는 과정까지 구현해 보았다 - 발행된
AccessToken
의 값은 무조건적으로 명백하다고 생각하여 요청을 허가시킴. AccessToken
탈취의 위험이 존재하기 때문에 짧은 유효시간을 두어,Access Token
이 탈취 당하더라도 만료되어 사용할 수 없도록 한다. Refresh Token
은 서버에서 그 값(Redis
)을 저장함.Refresh Token
을 사용할 상황이 오면 반드시 서버에서 그 유효성을 판별, 유효하지 않는 경우라면 요청을 거부. 혹은 사용자로부터 탈취됐다는 정보가 오면 그Refrsh Token
을 폐기할 수 있도록 설정.
위 과정에서
Redis
를 사용하는 이유는 다음과 같다.
Refresh Token을 서버에서 어디에다 저장할 것인가?
이미 정답을 제시하고 문제를 냈기 때문에 답하는데 김이 빠지긴 하지만 이유를 설명하자면 Refresh Token은 만료되어야 하기 때문이다. 그럼 휘발성을 가진 데이터 베이스가 무엇이 있을까? 정답 : Redis
Redis를 사용하기 위해선 Dependency를 추가해준다.
먼저 Redis는 설치
가 되어있다고 가정하고 진행을 하겠습니다.
Redis
Gradle
에 추가하기
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
application.yml
spring:
redis:
host: localhost
port: 6379
- 다음과 같이 RedisUtil 클래스를 설정을 해주어야 한다.
@Component
@RequiredArgsConstructor
public class RedisUtil {
private final StringRedisTemplate stringRedisTemplate;
public String getData(String key) {
ValueOperations<String,String> valueOperations = stringRedisTemplate.opsForValue();
return valueOperations.get(key);
}
public void setData(String key, String value) {
ValueOperations<String,String> valueOperations = stringRedisTemplate.opsForValue();
valueOperations.set(key, value);
}
public void setDataExpire(String key, String value, long duration) {
ValueOperations<String, String> valueOperations = stringRedisTemplate.opsForValue();
Duration expireDuration = Duration.ofSeconds(duration);
valueOperations.set(key, value, expireDuration);
}
public void deleteData(String key) {
stringRedisTemplate.delete(key);
}
}
- 먼저 위와 같이 설정을 끝냈다면 이전 게시글에서 작성했던 코드를 바꿔보자
@Service
@RequiredArgsConstructor
@Slf4j
@Transactional(readOnly = true)
public class OauthService {
private static final String BEARER_TYPE = "Bearer";
private final InMemoryClientRegistrationRepository inMemoryRepository;
private final UserRepository userRepository;
private final JwtTokenProvider jwtTokenProvider;
private final RedisUtil redisUtil;
/**
* @InMemoryRepository application-oauth properties 정보를 담고 있음
* @getToken() 넘겨받은 code 로 Oauth 서버에 Token 요청
* @getUserProfile 첫 로그인 시 회원가입
* 유저 인증 후 Jwt AccessToken, Refresh Token 생성
* RefreshToken Redis 저장 만료기간 2주일
*/
@Transactional
public LoginResponse login(AuthorizationRequest authorizationRequest) {
ClientRegistration provider = inMemoryRepository.findByRegistrationId(authorizationRequest.getProviderName());
User user = getUserProfile(authorizationRequest, provider);
Token accessToken = jwtTokenProvider.createAccessToken(String.valueOf(user.getId()));
Token refreshToken = jwtTokenProvider.createRefreshToken();
redisUtil.setDataExpire(String.valueOf(user.getId()), refreshToken.getValue(), refreshToken.getExpiredTime() );
boolean validateUser = validateProfileSaveUser(user.getId());
return LoginResponse.builder()
.id(user.getId())
.nickName(user.getUserProfile().getNickName())
.email(user.getEmail())
.imageUrl(user.getUserProfile().getUploadFile().getStoreFilename())
.role(user.getRole())
.tokenType(BEARER_TYPE)
.accessToken(accessToken.getValue())
.refreshToken(refreshToken.getValue())
.profileSaveUser(validateUser)
.build();
}
- 자세한 코드는 시간 관계상 넘어가고
Redis
관련 로직만 살펴보겠다. - 먼저 간략하게 설명하자면
Front end
쪽으로 부터Oauth로그인을
할 때 Oauth 서버로부터 Code를 받아와서OauthController
에login
을 호출하게 된다. OauthService
의login
메소드를 호출하여 위와 같은 로직이 실행되는데 이전 게시글에서accessToken
의 시간을 30분으로 잡아놨다 그 이유는accessToken
이 탈취당할경우user
의 정보가 그대로 노출이 되기 때문에accessToken
의 지속시간은 되도록 짧게 잡아주는 것이 좋다.refreshToken
의 경우user
의 정보가 담겨있는것이 아닌user
의id
만 넣어주기로 했다refreshToken
을 탈취 당하더라도 회원의 정보가 아닌 회원의 아이디만 탈취당하게 된다.refreshToken
의 용도는accessToken
의 만료기간이 끝났을 경우 재발급을 받기 위해서 사용된다. 그로인해accessToken
에 비해 상대적으로 만료기간을 훨씬 길게 잡아주어서 2주일로 설정 하였다.- 이전 게시글에서와 같이
refreshToken
을 생성해 주는 것까지는 같다. 여기서redisUtil.setDataExpire() 메서드로
회원 id', '토큰 값', '토큰 만료 시간'을 넣어서 Redis`에
저장해준다.
실행이 되었다면
Terminal
에서 다음과 같이redis-cli
로 확인을 해보면 된다.
redis-cli
- keys * : (
redis
에 저장된 모든 키를 보여줌) - get(key) : 입력한
key
에 대한value
를 보여줌 - ttl(key) : 입력한
key
에 대한만료 시간
을 보여줌
위와 같이 성공적으로 redis
에 값이 들어온 것을 볼 수 있다.
다른 메서드는 아래에서 간략히 보여드리기만 하겠습니다.
추가로
redis
에 저장된refreshToken
을 사용하는 메서드도 살펴보겠다.
accessToken
재발급
/**
* refresh Token 으로 Access Token 이 만료 되었을 경우 재발급
* Redis Server 에서 refresh Token 을 가져옴
*/
public AccessTokenResponse accessTokenByRefreshToken(String accessToken, RefreshTokenRequest refreshTokenRequest) {
refreshTokenExtractor(refreshTokenRequest);
String id = jwtTokenProvider.getPayload(accessToken);
String data = redisUtil.getData(id);
if (!data.equals(refreshTokenRequest.getRefreshToken())) {
log.info("Exception!!");
throw new CustomException(ErrorCode.UNAUTHORIZED_REFRESH_TOKEN);
}
Token newAccessToken = jwtTokenProvider.createAccessToken(id);
return new AccessTokenResponse(newAccessToken.getValue());
}
- 로그아웃
/**
* 로그아웃 시 토큰도 같이 삭제
*/
@Transactional
public CustomResponse logout(String accessToken) {
String id = jwtTokenProvider.getPayload(accessToken);
redisUtil.deleteData(id);
return new CustomResponse("로그아웃이 완료 되었습니다.");
}
'Spring Boot' 카테고리의 다른 글
Oauth 카카오 로그인 + Spring Boot + JWT 로그인 구현(1) (3) | 2022.02.15 |
---|