본문 바로가기
DB/Redis

[Redis] Refresh Token Redis에 넣어보기

by persi0815 2024. 7. 8.

1. DB에 저장

로그인: http://localhost:8080/oauth2/authorization/naver
결과: user db에 회원 정보 저장, refreshToken db에 refresh token 저장, access token redirect(json, url)

로그아웃: http://localhost:8080/users/logout
헤더: access token 담아 요청
결과: refreshToken db에서 refresh token 삭제 -> refresh token으로 access token 재발급 받지 못하도록 방지!!

회원탈퇴: http://localhost:8080/users/me
헤더: access token 담아 요청
결과: user db에서 회원정보 삭제

 

아래 로직과 대조되는 부분만 첨부하자면, 

OAuth2SuccessHandler - refresh token 생성, DB에 저장

// 사용자의 리프레시 토큰을 업데이트
Optional<RefreshToken> existingToken = refreshTokenRedisRepository.findById(username);
Optional<RefreshToken> existingToken = refreshTokenRepository.findById(username);
if (existingToken.isPresent()) {
    refreshTokenRepository.deleteById(username);
}

refreshTokenRepository.save(
        RefreshToken.builder()
                .id(username)
                .ip(IpUtil.getClientIp(request))
                .ttl(validPeriod)
                .refreshToken(jwt.getRefreshToken())
                .build()
);

UserService - 로그아웃, 회원탈퇴 시 DB에서 Refresh token 삭제

// 로그아웃
public void logout(HttpServletRequest request) {
    // 1. access token 찾아오기
    String accessToken = request.getHeader("Authorization").split(" ")[1];

    // 2. 리프레시 토큰을 username으로 찾아 삭제
    String username = jwtTokenUtils.parseClaims(accessToken).getSubject();
    log.info("access token에서 추출한 username : {}", username);
    if (refreshTokenRepository.existsById(username)) {
        refreshTokenRepository.deleteById(username);
        log.info("DB에서 리프레시 토큰 삭제 완료");
    } else {
        throw GeneralException.of(ErrorCode.WRONG_REFRESH_TOKEN);
    }
}

// access, refresh 토큰 재발급
public JwtDto reissue(HttpServletRequest request ) {
    // 1. Request에서 Refresh Token 추출
    String refreshTokenValue = request.getHeader("Authorization").split(" ")[1];

    // 2. DB에서 해당 Refresh Token을 찾음
    RefreshToken refreshToken = refreshTokenRepository.findByRefreshToken(refreshTokenValue)
            .orElseThrow(() -> new GeneralException(ErrorCode.WRONG_REFRESH_TOKEN));
    log.info("찾은 refresh token : {}", refreshToken);

    // 3. Refresh Token의 유효기간 확인 (생략)

    // 4. Refresh Token을 발급한 사용자 정보 로드
    UserDetails userDetails = manager.loadUserByUsername(refreshToken.getId());
    log.info("refresh token에서 추출한 username : {}", refreshToken.getId());

    // 5. 새로운 Access Token 및 Refresh Token 생성
    JwtDto jwt = jwtTokenUtils.generateToken(userDetails);
    log.info("reissue: refresh token 재발급 완료");

    // 6. Refresh Token 정보 업데이트 및 DB에 저장
    refreshToken.updateRefreshToken(jwt.getRefreshToken());
    Claims refreshTokenClaims = jwtTokenUtils.parseClaims(jwt.getRefreshToken());
    Long validPeriod = refreshTokenClaims.getExpiration().toInstant().getEpochSecond()
            - refreshTokenClaims.getIssuedAt().toInstant().getEpochSecond();
    refreshToken.updateTtl(validPeriod);
    refreshTokenRepository.save(refreshToken);
    log.info("accessToken: {}", jwt.getAccessToken());
    log.info("refreshToken: {} ", jwt.getRefreshToken());

    // 7. DB에 새로운 리프레시 토큰이 정상적으로 저장되었는지 확인
    if (!refreshTokenRepository.existsById(refreshToken.getId())) {
        throw GeneralException.of(ErrorCode.WRONG_REFRESH_TOKEN);
    }

    return jwt;
}

// 회원 탈퇴
public void deleteUser(String username) {
    User user = userRepository.findByUsername(username)
            .orElseThrow(() -> new GeneralException(ErrorCode.USER_NOT_FOUND));
    if (refreshTokenRepository.existsById(username)) {
        refreshTokenRepository.deleteById(username);
        log.info("DB에서 리프레시 토큰 삭제 완료");
    }
    userRepository.delete(user);
    log.info("{} 회원 탈퇴 완료", username);
}

 


2. Redis에 저장

로그인: http://localhost:8080/oauth2/authorization/naver
결과: db에 회원 정보 저장, redis에 refresh token 저장, access token redirect(json, url)

로그아웃: http://localhost:8080/users/logout
헤더: access token 담아 요청
결과: redis에서 refresh token 삭제 -> refresh token으로 access token 재발급 받지 못하도록 방지!!

회원탈퇴: http://localhost:8080/users/me
헤더: access token 담아 요청
결과: db에서 회원정보 삭제

 

로그인

 

로그아웃

 

회원탈퇴

 

코드

전체 코드는 깃허브 링크 참고: https://github.com/LikeLion-2024/Backend/pull/11

OAuth2SuccessHandler - refresh token 생성, redis에 저장

@Slf4j
@Component
// OAuth2 통신이 성공적으로 끝났을 때 사용됨
// ID Provider에게 받은 정보를 바탕으로 JWT를 발급
// + 클라이언트가 저장할 수 있도록 특정 URL로 리다이렉트
public class OAuth2SuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
    private final JwtTokenUtils tokenUtils;
    private final UserDetailsManager userDetailsManager;
    private final RefreshTokenRedisRepository refreshTokenRedisRepository;

    public OAuth2SuccessHandler(
            JwtTokenUtils tokenUtils,
            UserDetailsManager userDetailsManager,
            RefreshTokenRedisRepository refreshTokenRedisRepository
    ) {
        this.tokenUtils = tokenUtils;
        this.userDetailsManager = userDetailsManager;
        this.refreshTokenRedisRepository = refreshTokenRedisRepository;
    }

    @Override
    // 인증 성공시 호출되는 메소드
    public void onAuthenticationSuccess(
            HttpServletRequest request,
            HttpServletResponse response,
            Authentication authentication
    ) throws IOException, ServletException {
        // OAuth2UserServiceImpl에서 반환한 DefaultOAuth2User가 저장
        OAuth2User oAuth2User
                = (OAuth2User) authentication.getPrincipal();
        // 소셜 로그인을 한 새로운 사용자를 우리의 UserEntity로 전환
        String email = oAuth2User.getAttribute("email");
        String provider = oAuth2User.getAttribute("provider");
        String username
                = String.format("{%s}%s", provider, email.split("@")[0]);
        String providerId = oAuth2User.getAttribute("id").toString();

        // 처음으로 소셜 로그인한 사용자를 데이터베이스에 등록
        if(!userDetailsManager.userExists(username)) { //1. 최초 로그인인지 확인
            userDetailsManager.createUser(CustomUserDetails.builder()
                    .username(username)
                    .password(providerId)
                    .email(email)
                    .provider(provider)
                    .providerId(providerId)
                    .build());
        }

        // JWT 생성 - access & refresh
        UserDetails details
                = userDetailsManager.loadUserByUsername(username);
        JwtDto jwt = tokenUtils.generateToken(details); //2. access, refresh token 생성 및 발급
        log.info("accessToken: {}", jwt.getAccessToken());
        log.info("refreshToken: {} ", jwt.getRefreshToken());

        // 유효기간 초단위 설정 후 redis에 refresh token save
        Claims refreshTokenClaims = tokenUtils.parseClaims(jwt.getRefreshToken());
        Long validPeriod
                = refreshTokenClaims.getExpiration().toInstant().getEpochSecond()
                - refreshTokenClaims.getIssuedAt().toInstant().getEpochSecond();
        refreshTokenRedisRepository.save(
                RefreshToken.builder()
                        .id(username)
                        .ip(IpUtil.getClientIp(request))
                        .ttl(validPeriod)
                        .refreshToken(jwt.getRefreshToken())
                        .build()
        );

        // 목적지 URL 설정 - 토큰 던짐
        String targetUrl = String.format(
                "http://localhost:8080/token?access-token=%s", jwt.getAccessToken()
        );
        // 실제 Redirect 응답 생성
        getRedirectStrategy().sendRedirect(request, response, targetUrl);

    }
}

 

UserService - 로그아웃, 회원탈퇴 시 redis에서 Refresh token 삭제

// 로그아웃
public void logout(HttpServletRequest request) {
    // 1. 레디스에 해당 토큰 있는 지 확인
    String accessToken = request.getHeader("Authorization").split(" ")[1];

    // 2. 리프레시 토큰을 username으로 찾아 삭제
    String username = jwtTokenUtils.parseClaims(accessToken).getSubject();
    log.info("access token에서 추출한 username : {}", username);
    if (refreshTokenRedisRepository.existsById(username)) {
        refreshTokenRedisRepository.deleteById(username);
        log.info("레디스에서 리프레시 토큰 삭제 완료");
    } else {
        throw GeneralException.of(ErrorCode.WRONG_REFRESH_TOKEN);
    }
}
// 회원 탈퇴
public void deleteUser(String username) {
    User user = userRepository.findByUsername(username)
            .orElseThrow(() -> new GeneralException(ErrorCode.USER_NOT_FOUND));
    if (refreshTokenRedisRepository.existsById(username)) {
        refreshTokenRedisRepository.deleteById(username);
        log.info("레디스에서 리프레시 토큰 삭제 완료");
    }
    userRepository.delete(user);
    log.info("{} 회원 탈퇴 완료", username);
}

'DB > Redis' 카테고리의 다른 글

[Redis] FCM Token Redis에 넣어보기  (0) 2024.07.08
Redis란? Remote Dictionary Server  (0) 2024.07.08