diff --git a/src/main/java/sopt/makers/authentication/application/auth/api/AuthKeyApi.java b/src/main/java/sopt/makers/authentication/application/auth/api/AuthKeyApi.java new file mode 100644 index 0000000..b778e3f --- /dev/null +++ b/src/main/java/sopt/makers/authentication/application/auth/api/AuthKeyApi.java @@ -0,0 +1,8 @@ +package sopt.makers.authentication.application.auth.api; + +import org.springframework.http.ResponseEntity; + +public interface AuthKeyApi { + + ResponseEntity retrievePublicJwks(); +} diff --git a/src/main/java/sopt/makers/authentication/application/auth/api/AuthKeyApiController.java b/src/main/java/sopt/makers/authentication/application/auth/api/AuthKeyApiController.java new file mode 100644 index 0000000..5a326bc --- /dev/null +++ b/src/main/java/sopt/makers/authentication/application/auth/api/AuthKeyApiController.java @@ -0,0 +1,25 @@ +package sopt.makers.authentication.application.auth.api; + +import sopt.makers.authentication.usecase.auth.port.in.JwksRetrieveUsecase; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import lombok.RequiredArgsConstructor; + +@RestController +@RequestMapping("/.well-known") +@RequiredArgsConstructor +public class AuthKeyApiController implements AuthKeyApi { + + private final JwksRetrieveUsecase jwksRetrieveUsecase; + + @Override + @GetMapping(value = "/jwks.json") + public ResponseEntity retrievePublicJwks() { + return ResponseEntity.status(HttpStatus.OK).body(jwksRetrieveUsecase.retrievePublicKey()); + } +} diff --git a/src/main/java/sopt/makers/authentication/support/code/support/failure/ResourceFailure.java b/src/main/java/sopt/makers/authentication/support/code/support/failure/ResourceFailure.java new file mode 100644 index 0000000..2869b62 --- /dev/null +++ b/src/main/java/sopt/makers/authentication/support/code/support/failure/ResourceFailure.java @@ -0,0 +1,21 @@ +package sopt.makers.authentication.support.code.support.failure; + +import static lombok.AccessLevel.PRIVATE; + +import sopt.makers.authentication.support.code.base.FailureCode; + +import org.springframework.http.HttpStatus; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor(access = PRIVATE) +public enum ResourceFailure implements FailureCode { + INVALID_LOCATION(HttpStatus.BAD_REQUEST, "키 파일 위치가 잘못되었습니다."), + INVALID_SUBJECT(HttpStatus.BAD_REQUEST, "주체 정보가 잘못되었습니다."), + INVALID_ALGORITHM(HttpStatus.BAD_REQUEST, "알고리즘이 잘못되었습니다."), + ; + private final HttpStatus status; + private final String message; +} diff --git a/src/main/java/sopt/makers/authentication/support/code/support/failure/TokenFailure.java b/src/main/java/sopt/makers/authentication/support/code/support/failure/TokenFailure.java index 30d9159..9649d5b 100644 --- a/src/main/java/sopt/makers/authentication/support/code/support/failure/TokenFailure.java +++ b/src/main/java/sopt/makers/authentication/support/code/support/failure/TokenFailure.java @@ -14,11 +14,8 @@ public enum TokenFailure implements FailureCode { TOKEN_EXPIRED(HttpStatus.BAD_REQUEST, "토큰이 만료되었습니다."), UNSUPPORTED_ISSUER(HttpStatus.BAD_REQUEST, "신뢰할 수 없는 발급자입니다."), - INVALID_SUBJECT(HttpStatus.BAD_REQUEST, "주체 정보가 잘못되었습니다."), INVALID_PREFIX(HttpStatus.BAD_REQUEST, "토큰 접두사가 잘못되었습니다."), - INVALID_ALGORITHM(HttpStatus.BAD_REQUEST, "알고리즘이 잘못되었습니다."), INVALID_SIGNATURE(HttpStatus.BAD_REQUEST, "서명이 잘못되었습니다."), - INVALID_LOCATION(HttpStatus.BAD_REQUEST, "키 파일 위치가 잘못되었습니다."), ; ; private final HttpStatus status; diff --git a/src/main/java/sopt/makers/authentication/support/config/JwtRSAKeyConfiguration.java b/src/main/java/sopt/makers/authentication/support/config/JwtRSAKeyConfiguration.java index b4c9cfb..74de01f 100644 --- a/src/main/java/sopt/makers/authentication/support/config/JwtRSAKeyConfiguration.java +++ b/src/main/java/sopt/makers/authentication/support/config/JwtRSAKeyConfiguration.java @@ -1,30 +1,12 @@ package sopt.makers.authentication.support.config; -import static sopt.makers.authentication.support.code.support.failure.TokenFailure.INVALID_ALGORITHM; -import static sopt.makers.authentication.support.code.support.failure.TokenFailure.INVALID_LOCATION; -import static sopt.makers.authentication.support.code.support.failure.TokenFailure.INVALID_SUBJECT; +import sopt.makers.authentication.support.jwt.RSAKeyManager; -import sopt.makers.authentication.support.exception.support.TokenException; -import sopt.makers.authentication.support.value.JwtProperty; - -import java.io.IOException; -import java.io.StringReader; -import java.nio.charset.StandardCharsets; -import java.security.KeyFactory; -import java.security.NoSuchAlgorithmException; import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPublicKey; -import java.security.spec.InvalidKeySpecException; -import java.security.spec.PKCS8EncodedKeySpec; -import java.security.spec.X509EncodedKeySpec; -import org.bouncycastle.util.io.pem.PemObject; -import org.bouncycastle.util.io.pem.PemReader; -import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.core.io.Resource; -import org.springframework.core.io.ResourceLoader; import org.springframework.security.oauth2.jwt.JwtDecoder; import org.springframework.security.oauth2.jwt.JwtEncoder; import org.springframework.security.oauth2.jwt.NimbusJwtDecoder; @@ -38,49 +20,17 @@ import com.nimbusds.jose.proc.SecurityContext; import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; @Configuration -@EnableConfigurationProperties(JwtProperty.class) @RequiredArgsConstructor -@Slf4j public class JwtRSAKeyConfiguration { - private final JwtProperty jwtProperty; - private final ResourceLoader resourceLoader; - - public RSAPublicKey createPublicKeyFromProperty() { - try { - Resource resource = loadPublicKeyResource(); - PemObject pemObject = readPublicPemFile(resource); - return generatePublicKey(pemObject); - } catch (IOException e) { - throw new TokenException(INVALID_LOCATION); - } catch (NoSuchAlgorithmException e) { - throw new TokenException(INVALID_ALGORITHM); - } catch (InvalidKeySpecException e) { - throw new TokenException(INVALID_SUBJECT); - } - } - - public RSAPrivateKey createPrivateKeyFromProperty() { - try { - Resource resource = loadPrivateKeyResource(); - PemObject pemObject = readPrivatePemFile(resource); - return generatePrivateKey(pemObject); - } catch (IOException e) { - throw new TokenException(INVALID_LOCATION); - } catch (NoSuchAlgorithmException e) { - throw new TokenException(INVALID_ALGORITHM); - } catch (InvalidKeySpecException e) { - throw new TokenException(INVALID_SUBJECT); - } - } + private final RSAKeyManager keyManager; @Bean public JwtEncoder jwtEncoder() { - RSAPublicKey publicKey = createPublicKeyFromProperty(); - RSAPrivateKey privateKey = createPrivateKeyFromProperty(); + RSAPublicKey publicKey = keyManager.getPublicKey(); + RSAPrivateKey privateKey = keyManager.getPrivateKey(); JWK jwk = new RSAKey.Builder(publicKey).privateKey(privateKey).build(); JWKSource jwks = new ImmutableJWKSet<>(new JWKSet(jwk)); @@ -89,44 +39,6 @@ public JwtEncoder jwtEncoder() { @Bean public JwtDecoder jwtDecoder() { - return NimbusJwtDecoder.withPublicKey(createPublicKeyFromProperty()).build(); - } - - private Resource loadPublicKeyResource() { - return resourceLoader.getResource(jwtProperty.secret().rsa().publicKey()); - } - - private PemObject readPublicPemFile(Resource resource) throws IOException { - try (PemReader pemReader = - new PemReader(new StringReader(resource.getContentAsString(StandardCharsets.UTF_8)))) { - return pemReader.readPemObject(); - } - } - - private RSAPublicKey generatePublicKey(PemObject pemObject) - throws NoSuchAlgorithmException, InvalidKeySpecException { - byte[] publicKeyBytes = pemObject.getContent(); - X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKeyBytes); - KeyFactory keyFactory = KeyFactory.getInstance("RSA"); - return (RSAPublicKey) keyFactory.generatePublic(keySpec); - } - - private Resource loadPrivateKeyResource() { - return resourceLoader.getResource(jwtProperty.secret().rsa().privateKey()); - } - - private PemObject readPrivatePemFile(Resource resource) throws IOException { - try (PemReader pemReader = - new PemReader(new StringReader(resource.getContentAsString(StandardCharsets.UTF_8)))) { - return pemReader.readPemObject(); - } - } - - private RSAPrivateKey generatePrivateKey(PemObject pemObject) - throws NoSuchAlgorithmException, InvalidKeySpecException { - byte[] privateKeyBytes = pemObject.getContent(); - PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKeyBytes); - KeyFactory keyFactory = KeyFactory.getInstance("RSA"); - return (RSAPrivateKey) keyFactory.generatePrivate(keySpec); + return NimbusJwtDecoder.withPublicKey(keyManager.getPublicKey()).build(); } } diff --git a/src/main/java/sopt/makers/authentication/support/config/LocalRSAKeyManager.java b/src/main/java/sopt/makers/authentication/support/config/LocalRSAKeyManager.java new file mode 100644 index 0000000..26780ad --- /dev/null +++ b/src/main/java/sopt/makers/authentication/support/config/LocalRSAKeyManager.java @@ -0,0 +1,108 @@ +package sopt.makers.authentication.support.config; + +import static sopt.makers.authentication.support.code.support.failure.ResourceFailure.INVALID_ALGORITHM; +import static sopt.makers.authentication.support.code.support.failure.ResourceFailure.INVALID_LOCATION; +import static sopt.makers.authentication.support.code.support.failure.ResourceFailure.INVALID_SUBJECT; + +import sopt.makers.authentication.support.exception.support.ResourceException; +import sopt.makers.authentication.support.jwt.RSAKeyManager; +import sopt.makers.authentication.support.value.JwtProperty; + +import java.io.IOException; +import java.io.StringReader; +import java.nio.charset.StandardCharsets; +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; + +import org.bouncycastle.util.io.pem.PemObject; +import org.bouncycastle.util.io.pem.PemReader; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceLoader; +import org.springframework.stereotype.Component; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Component +@EnableConfigurationProperties(JwtProperty.class) +@RequiredArgsConstructor +@Slf4j +public class LocalRSAKeyManager implements RSAKeyManager { + + private final JwtProperty jwtProperty; + private final ResourceLoader resourceLoader; + + @Override + public RSAPublicKey getPublicKey() { + try { + Resource resource = loadPublicKeyResource(); + PemObject pemObject = readPublicPemFile(resource); + return parsePublicKey(pemObject); + } catch (IOException e) { + throw new ResourceException(INVALID_LOCATION); + } catch (NoSuchAlgorithmException e) { + throw new ResourceException(INVALID_ALGORITHM); + } catch (InvalidKeySpecException e) { + throw new ResourceException(INVALID_SUBJECT); + } + } + + @Override + public RSAPrivateKey getPrivateKey() { + try { + Resource resource = loadPrivateKeyResource(); + PemObject pemObject = readPrivatePemFile(resource); + return generatePrivateKey(pemObject); + } catch (IOException e) { + throw new ResourceException(INVALID_LOCATION); + } catch (NoSuchAlgorithmException e) { + throw new ResourceException(INVALID_ALGORITHM); + } catch (InvalidKeySpecException e) { + throw new ResourceException(INVALID_SUBJECT); + } + } + + private Resource loadPublicKeyResource() { + return resourceLoader.getResource(jwtProperty.secret().rsa().publicKey()); + } + + private PemObject readPublicPemFile(final Resource resource) throws IOException { + try (PemReader pemReader = + new PemReader(new StringReader(resource.getContentAsString(StandardCharsets.UTF_8)))) { + return pemReader.readPemObject(); + } + } + + private RSAPublicKey parsePublicKey(final PemObject pemObject) + throws NoSuchAlgorithmException, InvalidKeySpecException { + byte[] publicKeyBytes = pemObject.getContent(); + X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKeyBytes); + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + return (RSAPublicKey) keyFactory.generatePublic(keySpec); + } + + private Resource loadPrivateKeyResource() { + return resourceLoader.getResource(jwtProperty.secret().rsa().privateKey()); + } + + private PemObject readPrivatePemFile(final Resource resource) throws IOException { + try (PemReader pemReader = + new PemReader(new StringReader(resource.getContentAsString(StandardCharsets.UTF_8)))) { + return pemReader.readPemObject(); + } + } + + private RSAPrivateKey generatePrivateKey(final PemObject pemObject) + throws NoSuchAlgorithmException, InvalidKeySpecException { + byte[] privateKeyBytes = pemObject.getContent(); + PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKeyBytes); + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + return (RSAPrivateKey) keyFactory.generatePrivate(keySpec); + } +} diff --git a/src/main/java/sopt/makers/authentication/support/constant/JwtConstant.java b/src/main/java/sopt/makers/authentication/support/constant/JwtConstant.java index e04e778..82a0c75 100644 --- a/src/main/java/sopt/makers/authentication/support/constant/JwtConstant.java +++ b/src/main/java/sopt/makers/authentication/support/constant/JwtConstant.java @@ -3,5 +3,6 @@ public class JwtConstant { public static final String TOKEN_HEADER = "Bearer "; + public static final String[] SERVICE_NAMES = {"playground", "crew", "app", "admin"}; public static final String REFRESH_TOKEN_HEADER = "refresh-token"; } diff --git a/src/main/java/sopt/makers/authentication/support/exception/support/ResourceException.java b/src/main/java/sopt/makers/authentication/support/exception/support/ResourceException.java new file mode 100644 index 0000000..8c37d59 --- /dev/null +++ b/src/main/java/sopt/makers/authentication/support/exception/support/ResourceException.java @@ -0,0 +1,11 @@ +package sopt.makers.authentication.support.exception.support; + +import sopt.makers.authentication.support.code.support.failure.ResourceFailure; +import sopt.makers.authentication.support.exception.base.BaseException; + +public class ResourceException extends BaseException { + + public ResourceException(final ResourceFailure failure) { + super(failure); + } +} diff --git a/src/main/java/sopt/makers/authentication/support/jwt/RSAKeyManager.java b/src/main/java/sopt/makers/authentication/support/jwt/RSAKeyManager.java new file mode 100644 index 0000000..a00d61f --- /dev/null +++ b/src/main/java/sopt/makers/authentication/support/jwt/RSAKeyManager.java @@ -0,0 +1,11 @@ +package sopt.makers.authentication.support.jwt; + +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; + +public interface RSAKeyManager { + + RSAPublicKey getPublicKey(); + + RSAPrivateKey getPrivateKey(); +} diff --git a/src/main/java/sopt/makers/authentication/support/jwt/token/JwtAccessToken.java b/src/main/java/sopt/makers/authentication/support/jwt/token/JwtAccessToken.java index d2c2b11..6c698c1 100644 --- a/src/main/java/sopt/makers/authentication/support/jwt/token/JwtAccessToken.java +++ b/src/main/java/sopt/makers/authentication/support/jwt/token/JwtAccessToken.java @@ -2,10 +2,11 @@ import static org.springframework.security.oauth2.jwt.JwtClaimNames.ISS; import static org.springframework.security.oauth2.jwt.JwtClaimNames.SUB; -import static sopt.makers.authentication.support.code.support.failure.TokenFailure.INVALID_SUBJECT; +import static sopt.makers.authentication.support.code.support.failure.ResourceFailure.INVALID_SUBJECT; import static sopt.makers.authentication.support.code.support.failure.TokenFailure.TOKEN_EXPIRED; import static sopt.makers.authentication.support.code.support.failure.TokenFailure.UNSUPPORTED_ISSUER; +import sopt.makers.authentication.support.exception.support.ResourceException; import sopt.makers.authentication.support.exception.support.TokenException; import sopt.makers.authentication.support.security.authentication.CustomAuthentication; import sopt.makers.authentication.support.value.JwtProperty; @@ -73,7 +74,7 @@ private void validateIssuer(JwtProperty jwtProperty) { private void validateSubject() { String subject = jwt.getClaim(SUB); if (subject == null) { - throw new TokenException(INVALID_SUBJECT); + throw new ResourceException(INVALID_SUBJECT); } } } diff --git a/src/main/java/sopt/makers/authentication/support/security/filter/JwtAuthenticationFilter.java b/src/main/java/sopt/makers/authentication/support/security/filter/JwtAuthenticationFilter.java index 7868c76..62039b3 100644 --- a/src/main/java/sopt/makers/authentication/support/security/filter/JwtAuthenticationFilter.java +++ b/src/main/java/sopt/makers/authentication/support/security/filter/JwtAuthenticationFilter.java @@ -1,9 +1,11 @@ package sopt.makers.authentication.support.security.filter; +import sopt.makers.authentication.support.constant.JwtConstant; import sopt.makers.authentication.support.jwt.provider.JwtAuthAccessTokenProvider; import sopt.makers.authentication.support.security.authentication.CustomAuthentication; import java.io.IOException; +import java.util.Arrays; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; @@ -28,6 +30,7 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { protected void doFilterInternal( final HttpServletRequest request, final HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { + String authorizationToken = getAuthorizationToken(request); CustomAuthentication authentication = authTokenProvider.parse(authorizationToken); @@ -37,10 +40,27 @@ protected void doFilterInternal( filterChain.doFilter(request, response); } + @Override + public boolean shouldNotFilter(HttpServletRequest request) { + return isJwksRequest(request); + } + + /** + * @author 강현욱 @hyunw9 + * @return JwtToken Authorization 헤더에서 "Bearer "를 제거하여 토큰을 추출합니다. + */ private String getAuthorizationToken(final HttpServletRequest request) { String authorizationHeaderValue = request.getHeader(HttpHeaders.AUTHORIZATION).substring(HttpHeaders.AUTHORIZATION.length()); - String authorizationToken = authorizationHeaderValue.trim(); - return authorizationToken; + return authorizationHeaderValue.trim(); + } + + private static boolean isJwksRequest(HttpServletRequest request) { + boolean isCorrectUrl = request.getRequestURI().equals("/.well-known/jwks.json"); + boolean isCorrectHeader = + Arrays.stream(JwtConstant.SERVICE_NAMES) + .anyMatch(request.getHeader(HttpHeaders.SERVER)::contains); + + return isCorrectUrl && isCorrectHeader; } } diff --git a/src/main/java/sopt/makers/authentication/usecase/auth/port/in/JwksRetrieveUsecase.java b/src/main/java/sopt/makers/authentication/usecase/auth/port/in/JwksRetrieveUsecase.java new file mode 100644 index 0000000..3680a34 --- /dev/null +++ b/src/main/java/sopt/makers/authentication/usecase/auth/port/in/JwksRetrieveUsecase.java @@ -0,0 +1,8 @@ +package sopt.makers.authentication.usecase.auth.port.in; + +import com.nimbusds.jose.jwk.JWKSet; + +public interface JwksRetrieveUsecase { + + JWKSet retrievePublicKey(); +} diff --git a/src/main/java/sopt/makers/authentication/usecase/auth/service/JwksRetrieveService.java b/src/main/java/sopt/makers/authentication/usecase/auth/service/JwksRetrieveService.java new file mode 100644 index 0000000..f58f2cd --- /dev/null +++ b/src/main/java/sopt/makers/authentication/usecase/auth/service/JwksRetrieveService.java @@ -0,0 +1,32 @@ +package sopt.makers.authentication.usecase.auth.service; + +import static com.nimbusds.jose.JWSAlgorithm.RS512; +import static com.nimbusds.jose.jwk.KeyUse.SIGNATURE; + +import sopt.makers.authentication.support.jwt.RSAKeyManager; +import sopt.makers.authentication.usecase.auth.port.in.JwksRetrieveUsecase; + +import org.springframework.stereotype.Service; + +import com.nimbusds.jose.jwk.JWKSet; +import com.nimbusds.jose.jwk.RSAKey; + +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class JwksRetrieveService implements JwksRetrieveUsecase { + + private final RSAKeyManager rsaKeyManager; + + public JWKSet retrievePublicKey() { + + RSAKey jwk = + new RSAKey.Builder(rsaKeyManager.getPublicKey()) + .keyUse(SIGNATURE) + .algorithm(RS512) + .keyID("makers-auth-1") + .build(); + return new JWKSet(jwk); + } +} diff --git a/src/test/java/sopt/makers/authentication/jwt/JwtAccessTokenTest.java b/src/test/java/sopt/makers/authentication/jwt/JwtAccessTokenTest.java index dc99cff..24c5795 100644 --- a/src/test/java/sopt/makers/authentication/jwt/JwtAccessTokenTest.java +++ b/src/test/java/sopt/makers/authentication/jwt/JwtAccessTokenTest.java @@ -6,8 +6,11 @@ import sopt.makers.authentication.support.jwt.provider.JwtAuthAccessTokenProvider; import sopt.makers.authentication.support.security.authentication.CustomAuthentication; +import sopt.makers.authentication.usecase.auth.port.in.JwksRetrieveUsecase; import java.io.IOException; +import java.security.interfaces.RSAPublicKey; +import java.text.ParseException; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -20,6 +23,13 @@ import org.springframework.security.oauth2.jwt.JwtEncoder; import org.springframework.test.context.ActiveProfiles; +import com.nimbusds.jose.JOSEException; +import com.nimbusds.jose.JWSVerifier; +import com.nimbusds.jose.crypto.RSASSAVerifier; +import com.nimbusds.jose.jwk.JWKSet; +import com.nimbusds.jose.jwk.RSAKey; +import com.nimbusds.jwt.SignedJWT; + @SpringBootTest @ActiveProfiles("test") public class JwtAccessTokenTest { @@ -31,6 +41,8 @@ public class JwtAccessTokenTest { @Autowired private JwtDecoder jwtDecoder; + @Autowired private JwksRetrieveUsecase jwksRetrieveUsecase; + @Test @DisplayName("AccessToken 생성") void create_jwt_access_token() { @@ -105,4 +117,25 @@ void extract_prefix() { // then assertThat(pureToken).isEqualTo(expectedToken); } + + @Test + @DisplayName("JwtAuthService로 Jwk를 조회하고, Public key로 AccessToken 디코딩") + void decode_jwt_access_token_with_public_key() throws ParseException, JOSEException { + // Arrange + CustomAuthentication customAuthentication = new CustomAuthentication("test", "test"); + String accessTokenWithHeader = jwtAuthAccessTokenProvider.generate(customAuthentication); + String pureToken = extract(accessTokenWithHeader); + + SignedJWT signedJWT = SignedJWT.parse(pureToken); + JWKSet info = jwksRetrieveUsecase.retrievePublicKey(); + RSAKey rsaKey = (RSAKey) info.getKeyByKeyId("makers-auth-1"); + RSAPublicKey publicKey = rsaKey.toRSAPublicKey(); + + // Act + JWSVerifier verifier = new RSASSAVerifier(publicKey); + boolean isValid = signedJWT.verify(verifier); + + // Assert + assertThat(isValid).isTrue(); + } }