mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-07-31 17:39:05 +00:00
ACS-6121 MNT-24007 Use issuer URI from the IdP (#2250)
This commit is contained in:
@@ -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();
|
||||
|
@@ -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();
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user