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.proc.SecurityContext;
|
||||||
import com.nimbusds.jose.util.ResourceRetriever;
|
import com.nimbusds.jose.util.ResourceRetriever;
|
||||||
import com.nimbusds.jwt.proc.ConfigurableJWTProcessor;
|
import com.nimbusds.jwt.proc.ConfigurableJWTProcessor;
|
||||||
|
import com.nimbusds.oauth2.sdk.id.Issuer;
|
||||||
import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata;
|
import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata;
|
||||||
|
|
||||||
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacade.IdentityServiceFacadeException;
|
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.DelegatingOAuth2TokenValidator;
|
||||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||||
import org.springframework.security.oauth2.core.OAuth2Error;
|
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.OAuth2TokenValidator;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult;
|
||||||
import org.springframework.security.oauth2.core.converter.ClaimTypeConverter;
|
import org.springframework.security.oauth2.core.converter.ClaimTypeConverter;
|
||||||
import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter;
|
import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter;
|
||||||
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
|
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.JwtClaimNames;
|
||||||
import org.springframework.security.oauth2.jwt.JwtClaimValidator;
|
import org.springframework.security.oauth2.jwt.JwtClaimValidator;
|
||||||
import org.springframework.security.oauth2.jwt.JwtDecoder;
|
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.JwtTimestampValidator;
|
||||||
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
|
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
|
||||||
import org.springframework.web.client.RestOperations;
|
import org.springframework.web.client.RestOperations;
|
||||||
@@ -375,12 +377,18 @@ public class IdentityServiceFacadeFactoryBean implements FactoryBean<IdentitySer
|
|||||||
.map(OIDCProviderMetadata::getAuthorizationEndpointURI)
|
.map(OIDCProviderMetadata::getAuthorizationEndpointURI)
|
||||||
.map(URI::toASCIIString)
|
.map(URI::toASCIIString)
|
||||||
.orElse(null);
|
.orElse(null);
|
||||||
|
|
||||||
|
final String issuerUri = Optional.of(metadata)
|
||||||
|
.map(OIDCProviderMetadata::getIssuer)
|
||||||
|
.map(Issuer::getValue)
|
||||||
|
.orElseGet(config::getIssuerUrl);
|
||||||
|
|
||||||
return ClientRegistration
|
return ClientRegistration
|
||||||
.withRegistrationId("ids")
|
.withRegistrationId("ids")
|
||||||
.authorizationUri(authUri)
|
.authorizationUri(authUri)
|
||||||
.tokenUri(metadata.getTokenEndpointURI().toASCIIString())
|
.tokenUri(metadata.getTokenEndpointURI().toASCIIString())
|
||||||
.jwkSetUri(metadata.getJWKSetURI().toASCIIString())
|
.jwkSetUri(metadata.getJWKSetURI().toASCIIString())
|
||||||
.issuerUri(config.getIssuerUrl())
|
.issuerUri(issuerUri)
|
||||||
.authorizationGrantType(AuthorizationGrantType.PASSWORD);
|
.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)
|
private static boolean isDefined(String value)
|
||||||
{
|
{
|
||||||
return value != null && !value.isBlank();
|
return value != null && !value.isBlank();
|
||||||
|
@@ -31,15 +31,20 @@ import static org.mockito.Mockito.mock;
|
|||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
import java.util.Map;
|
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.JwtDecoderProvider;
|
||||||
|
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacadeFactoryBean.JwtIssuerValidator;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.springframework.security.oauth2.client.registration.ClientRegistration.ProviderDetails;
|
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.Jwt;
|
||||||
import org.springframework.security.oauth2.jwt.JwtDecoder;
|
import org.springframework.security.oauth2.jwt.JwtDecoder;
|
||||||
|
|
||||||
public class IdentityServiceFacadeFactoryBeanTest
|
public class IdentityServiceFacadeFactoryBeanTest
|
||||||
{
|
{
|
||||||
|
private static final String EXPECTED_ISSUER = "expected-issuer";
|
||||||
@Test
|
@Test
|
||||||
public void shouldCreateJwtDecoderWithoutIDSWhenPublicKeyIsProvided()
|
public void shouldCreateJwtDecoderWithoutIDSWhenPublicKeyIsProvided()
|
||||||
{
|
{
|
||||||
@@ -62,4 +67,53 @@ public class IdentityServiceFacadeFactoryBeanTest
|
|||||||
.containsEntry(USERNAME_CLAIM, "piotrek");
|
.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