ACS-6121 MNT-24007 Use issuer URI from the IdP (#2250)

This commit is contained in:
Piotr Żurek
2023-10-13 14:48:35 +02:00
committed by GitHub
parent 53c99a0ba4
commit 582fc8ec2d
2 changed files with 92 additions and 2 deletions

View File

@@ -57,6 +57,7 @@ import com.nimbusds.jose.proc.JWSVerificationKeySelector;
import com.nimbusds.jose.proc.SecurityContext;
import com.nimbusds.jose.util.ResourceRetriever;
import com.nimbusds.jwt.proc.ConfigurableJWTProcessor;
import com.nimbusds.oauth2.sdk.id.Issuer;
import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata;
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacade.IdentityServiceFacadeException;
@@ -91,7 +92,9 @@ import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
import org.springframework.security.oauth2.core.OAuth2TokenValidator;
import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult;
import org.springframework.security.oauth2.core.converter.ClaimTypeConverter;
import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter;
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
@@ -99,7 +102,6 @@ import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtClaimNames;
import org.springframework.security.oauth2.jwt.JwtClaimValidator;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.JwtIssuerValidator;
import org.springframework.security.oauth2.jwt.JwtTimestampValidator;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
import org.springframework.web.client.RestOperations;
@@ -375,12 +377,18 @@ public class IdentityServiceFacadeFactoryBean implements FactoryBean<IdentitySer
.map(OIDCProviderMetadata::getAuthorizationEndpointURI)
.map(URI::toASCIIString)
.orElse(null);
final String issuerUri = Optional.of(metadata)
.map(OIDCProviderMetadata::getIssuer)
.map(Issuer::getValue)
.orElseGet(config::getIssuerUrl);
return ClientRegistration
.withRegistrationId("ids")
.authorizationUri(authUri)
.tokenUri(metadata.getTokenEndpointURI().toASCIIString())
.jwkSetUri(metadata.getJWKSetURI().toASCIIString())
.issuerUri(config.getIssuerUrl())
.issuerUri(issuerUri)
.authorizationGrantType(AuthorizationGrantType.PASSWORD);
}
@@ -565,6 +573,34 @@ public class IdentityServiceFacadeFactoryBean implements FactoryBean<IdentitySer
}
}
static class JwtIssuerValidator implements OAuth2TokenValidator<Jwt>
{
private final String requiredIssuer;
public JwtIssuerValidator(String issuer)
{
this.requiredIssuer = requireNonNull(issuer, "issuer cannot be null");
}
@Override
public OAuth2TokenValidatorResult validate(Jwt token)
{
requireNonNull(token, "token cannot be null");
final Object issuer = token.getClaim(JwtClaimNames.ISS);
if (issuer != null && requiredIssuer.equals(issuer.toString()))
{
return OAuth2TokenValidatorResult.success();
}
final OAuth2Error error = new OAuth2Error(
OAuth2ErrorCodes.INVALID_TOKEN,
"The iss claim is not valid. Expected `%s` but got `%s`.".formatted(requiredIssuer, issuer),
"https://tools.ietf.org/html/rfc6750#section-3.1");
return OAuth2TokenValidatorResult.failure(error);
}
}
private static boolean isDefined(String value)
{
return value != null && !value.isBlank();

View File

@@ -31,15 +31,20 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.util.Map;
import java.util.UUID;
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacadeFactoryBean.JwtDecoderProvider;
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacadeFactoryBean.JwtIssuerValidator;
import org.junit.Test;
import org.springframework.security.oauth2.client.registration.ClientRegistration.ProviderDetails;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtDecoder;
public class IdentityServiceFacadeFactoryBeanTest
{
private static final String EXPECTED_ISSUER = "expected-issuer";
@Test
public void shouldCreateJwtDecoderWithoutIDSWhenPublicKeyIsProvided()
{
@@ -62,4 +67,53 @@ public class IdentityServiceFacadeFactoryBeanTest
.containsEntry(USERNAME_CLAIM, "piotrek");
}
@Test
public void shouldFailWithNotMatchingIssuerURIs()
{
final JwtIssuerValidator issuerValidator = new JwtIssuerValidator(EXPECTED_ISSUER);
final OAuth2TokenValidatorResult validationResult = issuerValidator.validate(tokenWithIssuer("different-issuer"));
assertThat(validationResult).isNotNull();
assertThat(validationResult.hasErrors()).isTrue();
assertThat(validationResult.getErrors()).hasSize(1);
final OAuth2Error error = validationResult.getErrors().iterator().next();
assertThat(error).isNotNull();
assertThat(error.getDescription()).contains(EXPECTED_ISSUER, "different-issuer");
}
@Test
public void shouldFailWithNullIssuerURI()
{
final JwtIssuerValidator issuerValidator = new JwtIssuerValidator(EXPECTED_ISSUER);
final OAuth2TokenValidatorResult validationResult = issuerValidator.validate(tokenWithIssuer(null));
assertThat(validationResult).isNotNull();
assertThat(validationResult.hasErrors()).isTrue();
assertThat(validationResult.getErrors()).hasSize(1);
final OAuth2Error error = validationResult.getErrors().iterator().next();
assertThat(error).isNotNull();
assertThat(error.getDescription()).contains(EXPECTED_ISSUER, "null");
}
@Test
public void shouldSucceedWithMatchingIssuerURI()
{
final JwtIssuerValidator issuerValidator = new JwtIssuerValidator(EXPECTED_ISSUER);
final OAuth2TokenValidatorResult validationResult = issuerValidator.validate(tokenWithIssuer(EXPECTED_ISSUER));
assertThat(validationResult).isNotNull();
assertThat(validationResult.hasErrors()).isFalse();
assertThat(validationResult.getErrors()).isEmpty();
}
private Jwt tokenWithIssuer(String issuer)
{
return Jwt.withTokenValue(UUID.randomUUID().toString())
.issuer(issuer)
.header("JUST", "FOR TESTING")
.build();
}
}