From 3d29ae8163589fcbc582f021a1924ba8648101c4 Mon Sep 17 00:00:00 2001 From: hyunw9 Date: Tue, 26 Nov 2024 23:45:30 +0900 Subject: [PATCH 01/10] =?UTF-8?q?[FEAT]=20Jwks=20retrieve=20Controller=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/auth/api/AuthKeyApi.java | 8 ++++++ .../auth/api/AuthKeyApiController.java | 25 +++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 src/main/java/sopt/makers/authentication/application/auth/api/AuthKeyApi.java create mode 100644 src/main/java/sopt/makers/authentication/application/auth/api/AuthKeyApiController.java 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 00000000..b778e3f2 --- /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 00000000..5a326bc8 --- /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()); + } +} From 2e31bad8189a73eb581cd1e42d71daf6c0324237 Mon Sep 17 00:00:00 2001 From: hyunw9 Date: Tue, 26 Nov 2024 23:46:20 +0900 Subject: [PATCH 02/10] =?UTF-8?q?[FEAT]=20RSA=20Key=20config=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC=20&=20KeyManager=20=EC=B6=94=EC=83=81=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../config/JwtRSAKeyConfiguration.java | 98 +--------------- .../support/config/LocalRSARSAKeyManager.java | 108 ++++++++++++++++++ .../support/jwt/RSAKeyManager.java | 11 ++ 3 files changed, 124 insertions(+), 93 deletions(-) create mode 100644 src/main/java/sopt/makers/authentication/support/config/LocalRSARSAKeyManager.java create mode 100644 src/main/java/sopt/makers/authentication/support/jwt/RSAKeyManager.java 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 b4c9cfbc..74de01f5 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/LocalRSARSAKeyManager.java b/src/main/java/sopt/makers/authentication/support/config/LocalRSARSAKeyManager.java new file mode 100644 index 00000000..7ab1b86f --- /dev/null +++ b/src/main/java/sopt/makers/authentication/support/config/LocalRSARSAKeyManager.java @@ -0,0 +1,108 @@ +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.exception.support.TokenException; +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.context.annotation.Configuration; +import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceLoader; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Configuration +@EnableConfigurationProperties(JwtProperty.class) +@RequiredArgsConstructor +@Slf4j +public class LocalRSARSAKeyManager 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 TokenException(INVALID_LOCATION); + } catch (NoSuchAlgorithmException e) { + throw new TokenException(INVALID_ALGORITHM); + } catch (InvalidKeySpecException e) { + throw new TokenException(INVALID_SUBJECT); + } + } + + @Override + public RSAPrivateKey getPrivateKey() { + 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 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 parsePublicKey(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); + } +} 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 00000000..a00d61f4 --- /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(); +} From 64c2c60361e87b3ee7b624c3286ae3116079dfaa Mon Sep 17 00:00:00 2001 From: hyunw9 Date: Tue, 26 Nov 2024 23:47:04 +0900 Subject: [PATCH 03/10] =?UTF-8?q?[FEAT]=20=ED=95=84=ED=84=B0=EC=99=80=20?= =?UTF-8?q?=ED=97=A4=EB=8D=94=20=EA=B2=80=EC=A6=9D=EC=9A=A9=20=EC=83=81?= =?UTF-8?q?=EC=88=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../support/constant/JwtConstant.java | 1 + .../filter/JwtAuthenticationFilter.java | 20 +++++++++++++++++-- 2 files changed, 19 insertions(+), 2 deletions(-) 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 f21f2e65..392300ff 100644 --- a/src/main/java/sopt/makers/authentication/support/constant/JwtConstant.java +++ b/src/main/java/sopt/makers/authentication/support/constant/JwtConstant.java @@ -3,4 +3,5 @@ public class JwtConstant { public static final String TOKEN_HEADER = "Bearer "; + public static final String[] serviceNames = {"playground", "crew", "app", "admin"}; } 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 7868c76d..b2aebbd4 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,23 @@ protected void doFilterInternal( filterChain.doFilter(request, response); } + @Override + public boolean shouldNotFilter(HttpServletRequest request) { + return isJwksRequest(request); + } + 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.serviceNames) + .anyMatch(request.getHeader(HttpHeaders.SERVER)::contains); + + return isCorrectUrl && isCorrectHeader; } } From bac24a74539cc1fc91a6164a41f5c88fddd2a00c Mon Sep 17 00:00:00 2001 From: hyunw9 Date: Tue, 26 Nov 2024 23:47:42 +0900 Subject: [PATCH 04/10] =?UTF-8?q?[FEAT]=20Jwks=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20&=20=EB=8B=A8=EC=9C=84=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/port/in/JwksRetrieveUsecase.java | 8 +++++ .../auth/service/JwksRetrieveService.java | 31 +++++++++++++++++ .../jwt/JwtAccessTokenTest.java | 33 +++++++++++++++++++ 3 files changed, 72 insertions(+) create mode 100644 src/main/java/sopt/makers/authentication/usecase/auth/port/in/JwksRetrieveUsecase.java create mode 100644 src/main/java/sopt/makers/authentication/usecase/auth/service/JwksRetrieveService.java 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 00000000..3680a346 --- /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 00000000..db0efbee --- /dev/null +++ b/src/main/java/sopt/makers/authentication/usecase/auth/service/JwksRetrieveService.java @@ -0,0 +1,31 @@ +package sopt.makers.authentication.usecase.auth.service; + +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.JWSAlgorithm; +import com.nimbusds.jose.jwk.JWKSet; +import com.nimbusds.jose.jwk.KeyUse; +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(KeyUse.SIGNATURE) + .algorithm(JWSAlgorithm.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 dc99cff4..24c57953 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(); + } } From f74d009a84524478c1a6bea215cff2e581e5bb3b Mon Sep 17 00:00:00 2001 From: hyunw9 Date: Tue, 26 Nov 2024 23:54:14 +0900 Subject: [PATCH 05/10] =?UTF-8?q?[CHORE]=20=EC=9D=B4=EB=A6=84=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 --- .../{LocalRSARSAKeyManager.java => LocalRSAKeyManager.java} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename src/main/java/sopt/makers/authentication/support/config/{LocalRSARSAKeyManager.java => LocalRSAKeyManager.java} (98%) diff --git a/src/main/java/sopt/makers/authentication/support/config/LocalRSARSAKeyManager.java b/src/main/java/sopt/makers/authentication/support/config/LocalRSAKeyManager.java similarity index 98% rename from src/main/java/sopt/makers/authentication/support/config/LocalRSARSAKeyManager.java rename to src/main/java/sopt/makers/authentication/support/config/LocalRSAKeyManager.java index 7ab1b86f..accf1947 100644 --- a/src/main/java/sopt/makers/authentication/support/config/LocalRSARSAKeyManager.java +++ b/src/main/java/sopt/makers/authentication/support/config/LocalRSAKeyManager.java @@ -33,7 +33,7 @@ @EnableConfigurationProperties(JwtProperty.class) @RequiredArgsConstructor @Slf4j -public class LocalRSARSAKeyManager implements RSAKeyManager { +public class LocalRSAKeyManager implements RSAKeyManager { private final JwtProperty jwtProperty; private final ResourceLoader resourceLoader; From 5b554a54f9d8ac23601c1977f8da4cd7a7d56a59 Mon Sep 17 00:00:00 2001 From: hyunw9 Date: Sun, 1 Dec 2024 15:10:25 +0900 Subject: [PATCH 06/10] =?UTF-8?q?[FIX]=20Token=20/=20Resource=20Exception?= =?UTF-8?q?=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../code/support/failure/ResourceFailure.java | 21 +++++++++++++++++++ .../code/support/failure/TokenFailure.java | 3 --- .../exception/support/ResourceException.java | 11 ++++++++++ 3 files changed, 32 insertions(+), 3 deletions(-) create mode 100644 src/main/java/sopt/makers/authentication/support/code/support/failure/ResourceFailure.java create mode 100644 src/main/java/sopt/makers/authentication/support/exception/support/ResourceException.java 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 00000000..2869b622 --- /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 30d9159a..9649d5b3 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/exception/support/ResourceException.java b/src/main/java/sopt/makers/authentication/support/exception/support/ResourceException.java new file mode 100644 index 00000000..8c37d594 --- /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); + } +} From de20591fc9834bb8924893b6a38545e4567987c1 Mon Sep 17 00:00:00 2001 From: hyunw9 Date: Sun, 1 Dec 2024 15:10:46 +0900 Subject: [PATCH 07/10] =?UTF-8?q?[FIX]=20@Component,=20final=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../support/config/LocalRSAKeyManager.java | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/main/java/sopt/makers/authentication/support/config/LocalRSAKeyManager.java b/src/main/java/sopt/makers/authentication/support/config/LocalRSAKeyManager.java index accf1947..26780adf 100644 --- a/src/main/java/sopt/makers/authentication/support/config/LocalRSAKeyManager.java +++ b/src/main/java/sopt/makers/authentication/support/config/LocalRSAKeyManager.java @@ -1,10 +1,10 @@ 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 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.TokenException; +import sopt.makers.authentication.support.exception.support.ResourceException; import sopt.makers.authentication.support.jwt.RSAKeyManager; import sopt.makers.authentication.support.value.JwtProperty; @@ -22,14 +22,14 @@ 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.Configuration; 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; -@Configuration +@Component @EnableConfigurationProperties(JwtProperty.class) @RequiredArgsConstructor @Slf4j @@ -45,11 +45,11 @@ public RSAPublicKey getPublicKey() { PemObject pemObject = readPublicPemFile(resource); return parsePublicKey(pemObject); } catch (IOException e) { - throw new TokenException(INVALID_LOCATION); + throw new ResourceException(INVALID_LOCATION); } catch (NoSuchAlgorithmException e) { - throw new TokenException(INVALID_ALGORITHM); + throw new ResourceException(INVALID_ALGORITHM); } catch (InvalidKeySpecException e) { - throw new TokenException(INVALID_SUBJECT); + throw new ResourceException(INVALID_SUBJECT); } } @@ -60,11 +60,11 @@ public RSAPrivateKey getPrivateKey() { PemObject pemObject = readPrivatePemFile(resource); return generatePrivateKey(pemObject); } catch (IOException e) { - throw new TokenException(INVALID_LOCATION); + throw new ResourceException(INVALID_LOCATION); } catch (NoSuchAlgorithmException e) { - throw new TokenException(INVALID_ALGORITHM); + throw new ResourceException(INVALID_ALGORITHM); } catch (InvalidKeySpecException e) { - throw new TokenException(INVALID_SUBJECT); + throw new ResourceException(INVALID_SUBJECT); } } @@ -72,14 +72,14 @@ private Resource loadPublicKeyResource() { return resourceLoader.getResource(jwtProperty.secret().rsa().publicKey()); } - private PemObject readPublicPemFile(Resource resource) throws IOException { + 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(PemObject pemObject) + private RSAPublicKey parsePublicKey(final PemObject pemObject) throws NoSuchAlgorithmException, InvalidKeySpecException { byte[] publicKeyBytes = pemObject.getContent(); X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKeyBytes); @@ -91,14 +91,14 @@ private Resource loadPrivateKeyResource() { return resourceLoader.getResource(jwtProperty.secret().rsa().privateKey()); } - private PemObject readPrivatePemFile(Resource resource) throws IOException { + 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(PemObject pemObject) + private RSAPrivateKey generatePrivateKey(final PemObject pemObject) throws NoSuchAlgorithmException, InvalidKeySpecException { byte[] privateKeyBytes = pemObject.getContent(); PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKeyBytes); From 587771cdf13f622b01782cc6165b40a7a0a8890c Mon Sep 17 00:00:00 2001 From: hyunw9 Date: Sun, 1 Dec 2024 15:11:45 +0900 Subject: [PATCH 08/10] =?UTF-8?q?[FIX]=20=EB=B3=80=EC=88=98=EB=AA=85=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20&=20static=20import=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../authentication/support/constant/JwtConstant.java | 2 +- .../authentication/support/jwt/token/JwtAccessToken.java | 5 +++-- .../support/security/filter/JwtAuthenticationFilter.java | 5 ++++- .../usecase/auth/service/JwksRetrieveService.java | 9 +++++---- 4 files changed, 13 insertions(+), 8 deletions(-) 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 392300ff..a9ba4c53 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,5 @@ public class JwtConstant { public static final String TOKEN_HEADER = "Bearer "; - public static final String[] serviceNames = {"playground", "crew", "app", "admin"}; + public static final String[] SERVICE_NAMES = {"playground", "crew", "app", "admin"}; } 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 d2c2b115..6c698c12 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 b2aebbd4..66cc7885 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 @@ -45,6 +45,9 @@ public boolean shouldNotFilter(HttpServletRequest request) { return isJwksRequest(request); } + /* + * Authorization 헤더에서 "Bearer "를 제거하여 토큰을 추출합니다. + */ private String getAuthorizationToken(final HttpServletRequest request) { String authorizationHeaderValue = request.getHeader(HttpHeaders.AUTHORIZATION).substring(HttpHeaders.AUTHORIZATION.length()); @@ -54,7 +57,7 @@ private String getAuthorizationToken(final HttpServletRequest request) { private static boolean isJwksRequest(HttpServletRequest request) { boolean isCorrectUrl = request.getRequestURI().equals("/.well-known/jwks.json"); boolean isCorrectHeader = - Arrays.stream(JwtConstant.serviceNames) + 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/service/JwksRetrieveService.java b/src/main/java/sopt/makers/authentication/usecase/auth/service/JwksRetrieveService.java index db0efbee..f58f2cd7 100644 --- a/src/main/java/sopt/makers/authentication/usecase/auth/service/JwksRetrieveService.java +++ b/src/main/java/sopt/makers/authentication/usecase/auth/service/JwksRetrieveService.java @@ -1,13 +1,14 @@ 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.JWSAlgorithm; import com.nimbusds.jose.jwk.JWKSet; -import com.nimbusds.jose.jwk.KeyUse; import com.nimbusds.jose.jwk.RSAKey; import lombok.RequiredArgsConstructor; @@ -22,8 +23,8 @@ public JWKSet retrievePublicKey() { RSAKey jwk = new RSAKey.Builder(rsaKeyManager.getPublicKey()) - .keyUse(KeyUse.SIGNATURE) - .algorithm(JWSAlgorithm.RS512) + .keyUse(SIGNATURE) + .algorithm(RS512) .keyID("makers-auth-1") .build(); return new JWKSet(jwk); From 883a636cd6891b3b9abfb131aadfac1bd8e1b73f Mon Sep 17 00:00:00 2001 From: hyunw9 Date: Sun, 1 Dec 2024 15:21:32 +0900 Subject: [PATCH 09/10] =?UTF-8?q?[CHORE]=20=EB=A9=94=EC=84=9C=EB=93=9C=20?= =?UTF-8?q?=EC=A3=BC=EC=84=9D=20JavaDoc=20=EC=8A=A4=ED=83=80=EC=9D=BC?= =?UTF-8?q?=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../support/security/filter/JwtAuthenticationFilter.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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 66cc7885..62039b31 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 @@ -45,8 +45,9 @@ public boolean shouldNotFilter(HttpServletRequest request) { return isJwksRequest(request); } - /* - * Authorization 헤더에서 "Bearer "를 제거하여 토큰을 추출합니다. + /** + * @author 강현욱 @hyunw9 + * @return JwtToken Authorization 헤더에서 "Bearer "를 제거하여 토큰을 추출합니다. */ private String getAuthorizationToken(final HttpServletRequest request) { String authorizationHeaderValue = From 802b8837c6e5c305f864016e00e25a4950ec9b49 Mon Sep 17 00:00:00 2001 From: hyunw9 Date: Sun, 8 Dec 2024 00:04:51 +0900 Subject: [PATCH 10/10] [CHORE] Spotless Apply --- .../sopt/makers/authentication/support/constant/JwtConstant.java | 1 - 1 file changed, 1 deletion(-) 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 a1c0dd70..82a0c759 100644 --- a/src/main/java/sopt/makers/authentication/support/constant/JwtConstant.java +++ b/src/main/java/sopt/makers/authentication/support/constant/JwtConstant.java @@ -5,5 +5,4 @@ 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"; - }