diff --git a/docs/src/docs/asciidoc/configuration-model.adoc b/docs/src/docs/asciidoc/configuration-model.adoc index 0f996bab..388c0203 100644 --- a/docs/src/docs/asciidoc/configuration-model.adoc +++ b/docs/src/docs/asciidoc/configuration-model.adoc @@ -253,3 +253,55 @@ The supported client authentication methods are `client_secret_basic`, `client_s * `*AuthenticationManager*` -- An `AuthenticationManager` composed of `JwtClientAssertionAuthenticationProvider`, `ClientSecretAuthenticationProvider`, and `PublicClientAuthenticationProvider`. * `*AuthenticationSuccessHandler*` -- An internal implementation that associates the "`authenticated`" `OAuth2ClientAuthenticationToken` (current `Authentication`) to the `SecurityContext`. * `*AuthenticationFailureHandler*` -- An internal implementation that uses the `OAuth2Error` associated with the `OAuth2AuthenticationException` to return the OAuth2 error response. + +[[configuring-client-authentication-customizing-jwt-client-assertion-validation]] +=== Customizing Jwt Client Assertion Validation + +`JwtClientAssertionDecoderFactory.DEFAULT_JWT_VALIDATOR_FACTORY` is the default factory that provides an `OAuth2TokenValidator` for the specified `RegisteredClient` and is used for validating the `iss`, `sub`, `aud`, `exp` and `nbf` claims of the `Jwt` client assertion. + +`JwtClientAssertionDecoderFactory` provides the ability to override the default `Jwt` client assertion validation by supplying a custom factory of type `Function>` to `setJwtValidatorFactory()`. + +[NOTE] +`JwtClientAssertionDecoderFactory` is the default `JwtDecoderFactory` used by `JwtClientAssertionAuthenticationProvider` that provides a `JwtDecoder` for the specified `RegisteredClient` and is used for authenticating a `Jwt` Bearer Token during OAuth2 client authentication. + +A common use case for customizing `JwtClientAssertionDecoderFactory` is to validate additional claims in the `Jwt` client assertion. + +The following example shows how to configure `JwtClientAssertionAuthenticationProvider` with a customized `JwtClientAssertionDecoderFactory` that validates an additional claim in the `Jwt` client assertion: + +[source,java] +---- +@Bean +public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { + OAuth2AuthorizationServerConfigurer authorizationServerConfigurer = + new OAuth2AuthorizationServerConfigurer(); + http.apply(authorizationServerConfigurer); + + authorizationServerConfigurer + .clientAuthentication(clientAuthentication -> + clientAuthentication + .authenticationProviders(configureJwtClientAssertionValidator()) + ); + + return http.build(); +} + +private Consumer> configureJwtClientAssertionValidator() { + return (authenticationProviders) -> + authenticationProviders.forEach((authenticationProvider) -> { + if (authenticationProvider instanceof JwtClientAssertionAuthenticationProvider) { + // Customize JwtClientAssertionDecoderFactory + JwtClientAssertionDecoderFactory jwtDecoderFactory = new JwtClientAssertionDecoderFactory(); + Function> jwtValidatorFactory = (registeredClient) -> + new DelegatingOAuth2TokenValidator<>( + // Use default validators + JwtClientAssertionDecoderFactory.DEFAULT_JWT_VALIDATOR_FACTORY.apply(registeredClient), + // Add custom validator + new JwtClaimValidator<>("claim", "value"::equals)); + jwtDecoderFactory.setJwtValidatorFactory(jwtValidatorFactory); + + ((JwtClientAssertionAuthenticationProvider) authenticationProvider) + .setJwtDecoderFactory(jwtDecoderFactory); + } + }); +} +---- diff --git a/docs/src/docs/asciidoc/protocol-endpoints.adoc b/docs/src/docs/asciidoc/protocol-endpoints.adoc index dc0cf2a9..83c2ea5e 100644 --- a/docs/src/docs/asciidoc/protocol-endpoints.adoc +++ b/docs/src/docs/asciidoc/protocol-endpoints.adoc @@ -50,6 +50,76 @@ public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity h * `*AuthenticationSuccessHandler*` -- An internal implementation that handles an "`authenticated`" `OAuth2AuthorizationCodeRequestAuthenticationToken` and returns the `OAuth2AuthorizationResponse`. * `*AuthenticationFailureHandler*` -- An internal implementation that uses the `OAuth2Error` associated with the `OAuth2AuthorizationCodeRequestAuthenticationException` and returns the `OAuth2Error` response. +[[oauth2-authorization-endpoint-customizing-authorization-request-validation]] +=== Customizing Authorization Request Validation + +`OAuth2AuthorizationCodeRequestAuthenticationValidator` is the default validator used for validating specific OAuth2 authorization request parameters used in the Authorization Code Grant. +The default implementation validates the `redirect_uri` and `scope` parameters. +If validation fails, an `OAuth2AuthorizationCodeRequestAuthenticationException` is thrown. + +`OAuth2AuthorizationCodeRequestAuthenticationProvider` provides the ability to override the default authorization request validation by supplying a custom authentication validator of type `Consumer` to `setAuthenticationValidator()`. + +[TIP] +`OAuth2AuthorizationCodeRequestAuthenticationContext` holds the `OAuth2AuthorizationCodeRequestAuthenticationToken`, which contains the OAuth2 authorization request parameters. + +[IMPORTANT] +If validation fails, the authentication validator *MUST* throw `OAuth2AuthorizationCodeRequestAuthenticationException`. + +A common use case during the development life cycle phase is to allow for `localhost` in the `redirect_uri` parameter. + +The following example shows how to configure `OAuth2AuthorizationCodeRequestAuthenticationProvider` with a custom authentication validator that allows for `localhost` in the `redirect_uri` parameter: + +[source,java] +---- +@Bean +public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { + OAuth2AuthorizationServerConfigurer authorizationServerConfigurer = + new OAuth2AuthorizationServerConfigurer(); + http.apply(authorizationServerConfigurer); + + authorizationServerConfigurer + .authorizationEndpoint(authorizationEndpoint -> + authorizationEndpoint + .authenticationProviders(configureAuthenticationValidator()) + ); + + return http.build(); +} + +private Consumer> configureAuthenticationValidator() { + return (authenticationProviders) -> + authenticationProviders.forEach((authenticationProvider) -> { + if (authenticationProvider instanceof OAuth2AuthorizationCodeRequestAuthenticationProvider) { + Consumer authenticationValidator = + // Override default redirect_uri validator + new CustomRedirectUriValidator() + // Reuse default scope validator + .andThen(OAuth2AuthorizationCodeRequestAuthenticationValidator.DEFAULT_SCOPE_VALIDATOR); + + ((OAuth2AuthorizationCodeRequestAuthenticationProvider) authenticationProvider) + .setAuthenticationValidator(authenticationValidator); + } + }); +} + +static class CustomRedirectUriValidator implements Consumer { + + @Override + public void accept(OAuth2AuthorizationCodeRequestAuthenticationContext authenticationContext) { + OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication = + authenticationContext.getAuthentication(); + RegisteredClient registeredClient = authenticationContext.getRegisteredClient(); + String requestedRedirectUri = authorizationCodeRequestAuthentication.getRedirectUri(); + + // Use exact string matching when comparing client redirect URIs against pre-registered URIs + if (!registeredClient.getRedirectUris().contains(requestedRedirectUri)) { + OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.INVALID_REQUEST); + throw new OAuth2AuthorizationCodeRequestAuthenticationException(error, null); + } + } +} +---- + [[oauth2-token-endpoint]] == OAuth2 Token Endpoint diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2AuthorizationCodeGrantTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2AuthorizationCodeGrantTests.java index ce789997..3e75b7e9 100644 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2AuthorizationCodeGrantTests.java +++ b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2AuthorizationCodeGrantTests.java @@ -943,11 +943,6 @@ public class OAuth2AuthorizationCodeGrantTests { @EnableWebSecurity @Configuration(proxyBeanMethods = false) static class AuthorizationServerConfigurationCustomConsentRequest extends AuthorizationServerConfiguration { - @Autowired - private RegisteredClientRepository registeredClientRepository; - - @Autowired - private OAuth2AuthorizationService authorizationService; @Autowired private OAuth2AuthorizationConsentService authorizationConsentService;