Oauth 카카오 로그인 + Spring Boot + JWT 로그인 구현(2)

김도훈 - DevOps Engineer·2022년 3월 23일
2

Spring Boot

목록 보기
2/3

지난 글에서 Jwt AccessToken, RefreshToken을 발급받는 과정까지 구현해 보았다

  • 발행된 AccessToken의 값은 무조건적으로 명백하다고 생각하여 요청을 허가시킴. Access Token탈취의 위험이 존재하기 때문에 짧은 유효시간을 두어, 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);
    }

}

먼저 위와 같이 설정을 끝냈다면 이전 게시글에서 작성했던 코드를 바꿔보자

@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);
    }

}
  • 자세한 코드는 시간 관계상 넘어가고 Redis 관련 로직만 살펴보겠다.
  • 먼저 간략하게 설명하자면 Front end 쪽으로 부터 Oauth로그인을 할 때 Oauth 서버로부터 Code를 받아와서 OauthController 에 login을 호출하게 된다.
  • OauthServicelogin 메소드를 호출하여 위와 같은 로직이 실행되는데 이전 게시글에서 accessToken의 시간을 30분으로 잡아놨다 그 이유는 accessToken탈취당할경우 user의 정보가 그대로 노출이 되기 때문에 accessToken의 지속시간은 되도록 짧게 잡아주는 것이 좋다. refreshToke의 경우 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("로그아웃이 완료 되었습니다.");
    }
    
profile
Email:ehgns5669@gmail.com

0개의 댓글