From c9e2976ae9ac5ee52ec6cca689bd818f1c2a9258 Mon Sep 17 00:00:00 2001 From: inhyeok Date: Sat, 27 Jul 2024 17:29:55 +0900 Subject: [PATCH 1/8] =?UTF-8?q?Chore(*):=20=EC=9E=84=EC=8B=9C=20=EC=BB=A4?= =?UTF-8?q?=EB=B0=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/usecase/ReissueUseCase.java | 30 ++++++++++++++++++- .../auth/controller/AuthController.java | 7 +++-- .../com/jabiseo/auth/dto/ReissueRequest.java | 6 ++++ .../jabiseo/cache/RedisCacheRepository.java | 7 +++-- 4 files changed, 45 insertions(+), 5 deletions(-) create mode 100644 jabiseo-api/src/main/java/com/jabiseo/auth/dto/ReissueRequest.java diff --git a/jabiseo-api/src/main/java/com/jabiseo/auth/application/usecase/ReissueUseCase.java b/jabiseo-api/src/main/java/com/jabiseo/auth/application/usecase/ReissueUseCase.java index 06124c9..53a514f 100644 --- a/jabiseo-api/src/main/java/com/jabiseo/auth/application/usecase/ReissueUseCase.java +++ b/jabiseo-api/src/main/java/com/jabiseo/auth/application/usecase/ReissueUseCase.java @@ -1,13 +1,41 @@ package com.jabiseo.auth.application.usecase; +import com.jabiseo.auth.application.JwtHandler; import com.jabiseo.auth.dto.LoginResponse; +import com.jabiseo.auth.dto.ReissueRequest; +import com.jabiseo.auth.exception.AuthenticationBusinessException; +import com.jabiseo.cache.RedisCacheRepository; +import com.jabiseo.member.domain.Member; +import com.jabiseo.member.domain.MemberRepository; +import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; @Service +@RequiredArgsConstructor +@Transactional public class ReissueUseCase { - public LoginResponse reissue(String refreshToken) { + /* + * + */ + + private final MemberRepository memberRepository; + private final RedisCacheRepository redisCacheRepository; + private final JwtHandler jwtHandler; + + public LoginResponse execute(ReissueRequest request, String memberId) { + Member member = memberRepository.getReferenceById(memberId); + + String token = redisCacheRepository.getToken(memberId); + if (!isCorretToken(token, request.refreshToken())) { + } return new LoginResponse("access_token", "refresh_token"); } + private boolean isCorretToken(String savedToken, String request) { + return request.equals(savedToken); + } + + } diff --git a/jabiseo-api/src/main/java/com/jabiseo/auth/controller/AuthController.java b/jabiseo-api/src/main/java/com/jabiseo/auth/controller/AuthController.java index 26f15ca..3f8725e 100644 --- a/jabiseo-api/src/main/java/com/jabiseo/auth/controller/AuthController.java +++ b/jabiseo-api/src/main/java/com/jabiseo/auth/controller/AuthController.java @@ -6,6 +6,9 @@ import com.jabiseo.auth.application.usecase.LogoutUseCase; import com.jabiseo.auth.application.usecase.ReissueUseCase; import com.jabiseo.auth.application.usecase.WithdrawUseCase; +import com.jabiseo.auth.dto.ReissueRequest; +import com.jabiseo.config.auth.AuthMember; +import com.jabiseo.config.auth.AuthenticatedMember; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; @@ -34,8 +37,8 @@ public ResponseEntity login(@Valid @RequestBody LoginRequest logi } @PostMapping("/reissue") - public ResponseEntity reissue(String refreshToken) { - LoginResponse result = reissueUseCase.reissue(refreshToken); + public ResponseEntity reissue(@Valid @RequestBody ReissueRequest request, @AuthenticatedMember AuthMember member) { + LoginResponse result = reissueUseCase.execute(request, member.getMemberId()); return ResponseEntity.ok(result); } diff --git a/jabiseo-api/src/main/java/com/jabiseo/auth/dto/ReissueRequest.java b/jabiseo-api/src/main/java/com/jabiseo/auth/dto/ReissueRequest.java new file mode 100644 index 0000000..244da78 --- /dev/null +++ b/jabiseo-api/src/main/java/com/jabiseo/auth/dto/ReissueRequest.java @@ -0,0 +1,6 @@ +package com.jabiseo.auth.dto; + +import jakarta.validation.constraints.NotBlank; + +public record ReissueRequest(@NotBlank String refreshToken) { +} diff --git a/jabiseo-infrastructure/src/main/java/com/jabiseo/cache/RedisCacheRepository.java b/jabiseo-infrastructure/src/main/java/com/jabiseo/cache/RedisCacheRepository.java index 8fc0132..7a527d1 100644 --- a/jabiseo-infrastructure/src/main/java/com/jabiseo/cache/RedisCacheRepository.java +++ b/jabiseo-infrastructure/src/main/java/com/jabiseo/cache/RedisCacheRepository.java @@ -18,6 +18,7 @@ public class RedisCacheRepository { private final RedisTemplate redisStringTemplate; private final ValueOperations operation; + private static final String MEMBER_TOKEN_PREFIX = "member_token:"; private final ObjectMapper mapper = new ObjectMapper(); public RedisCacheRepository(RedisTemplate redisStringTemplate) { @@ -27,14 +28,16 @@ public RedisCacheRepository(RedisTemplate redisStringTemplate) { public void saveToken(String key, String value) { - operation.set(key, value); + operation.set(MEMBER_TOKEN_PREFIX + key, value); } + public String getToken(String key) { + return operation.get(MEMBER_TOKEN_PREFIX + key); + } public void savePublicKey(String key, List publicKeys) { try { String publicKeyString = mapper.writeValueAsString(publicKeys); - // TODO: timeout 값 논의 필요 operation.set(key, publicKeyString, 1, TimeUnit.DAYS); } catch (JsonProcessingException e) { From dd46e16f8da032b1c9898cca873fffb9a6a0b959 Mon Sep 17 00:00:00 2001 From: inhyeok Date: Sat, 27 Jul 2024 19:04:33 +0900 Subject: [PATCH 2/8] =?UTF-8?q?Feat(Auth):=20reissue=20API=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/usecase/ReissueUseCase.java | 25 ++--- .../auth/controller/AuthController.java | 5 +- .../com/jabiseo/auth/dto/ReissueResponse.java | 5 + .../usecase/ReissueUseCaseTest.java | 95 +++++++++++++++++++ .../exception/AuthenticationErrorCode.java | 3 +- .../jabiseo/cache/RedisCacheRepository.java | 6 +- 6 files changed, 122 insertions(+), 17 deletions(-) create mode 100644 jabiseo-api/src/main/java/com/jabiseo/auth/dto/ReissueResponse.java create mode 100644 jabiseo-api/src/test/java/com/jabiseo/auth/application/usecase/ReissueUseCaseTest.java diff --git a/jabiseo-api/src/main/java/com/jabiseo/auth/application/usecase/ReissueUseCase.java b/jabiseo-api/src/main/java/com/jabiseo/auth/application/usecase/ReissueUseCase.java index 53a514f..97fa58d 100644 --- a/jabiseo-api/src/main/java/com/jabiseo/auth/application/usecase/ReissueUseCase.java +++ b/jabiseo-api/src/main/java/com/jabiseo/auth/application/usecase/ReissueUseCase.java @@ -3,7 +3,9 @@ import com.jabiseo.auth.application.JwtHandler; import com.jabiseo.auth.dto.LoginResponse; import com.jabiseo.auth.dto.ReissueRequest; +import com.jabiseo.auth.dto.ReissueResponse; import com.jabiseo.auth.exception.AuthenticationBusinessException; +import com.jabiseo.auth.exception.AuthenticationErrorCode; import com.jabiseo.cache.RedisCacheRepository; import com.jabiseo.member.domain.Member; import com.jabiseo.member.domain.MemberRepository; @@ -13,28 +15,27 @@ @Service @RequiredArgsConstructor -@Transactional +@Transactional(readOnly = true) public class ReissueUseCase { - /* - * - */ - private final MemberRepository memberRepository; private final RedisCacheRepository redisCacheRepository; private final JwtHandler jwtHandler; - public LoginResponse execute(ReissueRequest request, String memberId) { + public ReissueResponse execute(ReissueRequest request, String memberId) { Member member = memberRepository.getReferenceById(memberId); - String token = redisCacheRepository.getToken(memberId); - if (!isCorretToken(token, request.refreshToken())) { + jwtHandler.validateRefreshToken(request.refreshToken()); + + String savedToken = redisCacheRepository.findToken(memberId) + .orElseThrow(() -> new AuthenticationBusinessException(AuthenticationErrorCode.REQUIRE_LOGIN)); + + if (!savedToken.equals(request.refreshToken())) { + throw new AuthenticationBusinessException(AuthenticationErrorCode.NOT_MATCH_REFRESH); } - return new LoginResponse("access_token", "refresh_token"); - } - private boolean isCorretToken(String savedToken, String request) { - return request.equals(savedToken); + String accessToken = jwtHandler.createAccessToken(member); + return new ReissueResponse(accessToken); } diff --git a/jabiseo-api/src/main/java/com/jabiseo/auth/controller/AuthController.java b/jabiseo-api/src/main/java/com/jabiseo/auth/controller/AuthController.java index 3f8725e..adfa296 100644 --- a/jabiseo-api/src/main/java/com/jabiseo/auth/controller/AuthController.java +++ b/jabiseo-api/src/main/java/com/jabiseo/auth/controller/AuthController.java @@ -7,6 +7,7 @@ import com.jabiseo.auth.application.usecase.ReissueUseCase; import com.jabiseo.auth.application.usecase.WithdrawUseCase; import com.jabiseo.auth.dto.ReissueRequest; +import com.jabiseo.auth.dto.ReissueResponse; import com.jabiseo.config.auth.AuthMember; import com.jabiseo.config.auth.AuthenticatedMember; import jakarta.validation.Valid; @@ -37,8 +38,8 @@ public ResponseEntity login(@Valid @RequestBody LoginRequest logi } @PostMapping("/reissue") - public ResponseEntity reissue(@Valid @RequestBody ReissueRequest request, @AuthenticatedMember AuthMember member) { - LoginResponse result = reissueUseCase.execute(request, member.getMemberId()); + public ResponseEntity reissue(@Valid @RequestBody ReissueRequest request, @AuthenticatedMember AuthMember member) { + ReissueResponse result = reissueUseCase.execute(request, member.getMemberId()); return ResponseEntity.ok(result); } diff --git a/jabiseo-api/src/main/java/com/jabiseo/auth/dto/ReissueResponse.java b/jabiseo-api/src/main/java/com/jabiseo/auth/dto/ReissueResponse.java new file mode 100644 index 0000000..bc073d0 --- /dev/null +++ b/jabiseo-api/src/main/java/com/jabiseo/auth/dto/ReissueResponse.java @@ -0,0 +1,5 @@ +package com.jabiseo.auth.dto; + +public record ReissueResponse(String accessToken) { + +} diff --git a/jabiseo-api/src/test/java/com/jabiseo/auth/application/usecase/ReissueUseCaseTest.java b/jabiseo-api/src/test/java/com/jabiseo/auth/application/usecase/ReissueUseCaseTest.java new file mode 100644 index 0000000..383a245 --- /dev/null +++ b/jabiseo-api/src/test/java/com/jabiseo/auth/application/usecase/ReissueUseCaseTest.java @@ -0,0 +1,95 @@ +package com.jabiseo.auth.application.usecase; + +import com.jabiseo.auth.application.JwtHandler; +import com.jabiseo.auth.dto.ReissueRequest; +import com.jabiseo.auth.dto.ReissueResponse; +import com.jabiseo.auth.exception.AuthenticationBusinessException; +import com.jabiseo.auth.exception.AuthenticationErrorCode; +import com.jabiseo.cache.RedisCacheRepository; +import com.jabiseo.member.domain.Member; +import com.jabiseo.member.domain.MemberRepository; +import fixture.MemberFixture; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.BDDMockito.given; + +@ExtendWith(MockitoExtension.class) +class ReissueUseCaseTest { + + @InjectMocks + ReissueUseCase reissueUseCase; + + @Mock + MemberRepository memberRepository; + + @Mock + RedisCacheRepository redisCacheRepository; + + @Mock + JwtHandler jwtHandler; + + ReissueRequest request; + + @BeforeEach + void setUp() { + request = new ReissueRequest("refresh"); + } + + @Test + @DisplayName("저장된 토큰이 없는 경우 예외를 반환한다") + void savedTokenIsNullThrownException() { + //given + String memberId = "id"; + given(memberRepository.getReferenceById(memberId)).willReturn(MemberFixture.createMember(memberId)); + given(redisCacheRepository.findToken(memberId)).willReturn(Optional.empty()); + + //when then + assertThatThrownBy(() -> reissueUseCase.execute(request, memberId)) + .isInstanceOf(AuthenticationBusinessException.class); + } + + @Test + @DisplayName("다른 refreshToken으로 요청하면 예외를 반환한다") + void otherTokenRequestThrownException() { + //given + String memberId = "id"; + String otherToken = "tokens"; + given(memberRepository.getReferenceById(memberId)).willReturn(MemberFixture.createMember(memberId)); + given(redisCacheRepository.findToken(memberId)).willReturn(Optional.of(otherToken)); + + //when then + assertThatThrownBy(() -> reissueUseCase.execute(request, memberId)) + .isInstanceOf(AuthenticationBusinessException.class) + .hasMessage(AuthenticationErrorCode.NOT_MATCH_REFRESH.getMessage()); + } + + @Test + @DisplayName("정상 요청의 경우 새로운 access Token을 발급한다.") + void requestSuccessReturnNewAccessToken(){ + //given + String memberId = "id"; + Member member = MemberFixture.createMember(memberId); + String newAccessToken = "accessToken"; + given(memberRepository.getReferenceById(memberId)).willReturn(member); + given(redisCacheRepository.findToken(memberId)).willReturn(Optional.of(request.refreshToken())); + given(jwtHandler.createAccessToken(member)).willReturn(newAccessToken); + + //when + ReissueResponse execute = reissueUseCase.execute(request, memberId); + + //then + assertThat(execute.accessToken()).isEqualTo(newAccessToken); + } +} \ No newline at end of file diff --git a/jabiseo-domain/src/main/java/com/jabiseo/auth/exception/AuthenticationErrorCode.java b/jabiseo-domain/src/main/java/com/jabiseo/auth/exception/AuthenticationErrorCode.java index 0a0ed7e..158c9b2 100644 --- a/jabiseo-domain/src/main/java/com/jabiseo/auth/exception/AuthenticationErrorCode.java +++ b/jabiseo-domain/src/main/java/com/jabiseo/auth/exception/AuthenticationErrorCode.java @@ -10,7 +10,8 @@ public enum AuthenticationErrorCode implements ErrorCode { EXPIRED_APP_JWT("만료된 jwt 토큰 입니다", "AUTH_004", ErrorCode.UNAUTHORIZED), INVALID_APP_JWT("잘못된 jwt 토큰입니다", "AUTH_005", ErrorCode.UNAUTHORIZED), GET_JWK_FAIL("jwk 획득 실패", "AUTH_006", ErrorCode.INTERNAL_SERVER_ERROR), - REQUIRE_LOGIN("로그인이 필요합니다","AUTH007", ErrorCode.UNAUTHORIZED); + REQUIRE_LOGIN("로그인이 필요합니다","AUTH007", ErrorCode.UNAUTHORIZED), + NOT_MATCH_REFRESH("refresh token이 일치하지 않습니다", "AUTH_008", ErrorCode.UNAUTHORIZED); private final String message; private final String errorCode; diff --git a/jabiseo-infrastructure/src/main/java/com/jabiseo/cache/RedisCacheRepository.java b/jabiseo-infrastructure/src/main/java/com/jabiseo/cache/RedisCacheRepository.java index 7a527d1..69df62c 100644 --- a/jabiseo-infrastructure/src/main/java/com/jabiseo/cache/RedisCacheRepository.java +++ b/jabiseo-infrastructure/src/main/java/com/jabiseo/cache/RedisCacheRepository.java @@ -11,6 +11,7 @@ import java.util.Arrays; import java.util.List; +import java.util.Optional; import java.util.concurrent.TimeUnit; @Component @@ -31,8 +32,9 @@ public void saveToken(String key, String value) { operation.set(MEMBER_TOKEN_PREFIX + key, value); } - public String getToken(String key) { - return operation.get(MEMBER_TOKEN_PREFIX + key); + public Optional findToken(String key) { + String token = operation.get(MEMBER_TOKEN_PREFIX + key); + return Optional.ofNullable(token); } public void savePublicKey(String key, List publicKeys) { From d4efa158d030fab6aeda4044faa756f9149edec7 Mon Sep 17 00:00:00 2001 From: inhyeok Date: Sat, 27 Jul 2024 19:25:43 +0900 Subject: [PATCH 3/8] =?UTF-8?q?Feat(Auth):=20logout=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/application/usecase/LogoutUseCase.java | 9 ++++++++- .../com/jabiseo/auth/controller/AuthController.java | 4 ++-- .../java/com/jabiseo/cache/RedisCacheRepository.java | 12 ++++++++++-- 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/jabiseo-api/src/main/java/com/jabiseo/auth/application/usecase/LogoutUseCase.java b/jabiseo-api/src/main/java/com/jabiseo/auth/application/usecase/LogoutUseCase.java index 4008f47..1b99c00 100644 --- a/jabiseo-api/src/main/java/com/jabiseo/auth/application/usecase/LogoutUseCase.java +++ b/jabiseo-api/src/main/java/com/jabiseo/auth/application/usecase/LogoutUseCase.java @@ -1,10 +1,17 @@ package com.jabiseo.auth.application.usecase; +import com.jabiseo.cache.RedisCacheRepository; +import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @Service +@RequiredArgsConstructor public class LogoutUseCase { - public void execute() { + + private final RedisCacheRepository redisCacheRepository; + + public void execute(String memberId) { + redisCacheRepository.deleteToken(memberId); } } diff --git a/jabiseo-api/src/main/java/com/jabiseo/auth/controller/AuthController.java b/jabiseo-api/src/main/java/com/jabiseo/auth/controller/AuthController.java index adfa296..6432c62 100644 --- a/jabiseo-api/src/main/java/com/jabiseo/auth/controller/AuthController.java +++ b/jabiseo-api/src/main/java/com/jabiseo/auth/controller/AuthController.java @@ -44,8 +44,8 @@ public ResponseEntity reissue(@Valid @RequestBody ReissueReques } @PostMapping("/logout") - public ResponseEntity logout() { - logoutUseCase.execute(); + public ResponseEntity logout(@AuthenticatedMember AuthMember member) { + logoutUseCase.execute(member.getMemberId()); return ResponseEntity.noContent().build(); } diff --git a/jabiseo-infrastructure/src/main/java/com/jabiseo/cache/RedisCacheRepository.java b/jabiseo-infrastructure/src/main/java/com/jabiseo/cache/RedisCacheRepository.java index 69df62c..98cfe82 100644 --- a/jabiseo-infrastructure/src/main/java/com/jabiseo/cache/RedisCacheRepository.java +++ b/jabiseo-infrastructure/src/main/java/com/jabiseo/cache/RedisCacheRepository.java @@ -29,14 +29,22 @@ public RedisCacheRepository(RedisTemplate redisStringTemplate) { public void saveToken(String key, String value) { - operation.set(MEMBER_TOKEN_PREFIX + key, value); + operation.set(toMemberTokenKey(key), value); } public Optional findToken(String key) { - String token = operation.get(MEMBER_TOKEN_PREFIX + key); + String token = operation.get(toMemberTokenKey(key)); return Optional.ofNullable(token); } + public void deleteToken(String key){ + operation.getAndDelete(toMemberTokenKey(key)); + } + + private String toMemberTokenKey(String id){ + return MEMBER_TOKEN_PREFIX + id; + } + public void savePublicKey(String key, List publicKeys) { try { String publicKeyString = mapper.writeValueAsString(publicKeys); From 771d0ec173ce16f59780d0dcab300206c8b38a79 Mon Sep 17 00:00:00 2001 From: inhyeok Date: Sat, 27 Jul 2024 20:03:36 +0900 Subject: [PATCH 4/8] =?UTF-8?q?Feat(Auth):=20reissue=EC=9A=94=EC=B2=AD?= =?UTF-8?q?=EC=8B=9C=20jwtFilter=20=EA=B2=80=EC=A6=9D=20=EB=B6=84=EA=B8=B0?= =?UTF-8?q?=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../jabiseo/auth/application/JwtHandler.java | 26 ++++++++++----- .../security/JwtAuthenticationFilter.java | 15 ++++++--- .../security/JwtAuthenticationFilterTest.java | 32 ++++++++++++++++--- 3 files changed, 57 insertions(+), 16 deletions(-) diff --git a/jabiseo-api/src/main/java/com/jabiseo/auth/application/JwtHandler.java b/jabiseo-api/src/main/java/com/jabiseo/auth/application/JwtHandler.java index 3c50564..9cbf5d3 100644 --- a/jabiseo-api/src/main/java/com/jabiseo/auth/application/JwtHandler.java +++ b/jabiseo-api/src/main/java/com/jabiseo/auth/application/JwtHandler.java @@ -34,9 +34,11 @@ public JwtHandler(JwtProperty jwtProperty) { public String createAccessToken(Member member) { + System.out.println(this.accessExpiredMin); Instant accessExpiredTime = Instant.now() .plus(this.accessExpiredMin, ChronoUnit.MINUTES); - + System.out.println(accessExpiredTime); + System.out.println(Instant.now()); Map payload = new HashMap<>(); return Jwts.builder() @@ -51,6 +53,7 @@ public String createAccessToken(Member member) { public String createRefreshToken() { Instant refreshExpiredTime = Instant.now() .plus(this.refreshExpiredDay, ChronoUnit.DAYS); + System.out.println("refreshExpiredTime: " + refreshExpiredTime); return Jwts.builder() .setExpiration(Date.from(refreshExpiredTime)) .signWith(refreshKey) @@ -72,12 +75,13 @@ public boolean validateAccessToken(String token) { } } - public void validateRefreshToken(String refreshToken) { + public boolean validateRefreshToken(String refreshToken) { try { Jwts.parserBuilder() .setSigningKey(refreshKey) .build() .parseClaimsJws(refreshToken); + return true; } catch (ExpiredJwtException e) { throw new AuthenticationBusinessException(AuthenticationErrorCode.EXPIRED_APP_JWT); } catch (Exception e) { @@ -101,11 +105,17 @@ public Claims getClaimFromExpiredAccessToken(String accessToken) { } public Claims getClaimsFromAccessToken(String token) { - return Jwts - .parserBuilder() - .setSigningKey(accessKey) - .build() - .parseClaimsJws(token) - .getBody(); + try { + return Jwts + .parserBuilder() + .setSigningKey(accessKey) + .build() + .parseClaimsJws(token) + .getBody(); + } catch (ExpiredJwtException e) { + throw new AuthenticationBusinessException(AuthenticationErrorCode.EXPIRED_APP_JWT); + } catch (Exception e) { + throw new AuthenticationBusinessException(AuthenticationErrorCode.INVALID_APP_JWT); + } } } diff --git a/jabiseo-api/src/main/java/com/jabiseo/common/security/JwtAuthenticationFilter.java b/jabiseo-api/src/main/java/com/jabiseo/common/security/JwtAuthenticationFilter.java index 84dc535..150d021 100644 --- a/jabiseo-api/src/main/java/com/jabiseo/common/security/JwtAuthenticationFilter.java +++ b/jabiseo-api/src/main/java/com/jabiseo/common/security/JwtAuthenticationFilter.java @@ -24,22 +24,29 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { private final JwtHandler jwtHandler; - private static final String AUTHORIZATION_HEADER = "Authorization"; + private static final String AUTHORIZATION_HEADER = "Authorization"; private static final String HEADER_PREFIX = "Bearer "; - + private static final String REISSUE_REQUEST = "/api/auth/reissue"; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String token = extractTokenFromRequest(request); - if (StringUtils.hasText(token) && jwtHandler.validateAccessToken(token)) { - Claims claims = jwtHandler.getClaimsFromAccessToken(token); + if (StringUtils.hasText(token)) { + Claims claims = getClaimsFromToken(token, request); setAuthenticationToContext(claims); } filterChain.doFilter(request, response); } + private Claims getClaimsFromToken(String token, HttpServletRequest request) { + if (request.getRequestURI().equals(REISSUE_REQUEST)) { + return jwtHandler.getClaimFromExpiredAccessToken(token); + } + return jwtHandler.getClaimsFromAccessToken(token); + } + private void setAuthenticationToContext(Claims jwtClaim) { String memberId = jwtClaim.getSubject(); Set authorities = new HashSet<>(); diff --git a/jabiseo-api/src/test/java/com/jabiseo/common/security/JwtAuthenticationFilterTest.java b/jabiseo-api/src/test/java/com/jabiseo/common/security/JwtAuthenticationFilterTest.java index 407808d..621b13e 100644 --- a/jabiseo-api/src/test/java/com/jabiseo/common/security/JwtAuthenticationFilterTest.java +++ b/jabiseo-api/src/test/java/com/jabiseo/common/security/JwtAuthenticationFilterTest.java @@ -64,7 +64,8 @@ void containsInvalidTokenPassProcess(String headerValue) throws ServletException jwtAuthenticationFilter.doFilterInternal(request, response, chain); //then - verify(jwtHandler, never()).validateAccessToken(any()); + verify(jwtHandler, never()).getClaimsFromAccessToken(any()); + verify(jwtHandler, never()).getClaimFromExpiredAccessToken(any()); verify(chain, times(1)).doFilter(request, response); } @@ -76,14 +77,14 @@ void containsTokenStartJwtValidating() throws ServletException, IOException { String headerValue = "Bearer " + token; request.addHeader("Authorization", headerValue); - given(jwtHandler.validateAccessToken(token)).willReturn(true); given(jwtHandler.getClaimsFromAccessToken(token)).willReturn(claims); // when jwtAuthenticationFilter.doFilterInternal(request, response, chain); //then - verify(jwtHandler, times(1)).validateAccessToken(token); + verify(jwtHandler, times(1)).getClaimsFromAccessToken(token); + verify(jwtHandler, times(0)).getClaimFromExpiredAccessToken(token); } @Test @@ -96,7 +97,6 @@ void jwtValidSuccessSaveAuthenticationInfo() throws ServletException, IOExceptio String memberId = "memberId"; - given(jwtHandler.validateAccessToken(token)).willReturn(true); given(claims.getSubject()).willReturn(memberId); given(jwtHandler.getClaimsFromAccessToken(token)).willReturn(claims); @@ -109,5 +109,29 @@ void jwtValidSuccessSaveAuthenticationInfo() throws ServletException, IOExceptio assertThat(authentication.getPrincipal().toString()).isEqualTo(memberId); } + @Test + @DisplayName("reissue 요청 시 expired 검사안하고 Claim을 가져와 context에 저장한다.") + void reissueNotCheckExpired() throws ServletException, IOException { + //given + String token = "tokens"; + String headerValue = "Bearer " + token; + request.addHeader("Authorization", headerValue); + request.setRequestURI("/api/auth/reissue"); + String memberId = "memberId"; + + given(claims.getSubject()).willReturn(memberId); + given(jwtHandler.getClaimFromExpiredAccessToken(token)).willReturn(claims); + + //when + jwtAuthenticationFilter.doFilterInternal(request, response, chain); + + //then + verify(jwtHandler, never()).getClaimsFromAccessToken(token); + verify(jwtHandler, times(1)).getClaimFromExpiredAccessToken(token); + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + assertThat(authentication).isNotNull(); + assertThat(authentication.getPrincipal().toString()).isEqualTo(memberId); + } + } \ No newline at end of file From 71fb15923c39bd423fedc730bc13287e40dc5c3f Mon Sep 17 00:00:00 2001 From: inhyeok Date: Sat, 27 Jul 2024 20:04:35 +0900 Subject: [PATCH 5/8] =?UTF-8?q?Feat(*):=20=EC=82=AC=EC=9A=A9=EC=95=88?= =?UTF-8?q?=ED=95=98=EB=8A=94=20=EB=A9=94=EC=86=8C=EB=93=9C=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/jabiseo/auth/application/JwtHandler.java | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/jabiseo-api/src/main/java/com/jabiseo/auth/application/JwtHandler.java b/jabiseo-api/src/main/java/com/jabiseo/auth/application/JwtHandler.java index 9cbf5d3..3d1174c 100644 --- a/jabiseo-api/src/main/java/com/jabiseo/auth/application/JwtHandler.java +++ b/jabiseo-api/src/main/java/com/jabiseo/auth/application/JwtHandler.java @@ -61,20 +61,6 @@ public String createRefreshToken() { } - public boolean validateAccessToken(String token) { - try { - Jwts.parserBuilder() - .setSigningKey(accessKey) - .build() - .parseClaimsJws(token); - return true; - } catch (ExpiredJwtException e) { - throw new AuthenticationBusinessException(AuthenticationErrorCode.EXPIRED_APP_JWT); - } catch (Exception e) { - throw new AuthenticationBusinessException(AuthenticationErrorCode.INVALID_APP_JWT); - } - } - public boolean validateRefreshToken(String refreshToken) { try { Jwts.parserBuilder() From c6f0472a7531fb19f611d1ddd06a26fa3b3f1517 Mon Sep 17 00:00:00 2001 From: inhyeok Date: Sat, 27 Jul 2024 20:05:01 +0900 Subject: [PATCH 6/8] =?UTF-8?q?Feat(*):=20=EC=82=AC=EC=9A=A9=EC=95=88?= =?UTF-8?q?=ED=95=98=EB=8A=94=20=ED=83=80=EC=9E=85=20void=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/jabiseo/auth/application/JwtHandler.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/jabiseo-api/src/main/java/com/jabiseo/auth/application/JwtHandler.java b/jabiseo-api/src/main/java/com/jabiseo/auth/application/JwtHandler.java index 3d1174c..f07d554 100644 --- a/jabiseo-api/src/main/java/com/jabiseo/auth/application/JwtHandler.java +++ b/jabiseo-api/src/main/java/com/jabiseo/auth/application/JwtHandler.java @@ -61,13 +61,12 @@ public String createRefreshToken() { } - public boolean validateRefreshToken(String refreshToken) { + public void validateRefreshToken(String refreshToken) { try { Jwts.parserBuilder() .setSigningKey(refreshKey) .build() .parseClaimsJws(refreshToken); - return true; } catch (ExpiredJwtException e) { throw new AuthenticationBusinessException(AuthenticationErrorCode.EXPIRED_APP_JWT); } catch (Exception e) { From 3bad6b6cd8ee332ecbc40ea321b66a120203e645 Mon Sep 17 00:00:00 2001 From: inhyeok Date: Sat, 27 Jul 2024 20:05:45 +0900 Subject: [PATCH 7/8] =?UTF-8?q?Feat(*):=20print=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/jabiseo/auth/application/JwtHandler.java | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/jabiseo-api/src/main/java/com/jabiseo/auth/application/JwtHandler.java b/jabiseo-api/src/main/java/com/jabiseo/auth/application/JwtHandler.java index f07d554..a77ced6 100644 --- a/jabiseo-api/src/main/java/com/jabiseo/auth/application/JwtHandler.java +++ b/jabiseo-api/src/main/java/com/jabiseo/auth/application/JwtHandler.java @@ -21,7 +21,7 @@ public class JwtHandler { private final Key refreshKey; private final Integer accessExpiredMin; private final Integer refreshExpiredDay; - private final String APP_ISSUER = "jabiseo"; + private static final String APP_ISSUER = "jabiseo"; public JwtHandler(JwtProperty jwtProperty) { byte[] accessEncodeByte = Base64.getEncoder().encode((jwtProperty.getAccessKey().getBytes())); @@ -34,11 +34,8 @@ public JwtHandler(JwtProperty jwtProperty) { public String createAccessToken(Member member) { - System.out.println(this.accessExpiredMin); Instant accessExpiredTime = Instant.now() .plus(this.accessExpiredMin, ChronoUnit.MINUTES); - System.out.println(accessExpiredTime); - System.out.println(Instant.now()); Map payload = new HashMap<>(); return Jwts.builder() @@ -53,7 +50,6 @@ public String createAccessToken(Member member) { public String createRefreshToken() { Instant refreshExpiredTime = Instant.now() .plus(this.refreshExpiredDay, ChronoUnit.DAYS); - System.out.println("refreshExpiredTime: " + refreshExpiredTime); return Jwts.builder() .setExpiration(Date.from(refreshExpiredTime)) .signWith(refreshKey) From af2ec057f84867995bd3fc3e8336e1db0e0dcc2a Mon Sep 17 00:00:00 2001 From: inhyeok Date: Sun, 28 Jul 2024 03:30:25 +0900 Subject: [PATCH 8/8] =?UTF-8?q?Refactor(*):=20jwt=20=EA=B2=80=EC=A6=9D,=20?= =?UTF-8?q?claim=20=EC=A1=B0=ED=9A=8C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../jabiseo/auth/application/JwtHandler.java | 36 +++++++++++++------ .../security/JwtAuthenticationFilter.java | 4 ++- jabiseo-api/src/main/resources/api.yml | 2 +- .../security/JwtAuthenticationFilterTest.java | 12 +++---- 4 files changed, 36 insertions(+), 18 deletions(-) diff --git a/jabiseo-api/src/main/java/com/jabiseo/auth/application/JwtHandler.java b/jabiseo-api/src/main/java/com/jabiseo/auth/application/JwtHandler.java index a77ced6..b7acd7b 100644 --- a/jabiseo-api/src/main/java/com/jabiseo/auth/application/JwtHandler.java +++ b/jabiseo-api/src/main/java/com/jabiseo/auth/application/JwtHandler.java @@ -3,6 +3,7 @@ import com.jabiseo.auth.exception.AuthenticationBusinessException; import com.jabiseo.auth.exception.AuthenticationErrorCode; import com.jabiseo.member.domain.Member; +import io.jsonwebtoken.ClaimJwtException; import io.jsonwebtoken.Claims; import io.jsonwebtoken.ExpiredJwtException; import io.jsonwebtoken.Jwts; @@ -56,13 +57,13 @@ public String createRefreshToken() { .compact(); } - - public void validateRefreshToken(String refreshToken) { + public void validateAccessToken(String accessToken){ try { Jwts.parserBuilder() - .setSigningKey(refreshKey) + .setSigningKey(accessKey) .build() - .parseClaimsJws(refreshToken); + .parseClaimsJws(accessToken) + .getBody(); } catch (ExpiredJwtException e) { throw new AuthenticationBusinessException(AuthenticationErrorCode.EXPIRED_APP_JWT); } catch (Exception e) { @@ -70,21 +71,35 @@ public void validateRefreshToken(String refreshToken) { } } - - public Claims getClaimFromExpiredAccessToken(String accessToken) { + public void validateAccessTokenNotCheckExpired(String accessToken){ try { - return Jwts.parserBuilder() + Jwts.parserBuilder() .setSigningKey(accessKey) .build() .parseClaimsJws(accessToken) .getBody(); } catch (ExpiredJwtException e) { - return e.getClaims(); + return; + } catch (Exception e) { + throw new AuthenticationBusinessException(AuthenticationErrorCode.INVALID_APP_JWT); + } + } + + + public void validateRefreshToken(String refreshToken) { + try { + Jwts.parserBuilder() + .setSigningKey(refreshKey) + .build() + .parseClaimsJws(refreshToken); + } catch (ExpiredJwtException e) { + throw new AuthenticationBusinessException(AuthenticationErrorCode.EXPIRED_APP_JWT); } catch (Exception e) { throw new AuthenticationBusinessException(AuthenticationErrorCode.INVALID_APP_JWT); } } + public Claims getClaimsFromAccessToken(String token) { try { return Jwts @@ -93,8 +108,9 @@ public Claims getClaimsFromAccessToken(String token) { .build() .parseClaimsJws(token) .getBody(); - } catch (ExpiredJwtException e) { - throw new AuthenticationBusinessException(AuthenticationErrorCode.EXPIRED_APP_JWT); + } catch (ClaimJwtException e) { + // 기존 검증에서 처리후 가져오는 동작 + return e.getClaims(); } catch (Exception e) { throw new AuthenticationBusinessException(AuthenticationErrorCode.INVALID_APP_JWT); } diff --git a/jabiseo-api/src/main/java/com/jabiseo/common/security/JwtAuthenticationFilter.java b/jabiseo-api/src/main/java/com/jabiseo/common/security/JwtAuthenticationFilter.java index 150d021..8913621 100644 --- a/jabiseo-api/src/main/java/com/jabiseo/common/security/JwtAuthenticationFilter.java +++ b/jabiseo-api/src/main/java/com/jabiseo/common/security/JwtAuthenticationFilter.java @@ -42,7 +42,9 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse private Claims getClaimsFromToken(String token, HttpServletRequest request) { if (request.getRequestURI().equals(REISSUE_REQUEST)) { - return jwtHandler.getClaimFromExpiredAccessToken(token); + jwtHandler.validateAccessTokenNotCheckExpired(token); + } else { + jwtHandler.validateAccessToken(token); } return jwtHandler.getClaimsFromAccessToken(token); } diff --git a/jabiseo-api/src/main/resources/api.yml b/jabiseo-api/src/main/resources/api.yml index 31b2225..c5a1caf 100644 --- a/jabiseo-api/src/main/resources/api.yml +++ b/jabiseo-api/src/main/resources/api.yml @@ -38,7 +38,7 @@ management: include: "*" jwt: - access-expired-min: 6000 + access-expired-min: 1 --- spring: diff --git a/jabiseo-api/src/test/java/com/jabiseo/common/security/JwtAuthenticationFilterTest.java b/jabiseo-api/src/test/java/com/jabiseo/common/security/JwtAuthenticationFilterTest.java index 621b13e..6d021f3 100644 --- a/jabiseo-api/src/test/java/com/jabiseo/common/security/JwtAuthenticationFilterTest.java +++ b/jabiseo-api/src/test/java/com/jabiseo/common/security/JwtAuthenticationFilterTest.java @@ -65,7 +65,7 @@ void containsInvalidTokenPassProcess(String headerValue) throws ServletException //then verify(jwtHandler, never()).getClaimsFromAccessToken(any()); - verify(jwtHandler, never()).getClaimFromExpiredAccessToken(any()); + verify(jwtHandler, never()).validateAccessToken(any()); verify(chain, times(1)).doFilter(request, response); } @@ -83,8 +83,8 @@ void containsTokenStartJwtValidating() throws ServletException, IOException { jwtAuthenticationFilter.doFilterInternal(request, response, chain); //then - verify(jwtHandler, times(1)).getClaimsFromAccessToken(token); - verify(jwtHandler, times(0)).getClaimFromExpiredAccessToken(token); + verify(jwtHandler, times(1)).validateAccessToken(token); + verify(jwtHandler, times(0)).validateAccessTokenNotCheckExpired(token); } @Test @@ -120,14 +120,14 @@ void reissueNotCheckExpired() throws ServletException, IOException { String memberId = "memberId"; given(claims.getSubject()).willReturn(memberId); - given(jwtHandler.getClaimFromExpiredAccessToken(token)).willReturn(claims); + given(jwtHandler.getClaimsFromAccessToken(token)).willReturn(claims); //when jwtAuthenticationFilter.doFilterInternal(request, response, chain); //then - verify(jwtHandler, never()).getClaimsFromAccessToken(token); - verify(jwtHandler, times(1)).getClaimFromExpiredAccessToken(token); + verify(jwtHandler, never()).validateAccessToken(token); + verify(jwtHandler, times(1)).validateAccessTokenNotCheckExpired(token); Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); assertThat(authentication).isNotNull(); assertThat(authentication.getPrincipal().toString()).isEqualTo(memberId);