|
@@ -0,0 +1,436 @@
|
|
|
+/*
|
|
|
+ * Copyright 2020-2021 the original author or authors.
|
|
|
+ *
|
|
|
+ * Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
+ * you may not use this file except in compliance with the License.
|
|
|
+ * You may obtain a copy of the License at
|
|
|
+ *
|
|
|
+ * https://www.apache.org/licenses/LICENSE-2.0
|
|
|
+ *
|
|
|
+ * Unless required by applicable law or agreed to in writing, software
|
|
|
+ * distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
+ * See the License for the specific language governing permissions and
|
|
|
+ * limitations under the License.
|
|
|
+ */
|
|
|
+package org.springframework.security.oauth2.server.authorization.authentication;
|
|
|
+
|
|
|
+import com.nimbusds.jose.JOSEException;
|
|
|
+import com.nimbusds.jose.JWSAlgorithm;
|
|
|
+import com.nimbusds.jose.JWSHeader;
|
|
|
+import com.nimbusds.jose.JWSSigner;
|
|
|
+import com.nimbusds.jose.crypto.MACSigner;
|
|
|
+import com.nimbusds.jose.crypto.RSASSASigner;
|
|
|
+import com.nimbusds.jose.jwk.JWK;
|
|
|
+import com.nimbusds.jose.jwk.KeyUse;
|
|
|
+import com.nimbusds.jose.jwk.RSAKey;
|
|
|
+import com.nimbusds.jwt.JWTClaimsSet;
|
|
|
+import com.nimbusds.jwt.SignedJWT;
|
|
|
+import okhttp3.mockwebserver.MockResponse;
|
|
|
+import okhttp3.mockwebserver.MockWebServer;
|
|
|
+import org.junit.Before;
|
|
|
+import org.junit.Test;
|
|
|
+import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
|
|
|
+import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
|
|
+import org.springframework.security.oauth2.jose.jws.MacAlgorithm;
|
|
|
+import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
|
|
|
+import org.springframework.security.oauth2.jwt.JwtDecoder;
|
|
|
+import org.springframework.security.oauth2.jwt.JwtDecoderFactory;
|
|
|
+import org.springframework.security.oauth2.jwt.JwtException;
|
|
|
+import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
|
|
|
+import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
|
|
+import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
|
|
|
+import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
|
|
|
+import org.springframework.security.oauth2.server.authorization.config.ClientSettings;
|
|
|
+import org.springframework.test.util.ReflectionTestUtils;
|
|
|
+
|
|
|
+import java.nio.charset.StandardCharsets;
|
|
|
+import java.security.KeyPair;
|
|
|
+import java.security.KeyPairGenerator;
|
|
|
+import java.security.interfaces.RSAPublicKey;
|
|
|
+import java.time.Instant;
|
|
|
+import java.util.Date;
|
|
|
+import java.util.UUID;
|
|
|
+
|
|
|
+import static org.assertj.core.api.Assertions.assertThat;
|
|
|
+import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
|
|
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
|
|
+import static org.mockito.Mockito.mock;
|
|
|
+
|
|
|
+/**
|
|
|
+ * Tests for {@link OAuth2ClientAuthenticationProvider.RegisteredClientJwtAssertionDecoderFactory}
|
|
|
+ *
|
|
|
+ * @author Rafal Lewczuk
|
|
|
+ */
|
|
|
+public class RegisteredClientJwtAssertionDecoderFactoryTests {
|
|
|
+
|
|
|
+ private JwtDecoderFactory<RegisteredClient> registeredClientDecoderFactory;
|
|
|
+
|
|
|
+ @Before
|
|
|
+ public void setUp() {
|
|
|
+ OAuth2ClientAuthenticationProvider authenticationProvider = new OAuth2ClientAuthenticationProvider(
|
|
|
+ mock(RegisteredClientRepository.class), mock(OAuth2AuthorizationService.class));
|
|
|
+ this.registeredClientDecoderFactory = (JwtDecoderFactory<RegisteredClient>)
|
|
|
+ ReflectionTestUtils.getField(authenticationProvider, "jwtDecoderFactory");
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ public void createDecoderWhenRegisteredClientNullThenThrowIllegalArgumentException() {
|
|
|
+ assertThatIllegalArgumentException()
|
|
|
+ .isThrownBy(() -> registeredClientDecoderFactory.createDecoder(null))
|
|
|
+ .withMessage("registeredClient cannot be null");
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ public void createDecoderWhenClientAuthenticationMethodNotSupportedThenThrowOAuth2AuthenticationException() {
|
|
|
+ RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
|
|
+
|
|
|
+ assertThatThrownBy(() -> this.registeredClientDecoderFactory.createDecoder(registeredClient))
|
|
|
+ .isInstanceOf(OAuth2AuthenticationException.class);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ public void createDecoderWithClientSecretJwtWhenClientSecretNullThenThrowOAuth2Exception() {
|
|
|
+ RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
|
|
|
+ .clientSecret(null)
|
|
|
+ .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_JWT)
|
|
|
+ .clientSettings(ClientSettings.builder().tokenEndpointSigningAlgorithm(MacAlgorithm.HS256).build())
|
|
|
+ .build();
|
|
|
+
|
|
|
+ assertThatThrownBy(() -> this.registeredClientDecoderFactory.createDecoder(registeredClient))
|
|
|
+ .isInstanceOf(OAuth2AuthenticationException.class);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ public void createDecoderWithClientSecretJwtClientThenReturnDecoder() {
|
|
|
+ RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
|
|
|
+ .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_JWT)
|
|
|
+ .clientSecret("0123456789abcdef0123456789ABCDEF")
|
|
|
+ .clientSettings(ClientSettings.builder().tokenEndpointSigningAlgorithm(MacAlgorithm.HS256).build())
|
|
|
+ .build();
|
|
|
+
|
|
|
+ JwtDecoder jwtDecoder = this.registeredClientDecoderFactory.createDecoder(registeredClient);
|
|
|
+
|
|
|
+ assertThat(jwtDecoder).isNotNull();
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ public void createDecoderWithClientSecretJwtTwiceThenReturnCachedDecoder() {
|
|
|
+ RegisteredClient.Builder registeredClientBuilder = TestRegisteredClients.registeredClient()
|
|
|
+ .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_JWT)
|
|
|
+ .clientSecret("0123456789abcdef0123456789ABCDEF")
|
|
|
+ .clientSettings(ClientSettings.builder().tokenEndpointSigningAlgorithm(MacAlgorithm.HS256).build());
|
|
|
+
|
|
|
+ JwtDecoder decoder1 = this.registeredClientDecoderFactory.createDecoder(registeredClientBuilder.build());
|
|
|
+ JwtDecoder decoder2 = this.registeredClientDecoderFactory.createDecoder(registeredClientBuilder.build());
|
|
|
+
|
|
|
+ assertThat(decoder1).isNotNull();
|
|
|
+ assertThat(decoder2).isSameAs(decoder1);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ public void createDecoderWithClientSecretJwtAndSecondWithChangedAlgorithmThenReturnRecreatedDecoder() {
|
|
|
+ RegisteredClient.Builder registeredClientBuilder = TestRegisteredClients.registeredClient()
|
|
|
+ .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_JWT)
|
|
|
+ .clientSecret("0123456789abcdef0123456789ABCDEF");
|
|
|
+ RegisteredClient registeredClient1 = registeredClientBuilder.clientSettings(
|
|
|
+ ClientSettings.builder().tokenEndpointSigningAlgorithm(MacAlgorithm.HS256).build()).build();
|
|
|
+ RegisteredClient registeredClient = registeredClientBuilder.clientSettings(
|
|
|
+ ClientSettings.builder().tokenEndpointSigningAlgorithm(MacAlgorithm.HS512).build()).build();
|
|
|
+
|
|
|
+ JwtDecoder decoder1 = this.registeredClientDecoderFactory.createDecoder(registeredClient1);
|
|
|
+ JwtDecoder decoder2 = this.registeredClientDecoderFactory.createDecoder(registeredClient);
|
|
|
+
|
|
|
+ assertThat(decoder1).isNotNull();
|
|
|
+ assertThat(decoder2).isNotNull();
|
|
|
+ assertThat(decoder1).isNotSameAs(decoder2);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ public void createDecoderWithClientSecretJwtAndSecondWithChangedSecretThenReturnRecreatedDecoder() {
|
|
|
+ RegisteredClient.Builder registeredClientBuilder = TestRegisteredClients.registeredClient()
|
|
|
+ .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_JWT)
|
|
|
+ .clientSettings(ClientSettings.builder().tokenEndpointSigningAlgorithm(MacAlgorithm.HS256).build());
|
|
|
+ RegisteredClient registeredClient1 = registeredClientBuilder.clientSecret("0123456789abcdef0123456789ABCDEF").build();
|
|
|
+ RegisteredClient registeredClient2 = registeredClientBuilder.clientSecret("0123456789ABCDEF0123456789abcdef").build();
|
|
|
+
|
|
|
+ JwtDecoder decoder1 = this.registeredClientDecoderFactory.createDecoder(registeredClient1);
|
|
|
+ JwtDecoder decoder2 = this.registeredClientDecoderFactory.createDecoder(registeredClient2);
|
|
|
+
|
|
|
+ assertThat(decoder1).isNotNull();
|
|
|
+ assertThat(decoder2).isNotNull();
|
|
|
+ assertThat(decoder1).isNotSameAs(decoder2);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ public void createDecoderWithPrivateKeyJwtMissingJwksUrlThenThrowOAuth2AuthenticationException() {
|
|
|
+ RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
|
|
|
+ .clientAuthenticationMethod(ClientAuthenticationMethod.PRIVATE_KEY_JWT)
|
|
|
+ .clientSettings(ClientSettings.builder()
|
|
|
+ .tokenEndpointSigningAlgorithm(SignatureAlgorithm.RS256).build())
|
|
|
+ .build();
|
|
|
+
|
|
|
+ assertThatThrownBy(() -> this.registeredClientDecoderFactory.createDecoder(registeredClient))
|
|
|
+ .isInstanceOf(OAuth2AuthenticationException.class);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ public void createDecoderWithPrivateKeyJwtThenReturnDecoder() {
|
|
|
+ RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
|
|
|
+ .clientAuthenticationMethod(ClientAuthenticationMethod.PRIVATE_KEY_JWT)
|
|
|
+ .clientSettings(ClientSettings.builder()
|
|
|
+ .tokenEndpointSigningAlgorithm(SignatureAlgorithm.RS256).jwkSetUrl("https://client.example.com/jwks").build())
|
|
|
+ .build();
|
|
|
+
|
|
|
+ JwtDecoder jwtDecoder = this.registeredClientDecoderFactory.createDecoder(registeredClient);
|
|
|
+
|
|
|
+ assertThat(jwtDecoder).isNotNull();
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ public void createDecoderWithPrivateKeyJwtAndSecondWithChangedAlgorithmThenReturnRecreatedDecoder() {
|
|
|
+ RegisteredClient.Builder registeredClientBuilder = TestRegisteredClients.registeredClient()
|
|
|
+ .clientAuthenticationMethod(ClientAuthenticationMethod.PRIVATE_KEY_JWT);
|
|
|
+
|
|
|
+ RegisteredClient registeredClient1 = registeredClientBuilder.clientSettings(
|
|
|
+ ClientSettings.builder().tokenEndpointSigningAlgorithm(SignatureAlgorithm.RS256)
|
|
|
+ .jwkSetUrl("https://keysite.com/jwks").build()).build();
|
|
|
+ RegisteredClient registeredClient2 = registeredClientBuilder.clientSettings(
|
|
|
+ ClientSettings.builder().tokenEndpointSigningAlgorithm(SignatureAlgorithm.RS512)
|
|
|
+ .jwkSetUrl("https://keysite.com/jwks").build()).build();
|
|
|
+
|
|
|
+ JwtDecoder decoder1 = this.registeredClientDecoderFactory.createDecoder(registeredClient1);
|
|
|
+ JwtDecoder decoder2 = this.registeredClientDecoderFactory.createDecoder(registeredClient2);
|
|
|
+
|
|
|
+ assertThat(decoder1).isNotNull();
|
|
|
+ assertThat(decoder2).isNotNull();
|
|
|
+ assertThat(decoder1).isNotSameAs(decoder2);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ public void createDecoderWithPrivateKeyJwtAndSecondWithChangedJwksUrlThenReturnRecreatedDecoder() {
|
|
|
+ RegisteredClient.Builder registeredClientBuilder = TestRegisteredClients.registeredClient()
|
|
|
+ .clientAuthenticationMethod(ClientAuthenticationMethod.PRIVATE_KEY_JWT);
|
|
|
+
|
|
|
+ RegisteredClient client1 = registeredClientBuilder.clientSettings(
|
|
|
+ ClientSettings.builder().tokenEndpointSigningAlgorithm(SignatureAlgorithm.RS256)
|
|
|
+ .jwkSetUrl("https://keysite1.com/jwks").build()).build();
|
|
|
+ RegisteredClient client2 = registeredClientBuilder.clientSettings(
|
|
|
+ ClientSettings.builder().tokenEndpointSigningAlgorithm(SignatureAlgorithm.RS256)
|
|
|
+ .jwkSetUrl("https://keysite2.com/jwks").build()).build();
|
|
|
+
|
|
|
+ OAuth2ClientAuthenticationToken token = new OAuth2ClientAuthenticationToken(
|
|
|
+ "https://auth-server/oauth2/token", "client-1", ClientAuthenticationMethod.CLIENT_SECRET_JWT, "jwt", null);
|
|
|
+
|
|
|
+ JwtDecoder decoder1 = this.registeredClientDecoderFactory.createDecoder(client1);
|
|
|
+ JwtDecoder decoder2 = this.registeredClientDecoderFactory.createDecoder(client2);
|
|
|
+
|
|
|
+ assertThat(decoder1).isNotNull();
|
|
|
+ assertThat(decoder2).isNotNull();
|
|
|
+ assertThat(decoder1).isNotSameAs(decoder2);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ public void createDecoderWithPrivateKeyJwtNullAlgorithmThenReturnDefaultRS256Decoder() {
|
|
|
+ RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
|
|
|
+ .clientAuthenticationMethod(ClientAuthenticationMethod.PRIVATE_KEY_JWT)
|
|
|
+ .clientSettings(
|
|
|
+ ClientSettings.builder()
|
|
|
+ .jwkSetUrl("https://keysite1.com/jwks")
|
|
|
+ .build())
|
|
|
+ .build();
|
|
|
+
|
|
|
+ JwtDecoder decoder = this.registeredClientDecoderFactory.createDecoder(registeredClient);
|
|
|
+ assertThat(decoder).isNotNull();
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ public void validateClientSecretJwtTokenWhenValidThenReturnJwtObject() throws Exception {
|
|
|
+ RegisteredClient registeredClient = defaultRegisteredClient();
|
|
|
+ JwtDecoder jwtDecoder = this.registeredClientDecoderFactory.createDecoder(registeredClient);
|
|
|
+ String clientJwtAssertion = clientSecretJwtAssertion(registeredClient,
|
|
|
+ new JWTClaimsSet.Builder()
|
|
|
+ .issuer(registeredClient.getClientId())
|
|
|
+ .subject(registeredClient.getClientId())
|
|
|
+ .expirationTime(Date.from(Instant.now()))
|
|
|
+ .build());
|
|
|
+
|
|
|
+ assertThat(jwtDecoder.decode(clientJwtAssertion)).isNotNull();
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ public void validateClientSecretJwtTokenWhenBadIssuerThenThrowJwtException() throws Exception {
|
|
|
+ RegisteredClient registeredClient = defaultRegisteredClient();
|
|
|
+ JwtDecoder jwtDecoder = this.registeredClientDecoderFactory.createDecoder(registeredClient);
|
|
|
+ String clientJwtAssertion = clientSecretJwtAssertion(registeredClient,
|
|
|
+ new JWTClaimsSet.Builder()
|
|
|
+ .issuer("bad-issuer")
|
|
|
+ .subject(registeredClient.getClientId())
|
|
|
+ .expirationTime(Date.from(Instant.now()))
|
|
|
+ .build());
|
|
|
+
|
|
|
+ assertThatThrownBy(() -> jwtDecoder.decode(clientJwtAssertion))
|
|
|
+ .isInstanceOf(JwtException.class)
|
|
|
+ .extracting("message")
|
|
|
+ .matches(s -> s.toString().contains("The iss claim is not valid"));
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ public void validateClientSecretJwtTokenWhenBadSubjectThenThrowJwtException() throws Exception {
|
|
|
+ RegisteredClient registeredClient = defaultRegisteredClient();
|
|
|
+ JwtDecoder jwtDecoder = this.registeredClientDecoderFactory.createDecoder(registeredClient);
|
|
|
+ String clientJwtAssertion = clientSecretJwtAssertion(registeredClient,
|
|
|
+ new JWTClaimsSet.Builder()
|
|
|
+ .issuer(registeredClient.getClientId())
|
|
|
+ .subject("bad-client")
|
|
|
+ .expirationTime(Date.from(Instant.now()))
|
|
|
+ .build());
|
|
|
+
|
|
|
+ assertThatThrownBy(() -> jwtDecoder.decode(clientJwtAssertion))
|
|
|
+ .isInstanceOf(JwtException.class)
|
|
|
+ .extracting("message")
|
|
|
+ .matches(s -> s.toString().contains("The sub claim is not valid"));
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ public void validateClientSecretJwtTokenWhenNoExpClaimThenThrowJwtException() throws Exception {
|
|
|
+ RegisteredClient registeredClient = defaultRegisteredClient();
|
|
|
+ JwtDecoder jwtDecoder = this.registeredClientDecoderFactory.createDecoder(registeredClient);
|
|
|
+ String clientJwtAssertion = clientSecretJwtAssertion(registeredClient,
|
|
|
+ new JWTClaimsSet.Builder()
|
|
|
+ .subject(registeredClient.getClientId())
|
|
|
+ .issuer(registeredClient.getClientId())
|
|
|
+ .build());
|
|
|
+
|
|
|
+ assertThatThrownBy(() -> jwtDecoder.decode(clientJwtAssertion))
|
|
|
+ .isInstanceOf(JwtException.class)
|
|
|
+ .extracting("message")
|
|
|
+ .matches(s -> s.toString().contains("The exp claim is not valid"));
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ public void validateClientSecretJwtTokenWhenExpiredThenThrowJwtException() throws Exception {
|
|
|
+ RegisteredClient registeredClient = defaultRegisteredClient();
|
|
|
+ JwtDecoder jwtDecoder = this.registeredClientDecoderFactory.createDecoder(registeredClient);
|
|
|
+ String clientJwtAssertion = clientSecretJwtAssertion(registeredClient,
|
|
|
+ new JWTClaimsSet.Builder()
|
|
|
+ .subject(registeredClient.getClientId())
|
|
|
+ .issuer(registeredClient.getClientId())
|
|
|
+ .expirationTime(Date.from(Instant.now().minusSeconds(240)))
|
|
|
+ .build());
|
|
|
+
|
|
|
+ assertThatThrownBy(() -> jwtDecoder.decode(clientJwtAssertion))
|
|
|
+ .isInstanceOf(JwtException.class)
|
|
|
+ .extracting("message")
|
|
|
+ .matches(s -> s.toString().contains("Jwt expired at"));
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ public void validateClientSecretJwtTokenWhenExpiredWithinSkewThenReturnJwtObject() throws Exception {
|
|
|
+ RegisteredClient registeredClient = defaultRegisteredClient();
|
|
|
+ JwtDecoder jwtDecoder = this.registeredClientDecoderFactory.createDecoder(registeredClient);
|
|
|
+ String clientJwtAssertion = clientSecretJwtAssertion(registeredClient,
|
|
|
+ new JWTClaimsSet.Builder()
|
|
|
+ .subject(registeredClient.getClientId())
|
|
|
+ .issuer(registeredClient.getClientId())
|
|
|
+ .expirationTime(Date.from(Instant.now().minusSeconds(30)))
|
|
|
+ .build());
|
|
|
+
|
|
|
+ assertThat(jwtDecoder.decode(clientJwtAssertion)).isNotNull();
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ public void validateClientSecretJwtTokenWhenInvalidNbfThenThrowJwtException() throws Exception {
|
|
|
+ RegisteredClient registeredClient = defaultRegisteredClient();
|
|
|
+ JwtDecoder jwtDecoder = this.registeredClientDecoderFactory.createDecoder(registeredClient);
|
|
|
+ String clientJwtAssertion = clientSecretJwtAssertion(registeredClient,
|
|
|
+ new JWTClaimsSet.Builder()
|
|
|
+ .subject(registeredClient.getClientId())
|
|
|
+ .issuer(registeredClient.getClientId())
|
|
|
+ .expirationTime(Date.from(Instant.now()))
|
|
|
+ .notBeforeTime(Date.from(Instant.now().plusSeconds(90)))
|
|
|
+ .build());
|
|
|
+
|
|
|
+ assertThatThrownBy(() -> jwtDecoder.decode(clientJwtAssertion))
|
|
|
+ .isInstanceOf(JwtException.class)
|
|
|
+ .extracting("message")
|
|
|
+ .matches(s -> s.toString().contains("Jwt used before"));
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ public void validateClientSecretJwtTokenWhenInvalidIatThenThrowJwtException() throws Exception {
|
|
|
+ RegisteredClient registeredClient = defaultRegisteredClient();
|
|
|
+ JwtDecoder jwtDecoder = this.registeredClientDecoderFactory.createDecoder(registeredClient);
|
|
|
+ String clientJwtAssertion = clientSecretJwtAssertion(registeredClient,
|
|
|
+ new JWTClaimsSet.Builder()
|
|
|
+ .subject(registeredClient.getClientId())
|
|
|
+ .issuer(registeredClient.getClientId())
|
|
|
+ .expirationTime(Date.from(Instant.now()))
|
|
|
+ .issueTime(Date.from(Instant.now().plusSeconds(90)))
|
|
|
+ .build());
|
|
|
+
|
|
|
+ assertThatThrownBy(() -> jwtDecoder.decode(clientJwtAssertion))
|
|
|
+ .isInstanceOf(JwtException.class)
|
|
|
+ .extracting("message")
|
|
|
+ .matches(s -> s.toString().contains("expiresAt must be after issuedAt"));
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ public void validatePrivateKeyJwtTokenWhenValidThenReturnJwtObject() throws Exception {
|
|
|
+ KeyPairGenerator gen = KeyPairGenerator.getInstance("RSA");
|
|
|
+ gen.initialize(2048);
|
|
|
+ KeyPair keyPair = gen.generateKeyPair();
|
|
|
+
|
|
|
+ JWK jwk = new RSAKey.Builder((RSAPublicKey) keyPair.getPublic())
|
|
|
+ .keyUse(KeyUse.SIGNATURE)
|
|
|
+ .keyID(UUID.randomUUID().toString())
|
|
|
+ .build();
|
|
|
+
|
|
|
+ String jwks = "{\"keys\":[" + jwk.toJSONString() + "]}";
|
|
|
+
|
|
|
+ try (MockWebServer server = new MockWebServer()) {
|
|
|
+ String jwkSetUrl = server.url("/.well-known/jwks.json").toString();
|
|
|
+ server.enqueue(new MockResponse().setBody(jwks));
|
|
|
+
|
|
|
+ RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
|
|
|
+ .clientAuthenticationMethod(ClientAuthenticationMethod.PRIVATE_KEY_JWT)
|
|
|
+ .clientSettings(ClientSettings.builder()
|
|
|
+ .tokenEndpointSigningAlgorithm(SignatureAlgorithm.RS256)
|
|
|
+ .jwkSetUrl(jwkSetUrl).build())
|
|
|
+ .build();
|
|
|
+
|
|
|
+ JwtDecoder jwtDecoder = this.registeredClientDecoderFactory.createDecoder(registeredClient);
|
|
|
+
|
|
|
+ JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
|
|
|
+ .issuer(registeredClient.getClientId())
|
|
|
+ .subject(registeredClient.getClientId())
|
|
|
+ .expirationTime(Date.from(Instant.now()))
|
|
|
+ .build();
|
|
|
+ SignedJWT signedJWT = new SignedJWT(new JWSHeader(JWSAlgorithm.RS256), claimsSet);
|
|
|
+ JWSSigner signer = new RSASSASigner(keyPair.getPrivate());
|
|
|
+ signedJWT.sign(signer);
|
|
|
+ String clientJwtAssertion = signedJWT.serialize();
|
|
|
+
|
|
|
+ assertThat(jwtDecoder.decode(clientJwtAssertion)).isNotNull();
|
|
|
+
|
|
|
+ server.shutdown();
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ private RegisteredClient defaultRegisteredClient() {
|
|
|
+ return TestRegisteredClients.registeredClient()
|
|
|
+ .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_JWT)
|
|
|
+ .clientSecret("0123456789abcdef0123456789ABCDEF")
|
|
|
+ .clientSettings(ClientSettings.builder().tokenEndpointSigningAlgorithm(MacAlgorithm.HS256).build())
|
|
|
+ .build();
|
|
|
+ }
|
|
|
+
|
|
|
+ private String clientSecretJwtAssertion(RegisteredClient registeredClient, JWTClaimsSet claimsSet) throws JOSEException {
|
|
|
+ SignedJWT signedJWT = new SignedJWT(new JWSHeader(JWSAlgorithm.HS256), claimsSet);
|
|
|
+ JWSSigner signer = new MACSigner(registeredClient.getClientSecret().getBytes(StandardCharsets.UTF_8));
|
|
|
+ signedJWT.sign(signer);
|
|
|
+ String clientJwtAssertion = signedJWT.serialize();
|
|
|
+ return clientJwtAssertion;
|
|
|
+ }
|
|
|
+}
|