diff --git a/.gitignore b/.gitignore index 9c1927f7..e474c447 100644 --- a/.gitignore +++ b/.gitignore @@ -53,7 +53,6 @@ buildNumber.properties .idea/**/usage.statistics.xml .idea/**/dictionaries .idea/**/shelf -.vscode # AWS User-specific .idea/**/aws.xml diff --git a/.vscode/.gitignore b/.vscode/.gitignore new file mode 100644 index 00000000..d6b7ef32 --- /dev/null +++ b/.vscode/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/geom-service/geom-service-open/src/test/java/io/github/xezzon/geom/call/SubscriptionCallHttpTest.java b/geom-service/geom-service-open/src/test/java/io/github/xezzon/geom/call/SubscriptionCallHttpTest.java index 3812dca9..32b2ec7b 100644 --- a/geom-service/geom-service-open/src/test/java/io/github/xezzon/geom/call/SubscriptionCallHttpTest.java +++ b/geom-service/geom-service-open/src/test/java/io/github/xezzon/geom/call/SubscriptionCallHttpTest.java @@ -185,6 +185,32 @@ void validate_incorrectSecretKey() throws NoSuchAlgorithmException, InvalidKeyEx .expectHeader().valueEquals(ERROR_CODE_HEADER, OpenErrorCode.INVALID_ACCESS_KEY.code()); } + @Test + void validate_mismatchSignature() throws NoSuchAlgorithmException, InvalidKeyException { + final String rawBody = "{\"id\":\"1234567890\"}"; + long timestamp = Instant.now().toEpochMilli(); + Tuple3 dataset = this.initData(); + Mac mac = Mac.getInstance(GeomOpenRequestBuilder.DIGEST_ALGORITHM); + byte[] secretKey = Base64.getDecoder().decode(RandomUtil.randomString(8)); + mac.init(new SecretKeySpec(secretKey, GeomOpenRequestBuilder.DIGEST_ALGORITHM)); + mac.update("tampered message".getBytes()); + String signature = Base64.getEncoder().encodeToString(mac.doFinal()); + + webTestClient.post() + .uri(builder -> builder + .path("/subscription-call/validate") + .queryParam("path", dataset.getT2().getOpenapiCode()) + .build() + ) + .header(GeomOpenRequestBuilder.ACCESS_KEY_HEADER, RandomUtil.randomString(8)) + .header(GeomOpenRequestBuilder.TIMESTAMP_HEADER, String.valueOf(timestamp)) + .header(GeomOpenRequestBuilder.SIGNATURE_HEADER, signature) + .bodyValue(rawBody) + .exchange() + .expectStatus().isForbidden() + .expectHeader().valueEquals(ERROR_CODE_HEADER, OpenErrorCode.INVALID_ACCESS_KEY.code()); + } + @Test void validate_timeout() throws NoSuchAlgorithmException, InvalidKeyException { final String rawBody = "{\"id\":\"1234567890\"}"; diff --git a/geom-spring-boot-starter/src/main/java/io/github/xezzon/geom/auth/TestJwtGenerator.java b/geom-spring-boot-starter/src/main/java/io/github/xezzon/geom/auth/TestJwtGenerator.java index 05c5e986..a2f06f07 100644 --- a/geom-spring-boot-starter/src/main/java/io/github/xezzon/geom/auth/TestJwtGenerator.java +++ b/geom-spring-boot-starter/src/main/java/io/github/xezzon/geom/auth/TestJwtGenerator.java @@ -16,6 +16,8 @@ import java.time.temporal.ChronoUnit; import java.util.Base64; import java.util.UUID; +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; import org.jetbrains.annotations.TestOnly; /** @@ -27,6 +29,7 @@ public class TestJwtGenerator { private static final Base64.Encoder ENCODER = Base64.getEncoder(); private static final ECPrivateKey PRIVATE_KEY; private static final ECPublicKey PUBLIC_KEY; + private static final SecretKey SECRET_KEY; static { try { @@ -34,6 +37,9 @@ public class TestJwtGenerator { KeyPair keyPair = keyPairGenerator.generateKeyPair(); PRIVATE_KEY = (ECPrivateKey) keyPair.getPrivate(); PUBLIC_KEY = (ECPublicKey) keyPair.getPublic(); + KeyGenerator keyGenerator = KeyGenerator.getInstance("AES"); + keyGenerator.init(256); + SECRET_KEY = keyGenerator.generateKey(); } catch (NoSuchAlgorithmException e) { throw new GeomRuntimeException(ErrorCode.UNKNOWN, e); } @@ -64,7 +70,25 @@ public static String generateJwt(String userId) { return new JwtAuth(PRIVATE_KEY).sign(jwtBuilder); } + public static String generateJwt4App(String appId) { + JwtClaim claim = JwtClaim.newBuilder() + .setSubject(appId) + .setPreferredUsername(RandomUtil.randomString(8)) + .setNickname(RandomUtil.randomString(8)) + .build(); + Builder builder = new JwtClaimWrapper(claim) + .into() + .withIssuedAt(Instant.now()) + .withExpiresAt(Instant.now().plus(1, ChronoUnit.HOURS)) + .withJWTId(UUID.randomUUID().toString()); + return new JwtAuth(SECRET_KEY.getEncoded()).sign(builder); + } + public static String getPublicKey() { return ENCODER.encodeToString(PUBLIC_KEY.getEncoded()); } + + public static String getSecretKey() { + return ENCODER.encodeToString(SECRET_KEY.getEncoded()); + } } diff --git a/geom-spring-boot-starter/src/test/java/io/github/xezzon/geom/auth/JwtAuthTest.java b/geom-spring-boot-starter/src/test/java/io/github/xezzon/geom/auth/JwtAuthTest.java index 45163550..947882a5 100644 --- a/geom-spring-boot-starter/src/test/java/io/github/xezzon/geom/auth/JwtAuthTest.java +++ b/geom-spring-boot-starter/src/test/java/io/github/xezzon/geom/auth/JwtAuthTest.java @@ -2,6 +2,7 @@ import static com.google.auth.http.AuthHttpConstants.AUTHORIZATION; import static com.google.auth.http.AuthHttpConstants.BEARER; +import static io.github.xezzon.geom.auth.JwtFilter.ACCESS_KEY_HEADER; import static io.github.xezzon.geom.auth.JwtFilter.PUBLIC_KEY_HEADER; import static io.github.xezzon.geom.common.exception.GlobalExceptionHandler.ERROR_CODE_HEADER; @@ -62,4 +63,21 @@ void notLogin() { Assertions.assertEquals(errorCode.name(), responseBody.error().getCode()); Assertions.assertEquals(errorCode.message(), responseBody.error().getMessage()); } + + @Test + void accessKey() { + String appId = UUID.randomUUID().toString(); + String encodedJwt = TestJwtGenerator.generateJwt4App(appId); + String username = JWT.decode(encodedJwt).getClaim(JwtClaimWrapper.USERNAME_CLAIM).asString(); + String bearer = BEARER + " " + encodedJwt; + String responseBody = webTestClient.get() + .uri("/jwt") + .header(ACCESS_KEY_HEADER, TestJwtGenerator.getSecretKey()) + .header(AUTHORIZATION, bearer) + .exchange() + .expectStatus().isOk() + .expectBody(String.class) + .returnResult().getResponseBody(); + Assertions.assertEquals(username, responseBody); + } }