Skip to content

Commit

Permalink
feat: Redis를 통한 Refresh Token 도입 (#131)
Browse files Browse the repository at this point in the history
* feat: redis를 활용한 리프레시 토큰 로직 구현

* fix: 필드명 수정

* feat: 로그인 인터셉터 accessToken 검증 로직 수정

* style: 줄바꿈

* test: 테스트 코드 리프레시 토큰 검증 코드 추가

* feat: OAuth 리프레시 토큰 로직 코드 추가

* test: OAuth 테스트 코드 리프레시 토큰 검증 코드 추가

* fix: 토큰 검증 메서드 분리

* test: 테스트 환경변수 추가

* test: 리프레시 토큰 검증 테스트 코드 추가

* fix: 액세스 토큰과 리프레시 토큰 만료기간 분리

* feat: oauth 로그인 시 redis에 토큰 저장 로직 추가

* fix: 리프레시 토큰 로직 수정

* fix: 인증 토큰 관련 예외 수정

* test: 리프레시 서비스 테스트 코드 작성

* style: 액세스 토큰 재발급 메서드 명 변경

* test: 액세스 토큰 재발급 관련 테스트 코드 추가

* feat: 액세스 토큰 재발급 관련 인수 테스트 코드 추가

* style: 줄바꿈 적용 및 주석 삭제

* fix: ReissueTokenResponse 추가

* style: 한 줄 띄기 추가

* style: 한 줄 띄기 추가

* style: 사용하지 않는 메서드 제거

* fix: Refresh Token UUID 생성 로직으로 변경

* test: application.yml

* fix: 리프레시 토큰 로직 수정

* refact: 코드 인라인으로 수정

* delete: 사용하지 않는 예외 파일 삭제

* test: 리프레시 토큰 검증 테스트 코드 수정

* test: 테스트 코드 삭제

* style: 사용하지 않는 로깅 삭제

* style: 리프레시 토큰 정보 추출 메서드 명 수정

* style: 함수 인자 변경

* refact: response 신고 필드 제거

* refactor: delete unused code

* refactor: configure refresh token validity period using @value from yml

* refact: move createRefreshToken method to Token

* feat: add handling for NPE in findTokenByRefreshToken

* feat: add updateToken method in RefreshTokenService

* style: remove redundant null check logic

* test: add reissueAccessToken test

* refact: refact reissue token logic and test code

* style: remove @transactional

* style: remove @Sl4fj in AuthServiceTest

* feat: change access-key-expire-length for test

* style: remove unused reportesCount field

---------

Co-authored-by: 임지수 <[email protected]>
  • Loading branch information
Ji-soo708 and 임지수 authored Dec 16, 2023
1 parent c4b58e0 commit de435b2
Show file tree
Hide file tree
Showing 37 changed files with 501 additions and 123 deletions.
12 changes: 11 additions & 1 deletion src/main/java/mocacong/server/controller/AuthController.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import javax.validation.Valid;
import lombok.RequiredArgsConstructor;
import mocacong.server.dto.request.AppleLoginRequest;
import mocacong.server.dto.request.AuthLoginRequest;
import mocacong.server.dto.request.KakaoLoginRequest;
import mocacong.server.dto.request.RefreshTokenRequest;
import mocacong.server.dto.response.OAuthTokenResponse;
import mocacong.server.dto.response.ReissueTokenResponse;
import mocacong.server.dto.response.TokenResponse;
import mocacong.server.service.AuthService;
import org.springframework.http.ResponseEntity;
Expand All @@ -16,6 +17,8 @@
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.validation.Valid;

@Tag(name = "Login", description = "인증")
@RestController
@RequiredArgsConstructor
Expand Down Expand Up @@ -44,4 +47,11 @@ public ResponseEntity<OAuthTokenResponse> loginKakao(@RequestBody @Valid KakaoLo
OAuthTokenResponse response = authService.kakaoOAuthLogin(request);
return ResponseEntity.ok(response);
}

@Operation(summary = "토큰 재발급")
@PostMapping("/reissue")
public ResponseEntity<ReissueTokenResponse> refreshAccessToken(@RequestBody @Valid RefreshTokenRequest request) {
ReissueTokenResponse response = authService.reissueAccessToken(request);
return ResponseEntity.ok(response);
}
}
36 changes: 36 additions & 0 deletions src/main/java/mocacong/server/domain/Token.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package mocacong.server.domain;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.springframework.data.redis.core.TimeToLive;

import javax.persistence.Id;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class Token {

@Id
private Long id;

private String refreshToken;

private String accessToken;

@TimeToLive(unit = TimeUnit.MILLISECONDS)
private long expiration;

public void setAccessToken(String newAccessToken) {
this.accessToken = newAccessToken;
}

public static String createRefreshToken() {
return UUID.randomUUID().toString();
}
}
15 changes: 15 additions & 0 deletions src/main/java/mocacong/server/dto/request/RefreshTokenRequest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package mocacong.server.dto.request;

import lombok.*;

import javax.validation.constraints.NotBlank;

@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
@Getter
@ToString
public class RefreshTokenRequest {

@NotBlank(message = "1012:공백일 수 없습니다.")
private String refreshToken;
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,4 @@
public class CafeImageReportResponse {

private int cafeImageReportCount;

private int userReportCount;
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,10 @@ public class CafeReviewResponse {
private String sound;
private String desk;
private int reviewsCount;
private int userReportCount;

public static CafeReviewResponse of(double score, Cafe cafe, Member member) {
CafeDetail cafeDetail = cafe.getCafeDetail();
int reviewsCount = cafe.getReviews().size();
int userReportCount = member.getReportCount();

return new CafeReviewResponse(
score,
Expand All @@ -36,8 +34,7 @@ public static CafeReviewResponse of(double score, Cafe cafe, Member member) {
cafeDetail.getPowerValue(),
cafeDetail.getSoundValue(),
cafeDetail.getDeskValue(),
reviewsCount,
userReportCount
reviewsCount
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,4 @@
public class CommentReportResponse {

private int commentReportCount;

private int userReportCount;
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,4 @@
public class CommentSaveResponse {

private Long id;

private int userReportCount;
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,4 @@
public class FavoriteSaveResponse {

private Long favoriteId;

private int userReportCount;
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
@ToString
public class OAuthTokenResponse {

private String token;
private String accessToken;
private String refreshToken;
private String email;
private Boolean isRegistered;
private String platformId;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package mocacong.server.dto.response;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;

@Getter
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class ReissueTokenResponse {
private String accessToken;

private int userReportCount;

public static ReissueTokenResponse from(final String accessToken, int userReportCount) {
return new ReissueTokenResponse(accessToken, userReportCount);
}
}
8 changes: 5 additions & 3 deletions src/main/java/mocacong/server/dto/response/TokenResponse.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@
@AllArgsConstructor
@ToString
public class TokenResponse {
private String token;
private String accessToken;

private String refreshToken;

private int userReportCount;

public static TokenResponse from(final String token, int userReportCount) {
return new TokenResponse(token, userReportCount);
public static TokenResponse from(final String accessToken, final String refreshToken, int userReportCount) {
return new TokenResponse(accessToken, refreshToken, userReportCount);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package mocacong.server.exception.badrequest;

public class NotExpiredAccessTokenException extends BadRequestException {

public NotExpiredAccessTokenException() {
super("아직 만료되지 않은 액세스 토큰입니다", 1022);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package mocacong.server.exception.unauthorized;

import lombok.Getter;

@Getter
public class AccessTokenExpiredException extends UnauthorizedException {

public AccessTokenExpiredException() {
super("Access Token 유효기간이 만료되었습니다.", 1014);
}

public AccessTokenExpiredException(String message) {
super(message, 1014);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package mocacong.server.exception.unauthorized;

public class InvalidAccessTokenException extends UnauthorizedException {

public InvalidAccessTokenException() {
super("올바르지 않은 Access Token 입니다. 다시 로그인해주세요.", 1015);
}

public InvalidAccessTokenException(String message) {
super(message, 1015);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package mocacong.server.exception.unauthorized;

import lombok.Getter;

@Getter
public class InvalidRefreshTokenException extends UnauthorizedException {

public InvalidRefreshTokenException() {
super("올바르지 않은 Refresh Token 입니다. 다시 로그인해주세요.", 1021);
}
}

This file was deleted.

This file was deleted.

37 changes: 24 additions & 13 deletions src/main/java/mocacong/server/security/auth/JwtTokenProvider.java
Original file line number Diff line number Diff line change
@@ -1,29 +1,31 @@
package mocacong.server.security.auth;

import mocacong.server.exception.unauthorized.InvalidTokenException;
import mocacong.server.exception.unauthorized.TokenExpiredException;
import io.jsonwebtoken.*;
import mocacong.server.exception.unauthorized.AccessTokenExpiredException;
import mocacong.server.exception.unauthorized.InvalidAccessTokenException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import io.jsonwebtoken.*;

import java.util.Date;

@Component
public class JwtTokenProvider {
private final String secretKey;
private final long validityInMilliseconds;
private final long validityAccessTokenInMilliseconds;

private final JwtParser jwtParser;

public JwtTokenProvider(@Value("${security.jwt.token.secret-key}") String secretKey,
@Value("${security.jwt.token.expire-length}") long validityInMilliseconds) {
@Value("${security.jwt.token.access-key-expire-length}")
long validityAccessTokenInMilliseconds) {
this.secretKey = secretKey;
this.validityInMilliseconds = validityInMilliseconds;
this.validityAccessTokenInMilliseconds = validityAccessTokenInMilliseconds;
this.jwtParser = Jwts.parser().setSigningKey(secretKey);
}

public String createToken(Long memberId) {
public String createAccessToken(Long memberId) {
Date now = new Date();
Date validity = new Date(now.getTime() + validityInMilliseconds);
Date validity = new Date(now.getTime() + validityAccessTokenInMilliseconds);

return Jwts.builder()
.setSubject(String.valueOf(memberId))
Expand All @@ -33,23 +35,32 @@ public String createToken(Long memberId) {
.compact();
}

public void validateToken(String token) {
public void validateAccessToken(String token) {
try {
jwtParser.parseClaimsJws(token);
} catch (ExpiredJwtException e) {
throw new TokenExpiredException();
throw new AccessTokenExpiredException();
} catch (JwtException e) {
throw new InvalidTokenException();
throw new InvalidAccessTokenException();
}
}

public boolean isExpiredAccessToken(String token) {
try {
jwtParser.parseClaimsJws(token);
} catch (ExpiredJwtException e) {
return true;
}
return false;
}

public String getPayload(String token) {
try {
return jwtParser.parseClaimsJws(token).getBody().getSubject();
} catch (ExpiredJwtException e) {
throw new TokenExpiredException();
throw new AccessTokenExpiredException();
} catch (JwtException e) {
throw new InvalidTokenException();
throw new InvalidAccessTokenException();
}
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
package mocacong.server.security.auth;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Component
public class LoginInterceptor implements HandlerInterceptor {
private final JwtTokenProvider jwtTokenProvider;
Expand All @@ -20,8 +21,8 @@ public boolean preHandle(HttpServletRequest request, HttpServletResponse respons
return true;
}

String token = AuthorizationExtractor.extractAccessToken(request);
jwtTokenProvider.validateToken(token);
String accessToken = AuthorizationExtractor.extractAccessToken(request);
jwtTokenProvider.validateAccessToken(accessToken);
return true;
}

Expand Down
Loading

0 comments on commit de435b2

Please sign in to comment.