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 |