diff --git a/build.gradle b/build.gradle index d715b6d..a613391 100644 --- a/build.gradle +++ b/build.gradle @@ -34,6 +34,9 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' testImplementation 'org.springframework.boot:spring-boot-starter-test' + // Junit5 + testImplementation 'org.junit.jupiter:junit-jupiter:5.5.2' + // Validation implementation 'javax.validation:validation-api:2.0.1.Final' implementation 'org.springframework.boot:spring-boot-starter-validation' @@ -75,7 +78,7 @@ dependencies { testAnnotationProcessor 'org.projectlombok:lombok' } -tasks.named('test') { +test { useJUnitPlatform() } diff --git a/src/main/java/com/siliconvalley/global/config/security/jwt/JwtAuthenticationEntryPoint.java b/src/main/java/com/siliconvalley/global/config/security/jwt/JwtAuthenticationEntryPoint.java index 53d623a..2c72c72 100644 --- a/src/main/java/com/siliconvalley/global/config/security/jwt/JwtAuthenticationEntryPoint.java +++ b/src/main/java/com/siliconvalley/global/config/security/jwt/JwtAuthenticationEntryPoint.java @@ -1,5 +1,6 @@ package com.siliconvalley.global.config.security.jwt; +import com.auth0.jwt.exceptions.SignatureVerificationException; import com.auth0.jwt.exceptions.TokenExpiredException; import com.fasterxml.jackson.databind.ObjectMapper; import com.siliconvalley.global.config.security.response.JsonResponser; @@ -30,6 +31,8 @@ public void commence(HttpServletRequest request, HttpServletResponse response, A JsonResponser.sendJsonResponse(response, ErrorCode.UNCONNECTED_REDIS); } else if (e instanceof TokenExpiredException) { JsonResponser.sendJsonResponse(response, ErrorCode.EXPIRED_TOKEN); + } else if (e instanceof SignatureVerificationException){ + JsonResponser.sendJsonResponse(response, ErrorCode.INVALID_TOKEN); } else { JsonResponser.sendJsonResponse(response, ErrorCode.UNAUTHORIZATION); } diff --git a/src/main/java/com/siliconvalley/global/config/security/jwt/JwtTokenProvider.java b/src/main/java/com/siliconvalley/global/config/security/jwt/JwtTokenProvider.java index b6e6725..7d745f3 100644 --- a/src/main/java/com/siliconvalley/global/config/security/jwt/JwtTokenProvider.java +++ b/src/main/java/com/siliconvalley/global/config/security/jwt/JwtTokenProvider.java @@ -2,10 +2,10 @@ import com.auth0.jwt.JWT; import com.auth0.jwt.algorithms.Algorithm; import com.auth0.jwt.exceptions.JWTDecodeException; +import com.auth0.jwt.exceptions.SignatureVerificationException; import com.auth0.jwt.exceptions.TokenExpiredException; import com.siliconvalley.domain.member.dao.MemberFindDao; import com.siliconvalley.domain.member.domain.Member; -import com.siliconvalley.domain.member.dao.MemberRepository; import org.springframework.data.redis.RedisConnectionFailureException; import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.stereotype.Component; @@ -16,7 +16,6 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.Date; -import java.util.Optional; import java.util.concurrent.TimeUnit; @Component @@ -127,6 +126,8 @@ public String validateToken(String accessToken, HttpServletRequest request, Http request.setAttribute("exception", e); + } catch (SignatureVerificationException e) { + request.setAttribute("exception", e); } return null; } diff --git a/src/main/java/com/siliconvalley/global/config/security/oauth/OAuth2AuthenticationSuccessHandler.java b/src/main/java/com/siliconvalley/global/config/security/oauth/OAuth2AuthenticationSuccessHandler.java index 0d6bf2b..4dfe992 100644 --- a/src/main/java/com/siliconvalley/global/config/security/oauth/OAuth2AuthenticationSuccessHandler.java +++ b/src/main/java/com/siliconvalley/global/config/security/oauth/OAuth2AuthenticationSuccessHandler.java @@ -19,6 +19,7 @@ public class OAuth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler { private final JwtTokenProvider jwtTokenProvider; + private final RedirectUrlCreator redirectUrlCreator; @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException { @@ -39,9 +40,9 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo // token 발급 or 예외 리다이렉트 String redirectUrl; if (request.getAttribute("exception") != null) { - redirectUrl = RedirectUrlCreator.createTargetUrl(request); + redirectUrl = redirectUrlCreator.createTargetUrl(request); } else { - redirectUrl = RedirectUrlCreator.createTargetUrl(accessToken, (String) oAuth2User.getAttributes().get("userId")); + redirectUrl = redirectUrlCreator.createTargetUrl(accessToken, (String) oAuth2User.getAttributes().get("userId")); } getRedirectStrategy().sendRedirect(request, response, redirectUrl); diff --git a/src/main/java/com/siliconvalley/global/config/security/response/RedirectUrlCreator.java b/src/main/java/com/siliconvalley/global/config/security/response/RedirectUrlCreator.java index 7dc7df4..58806a5 100644 --- a/src/main/java/com/siliconvalley/global/config/security/response/RedirectUrlCreator.java +++ b/src/main/java/com/siliconvalley/global/config/security/response/RedirectUrlCreator.java @@ -1,13 +1,20 @@ package com.siliconvalley.global.config.security.response; +import org.springframework.stereotype.Component; import org.springframework.web.util.UriComponentsBuilder; import javax.servlet.http.HttpServletRequest; +import org.springframework.beans.factory.annotation.Value; import java.io.IOException; +@Component public class RedirectUrlCreator { - public static String createTargetUrl(HttpServletRequest request) { - String redirectUrl = "http://localhost:3000/oauth/redirect"; + + @Value("${spring.jwt.redirect-url}") + private String url; + + public String createTargetUrl(HttpServletRequest request) { + String redirectUrl = url; String exception = (String) request.getAttribute("exception"); redirectUrl = UriComponentsBuilder.fromUriString(redirectUrl) .queryParam("exception", exception) @@ -16,8 +23,9 @@ public static String createTargetUrl(HttpServletRequest request) { return redirectUrl; } - public static String createTargetUrl(String accessToken, String userId) throws IOException { - String redirectUrl = "http://localhost:3000/oauth/redirect"; + public String createTargetUrl(String accessToken, String userId) throws IOException { + String redirectUrl = url; + System.out.println("redirectUrl = " + redirectUrl); redirectUrl = UriComponentsBuilder.fromUriString(redirectUrl) .queryParam("accessToken", accessToken) .queryParam("userId", userId) diff --git a/src/test/java/com/siliconvalley/jwt/ResolveTokenTest.java b/src/test/java/com/siliconvalley/jwt/ResolveTokenTest.java new file mode 100644 index 0000000..f3717e2 --- /dev/null +++ b/src/test/java/com/siliconvalley/jwt/ResolveTokenTest.java @@ -0,0 +1,78 @@ +package com.siliconvalley.jwt; + +import com.auth0.jwt.JWT; +import com.auth0.jwt.algorithms.Algorithm; +import com.siliconvalley.global.config.security.jwt.JwtTokenProvider; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotSame; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.mock.web.MockHttpServletRequest; + +import java.util.Date; + +@SpringBootTest +public class ResolveTokenTest { + + @Autowired + JwtTokenProvider jwtTokenProvider; + String token; + MockHttpServletRequest request; + + @BeforeEach + public void setBefore() { + token = JWT.create() + .withSubject("Access") + .withExpiresAt(new Date(System.currentTimeMillis() + 1000 * 60)) + .withClaim("userId", "1") + .withClaim("role", "ROLE_USER") + .sign(Algorithm.HMAC512("secretKey")); + + request = new MockHttpServletRequest(); + } + + @Test + @DisplayName("Header가 Authorization이 아닌 경우 실패") + public void Header_Resolving_실패() { + // Given + request.addHeader("NotAuthorization", token); + + // When + String accessToken = jwtTokenProvider.resolveToken(request); + + // Then + assertEquals(accessToken, null); + } + + @Test + @DisplayName("Prefix가 Bearer가 아닌 경우 실패") + public void Prefix_Resolving_실패() { + // Given + token = "NotBearer " + token; + request.addHeader("Authorization", token); + + // When + String accessToken = jwtTokenProvider.resolveToken(request); + + // Then + assertEquals(accessToken, null); + } + + @Test + @DisplayName("Header가 Authorization이고 Prefix가 Bearer인 경우 성공") + public void Resolving_성공() { + // Given + token = "Bearer " + token; + request.addHeader("Authorization", token); + + // When + String accessToken = jwtTokenProvider.resolveToken(request); + + // Then + assertNotSame(accessToken, null); + } +} diff --git a/src/test/java/com/siliconvalley/jwt/ValidateTokenTest.java b/src/test/java/com/siliconvalley/jwt/ValidateTokenTest.java new file mode 100644 index 0000000..48e78f0 --- /dev/null +++ b/src/test/java/com/siliconvalley/jwt/ValidateTokenTest.java @@ -0,0 +1,73 @@ +package com.siliconvalley.jwt; + +import com.auth0.jwt.JWT; +import com.auth0.jwt.algorithms.Algorithm; +import com.siliconvalley.global.config.security.jwt.JwtTokenProvider; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; + +import java.util.Date; + +@SpringBootTest +public class ValidateTokenTest { + + @Autowired + JwtTokenProvider jwtTokenProvider; + + @Value("${spring.jwt.secret}") + private String secretKey; + String wrongSecretKeyToken; + MockHttpServletRequest request; + MockHttpServletResponse response; + + @BeforeEach + public void setBefore() { + request = new MockHttpServletRequest(); + response = new MockHttpServletResponse(); + } + + @Test + @DisplayName("accessToken이 만료되면 실패") + public void 토큰_만료_예외() { + // given + String expiredToken = JWT.create() + .withSubject("Access") + .withExpiresAt(new Date(System.currentTimeMillis() + 1)) + .withClaim("userId", "1") + .withClaim("role", "ROLE_USER") + .sign(Algorithm.HMAC512(secretKey)); + + // when + String accessToken = jwtTokenProvider.validateToken(expiredToken, request, response); + System.out.println("accessToken = " + accessToken); + + // then + Assertions.assertEquals(accessToken, null); + } + + @Test + @DisplayName("accessToken의 서명이 다르면 실패") + public void 잘못된_토큰_예외() { + // given + String wrongSecretKeyToken = JWT.create() + .withSubject("Access") + .withExpiresAt(new Date(System.currentTimeMillis() + 1000*60)) + .withClaim("userId", "1") + .withClaim("role", "ROLE_USER") + .sign(Algorithm.HMAC512("fakeKey")); + + // when + String accessToken = jwtTokenProvider.validateToken(wrongSecretKeyToken, request, response); + System.out.println("accessToken = " + accessToken); + + // then + Assertions.assertEquals(accessToken, null); + } +}