Skip to content

Commit 8043b8c

Browse files
committed
Allow customizing Authorization Server Metadata Response
Closes gh-878
1 parent 0994a1e commit 8043b8c

File tree

6 files changed

+247
-48
lines changed

6 files changed

+247
-48
lines changed

docs/src/docs/asciidoc/protocol-endpoints.adoc

+24-3
Original file line numberDiff line numberDiff line change
@@ -179,10 +179,31 @@ public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity h
179179
[[oauth2-authorization-server-metadata-endpoint]]
180180
== OAuth2 Authorization Server Metadata Endpoint
181181

182-
`OAuth2AuthorizationServerConfigurer` provides support for the https://datatracker.ietf.org/doc/html/rfc8414#section-3[OAuth2 Authorization Server Metadata endpoint].
182+
`OAuth2AuthorizationServerMetadataEndpointConfigurer` provides the ability to customize the https://datatracker.ietf.org/doc/html/rfc8414#section-3[OAuth2 Authorization Server Metadata endpoint].
183+
It defines an extension point that lets you customize the https://datatracker.ietf.org/doc/html/rfc8414#section-3.2[OAuth2 Authorization Server Metadata response].
183184

184-
`OAuth2AuthorizationServerConfigurer` configures the `OAuth2AuthorizationServerMetadataEndpointFilter` and registers it with the OAuth2 authorization server `SecurityFilterChain` `@Bean`.
185-
`OAuth2AuthorizationServerMetadataEndpointFilter` is the `Filter` that processes https://datatracker.ietf.org/doc/html/rfc8414#section-3.1[OAuth2 authorization server metadata requests] and returns the https://datatracker.ietf.org/doc/html/rfc8414#section-3.2[OAuth2AuthorizationServerMetadata response].
185+
`OAuth2AuthorizationServerMetadataEndpointConfigurer` provides the following configuration option:
186+
187+
[source,java]
188+
----
189+
@Bean
190+
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
191+
OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
192+
new OAuth2AuthorizationServerConfigurer();
193+
http.apply(authorizationServerConfigurer);
194+
195+
authorizationServerConfigurer
196+
.authorizationServerMetadataEndpoint(authorizationServerMetadataEndpoint ->
197+
authorizationServerMetadataEndpoint
198+
.authorizationServerMetadataCustomizer(authorizationServerMetadataCustomizer)); <1>
199+
200+
return http.build();
201+
}
202+
----
203+
<1> `authorizationServerMetadataCustomizer()`: The `Consumer` providing access to the `OAuth2AuthorizationServerMetadata.Builder` allowing the ability to customize the claims of the Authorization Server's configuration.
204+
205+
`OAuth2AuthorizationServerMetadataEndpointConfigurer` configures the `OAuth2AuthorizationServerMetadataEndpointFilter` and registers it with the OAuth2 authorization server `SecurityFilterChain` `@Bean`.
206+
`OAuth2AuthorizationServerMetadataEndpointFilter` is the `Filter` that returns the https://datatracker.ietf.org/doc/html/rfc8414#section-3.2[OAuth2AuthorizationServerMetadata response].
186207

187208
[[jwk-set-endpoint]]
188209
== JWK Set Endpoint

oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2AuthorizationServerConfigurer.java

+25-23
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@
3434
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
3535
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
3636
import org.springframework.security.oauth2.server.authorization.web.NimbusJwkSetEndpointFilter;
37-
import org.springframework.security.oauth2.server.authorization.web.OAuth2AuthorizationServerMetadataEndpointFilter;
3837
import org.springframework.security.web.authentication.HttpStatusEntryPoint;
3938
import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter;
4039
import org.springframework.security.web.context.SecurityContextHolderFilter;
@@ -54,6 +53,7 @@
5453
* @since 0.0.1
5554
* @see AbstractHttpConfigurer
5655
* @see OAuth2ClientAuthenticationConfigurer
56+
* @see OAuth2AuthorizationServerMetadataEndpointConfigurer
5757
* @see OAuth2AuthorizationEndpointConfigurer
5858
* @see OAuth2TokenEndpointConfigurer
5959
* @see OAuth2TokenIntrospectionEndpointConfigurer
@@ -63,22 +63,20 @@
6363
* @see OAuth2AuthorizationService
6464
* @see OAuth2AuthorizationConsentService
6565
* @see NimbusJwkSetEndpointFilter
66-
* @see OAuth2AuthorizationServerMetadataEndpointFilter
6766
*/
6867
public final class OAuth2AuthorizationServerConfigurer
6968
extends AbstractHttpConfigurer<OAuth2AuthorizationServerConfigurer, HttpSecurity> {
7069

7170
private final Map<Class<? extends AbstractOAuth2Configurer>, AbstractOAuth2Configurer> configurers = createConfigurers();
72-
private RequestMatcher jwkSetEndpointMatcher;
73-
private RequestMatcher authorizationServerMetadataEndpointMatcher;
7471
private final RequestMatcher endpointsMatcher = (request) ->
75-
getRequestMatcher(OAuth2AuthorizationEndpointConfigurer.class).matches(request) ||
76-
getRequestMatcher(OAuth2TokenEndpointConfigurer.class).matches(request) ||
77-
getRequestMatcher(OAuth2TokenIntrospectionEndpointConfigurer.class).matches(request) ||
78-
getRequestMatcher(OAuth2TokenRevocationEndpointConfigurer.class).matches(request) ||
79-
getRequestMatcher(OidcConfigurer.class).matches(request) ||
80-
this.jwkSetEndpointMatcher.matches(request) ||
81-
this.authorizationServerMetadataEndpointMatcher.matches(request);
72+
getRequestMatcher(OAuth2AuthorizationServerMetadataEndpointConfigurer.class).matches(request) ||
73+
getRequestMatcher(OAuth2AuthorizationEndpointConfigurer.class).matches(request) ||
74+
getRequestMatcher(OAuth2TokenEndpointConfigurer.class).matches(request) ||
75+
getRequestMatcher(OAuth2TokenIntrospectionEndpointConfigurer.class).matches(request) ||
76+
getRequestMatcher(OAuth2TokenRevocationEndpointConfigurer.class).matches(request) ||
77+
getRequestMatcher(OidcConfigurer.class).matches(request) ||
78+
this.jwkSetEndpointMatcher.matches(request);
79+
private RequestMatcher jwkSetEndpointMatcher;
8280

8381
/**
8482
* Sets the repository of registered clients.
@@ -152,6 +150,18 @@ public OAuth2AuthorizationServerConfigurer clientAuthentication(Customizer<OAuth
152150
return this;
153151
}
154152

153+
/**
154+
* Configures the OAuth 2.0 Authorization Server Metadata Endpoint.
155+
*
156+
* @param authorizationServerMetadataEndpointCustomizer the {@link Customizer} providing access to the {@link OAuth2AuthorizationServerMetadataEndpointConfigurer}
157+
* @return the {@link OAuth2AuthorizationServerConfigurer} for further configuration
158+
* @since 0.4.0
159+
*/
160+
public OAuth2AuthorizationServerConfigurer authorizationServerMetadataEndpoint(Customizer<OAuth2AuthorizationServerMetadataEndpointConfigurer> authorizationServerMetadataEndpointCustomizer) {
161+
authorizationServerMetadataEndpointCustomizer.customize(getConfigurer(OAuth2AuthorizationServerMetadataEndpointConfigurer.class));
162+
return this;
163+
}
164+
155165
/**
156166
* Configures the OAuth 2.0 Authorization Endpoint.
157167
*
@@ -222,7 +232,9 @@ public RequestMatcher getEndpointsMatcher() {
222232
public void init(HttpSecurity httpSecurity) {
223233
AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils.getAuthorizationServerSettings(httpSecurity);
224234
validateAuthorizationServerSettings(authorizationServerSettings);
225-
initEndpointMatchers(authorizationServerSettings);
235+
236+
this.jwkSetEndpointMatcher = new AntPathRequestMatcher(
237+
authorizationServerSettings.getJwkSetEndpoint(), HttpMethod.GET.name());
226238

227239
this.configurers.values().forEach(configurer -> configurer.init(httpSecurity));
228240

@@ -253,15 +265,12 @@ public void configure(HttpSecurity httpSecurity) {
253265
jwkSource, authorizationServerSettings.getJwkSetEndpoint());
254266
httpSecurity.addFilterBefore(postProcess(jwkSetEndpointFilter), AbstractPreAuthenticatedProcessingFilter.class);
255267
}
256-
257-
OAuth2AuthorizationServerMetadataEndpointFilter authorizationServerMetadataEndpointFilter =
258-
new OAuth2AuthorizationServerMetadataEndpointFilter();
259-
httpSecurity.addFilterBefore(postProcess(authorizationServerMetadataEndpointFilter), AbstractPreAuthenticatedProcessingFilter.class);
260268
}
261269

262270
private Map<Class<? extends AbstractOAuth2Configurer>, AbstractOAuth2Configurer> createConfigurers() {
263271
Map<Class<? extends AbstractOAuth2Configurer>, AbstractOAuth2Configurer> configurers = new LinkedHashMap<>();
264272
configurers.put(OAuth2ClientAuthenticationConfigurer.class, new OAuth2ClientAuthenticationConfigurer(this::postProcess));
273+
configurers.put(OAuth2AuthorizationServerMetadataEndpointConfigurer.class, new OAuth2AuthorizationServerMetadataEndpointConfigurer(this::postProcess));
265274
configurers.put(OAuth2AuthorizationEndpointConfigurer.class, new OAuth2AuthorizationEndpointConfigurer(this::postProcess));
266275
configurers.put(OAuth2TokenEndpointConfigurer.class, new OAuth2TokenEndpointConfigurer(this::postProcess));
267276
configurers.put(OAuth2TokenIntrospectionEndpointConfigurer.class, new OAuth2TokenIntrospectionEndpointConfigurer(this::postProcess));
@@ -279,13 +288,6 @@ private <T extends AbstractOAuth2Configurer> RequestMatcher getRequestMatcher(Cl
279288
return getConfigurer(configurerType).getRequestMatcher();
280289
}
281290

282-
private void initEndpointMatchers(AuthorizationServerSettings authorizationServerSettings) {
283-
this.jwkSetEndpointMatcher = new AntPathRequestMatcher(
284-
authorizationServerSettings.getJwkSetEndpoint(), HttpMethod.GET.name());
285-
this.authorizationServerMetadataEndpointMatcher = new AntPathRequestMatcher(
286-
"/.well-known/oauth-authorization-server", HttpMethod.GET.name());
287-
}
288-
289291
private static void validateAuthorizationServerSettings(AuthorizationServerSettings authorizationServerSettings) {
290292
if (authorizationServerSettings.getIssuer() != null) {
291293
URI issuerUri;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
/*
2+
* Copyright 2020-2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers;
17+
18+
import java.util.function.Consumer;
19+
20+
import org.springframework.http.HttpMethod;
21+
import org.springframework.security.config.annotation.ObjectPostProcessor;
22+
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
23+
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationServerMetadata;
24+
import org.springframework.security.oauth2.server.authorization.web.OAuth2AuthorizationServerMetadataEndpointFilter;
25+
import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter;
26+
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
27+
import org.springframework.security.web.util.matcher.RequestMatcher;
28+
29+
/**
30+
* Configurer for the OAuth 2.0 Authorization Server Metadata Endpoint.
31+
*
32+
* @author Joe Grandja
33+
* @since 0.4.0
34+
* @see OAuth2AuthorizationServerConfigurer#authorizationServerMetadataEndpoint
35+
* @see OAuth2AuthorizationServerMetadataEndpointFilter
36+
*/
37+
public final class OAuth2AuthorizationServerMetadataEndpointConfigurer extends AbstractOAuth2Configurer {
38+
private RequestMatcher requestMatcher;
39+
private Consumer<OAuth2AuthorizationServerMetadata.Builder> authorizationServerMetadataCustomizer;
40+
private Consumer<OAuth2AuthorizationServerMetadata.Builder> defaultAuthorizationServerMetadataCustomizer;
41+
42+
/**
43+
* Restrict for internal use only.
44+
*/
45+
OAuth2AuthorizationServerMetadataEndpointConfigurer(ObjectPostProcessor<Object> objectPostProcessor) {
46+
super(objectPostProcessor);
47+
}
48+
49+
/**
50+
* Sets the {@code Consumer} providing access to the {@link OAuth2AuthorizationServerMetadata.Builder}
51+
* allowing the ability to customize the claims of the Authorization Server's configuration.
52+
*
53+
* @param authorizationServerMetadataCustomizer the {@code Consumer} providing access to the {@link OAuth2AuthorizationServerMetadata.Builder}
54+
* @return the {@link OAuth2AuthorizationServerMetadataEndpointConfigurer} for further configuration
55+
*/
56+
public OAuth2AuthorizationServerMetadataEndpointConfigurer authorizationServerMetadataCustomizer(
57+
Consumer<OAuth2AuthorizationServerMetadata.Builder> authorizationServerMetadataCustomizer) {
58+
this.authorizationServerMetadataCustomizer = authorizationServerMetadataCustomizer;
59+
return this;
60+
}
61+
62+
void addDefaultAuthorizationServerMetadataCustomizer(
63+
Consumer<OAuth2AuthorizationServerMetadata.Builder> defaultAuthorizationServerMetadataCustomizer) {
64+
this.defaultAuthorizationServerMetadataCustomizer =
65+
this.defaultAuthorizationServerMetadataCustomizer == null ?
66+
defaultAuthorizationServerMetadataCustomizer :
67+
this.defaultAuthorizationServerMetadataCustomizer.andThen(defaultAuthorizationServerMetadataCustomizer);
68+
}
69+
70+
@Override
71+
void init(HttpSecurity httpSecurity) {
72+
this.requestMatcher = new AntPathRequestMatcher(
73+
"/.well-known/oauth-authorization-server", HttpMethod.GET.name());
74+
}
75+
76+
@Override
77+
void configure(HttpSecurity httpSecurity) {
78+
OAuth2AuthorizationServerMetadataEndpointFilter authorizationServerMetadataEndpointFilter =
79+
new OAuth2AuthorizationServerMetadataEndpointFilter();
80+
Consumer<OAuth2AuthorizationServerMetadata.Builder> authorizationServerMetadataCustomizer = getAuthorizationServerMetadataCustomizer();
81+
if (authorizationServerMetadataCustomizer != null) {
82+
authorizationServerMetadataEndpointFilter.setAuthorizationServerMetadataCustomizer(authorizationServerMetadataCustomizer);
83+
}
84+
httpSecurity.addFilterBefore(postProcess(authorizationServerMetadataEndpointFilter), AbstractPreAuthenticatedProcessingFilter.class);
85+
}
86+
87+
private Consumer<OAuth2AuthorizationServerMetadata.Builder> getAuthorizationServerMetadataCustomizer() {
88+
Consumer<OAuth2AuthorizationServerMetadata.Builder> authorizationServerMetadataCustomizer = null;
89+
if (this.defaultAuthorizationServerMetadataCustomizer != null || this.authorizationServerMetadataCustomizer != null) {
90+
if (this.defaultAuthorizationServerMetadataCustomizer != null) {
91+
authorizationServerMetadataCustomizer = this.defaultAuthorizationServerMetadataCustomizer;
92+
}
93+
if (this.authorizationServerMetadataCustomizer != null) {
94+
authorizationServerMetadataCustomizer =
95+
authorizationServerMetadataCustomizer == null ?
96+
this.authorizationServerMetadataCustomizer :
97+
authorizationServerMetadataCustomizer.andThen(this.authorizationServerMetadataCustomizer);
98+
}
99+
}
100+
return authorizationServerMetadataCustomizer;
101+
}
102+
103+
@Override
104+
RequestMatcher getRequestMatcher() {
105+
return this.requestMatcher;
106+
}
107+
108+
}

oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2AuthorizationServerMetadataEndpointFilter.java

+20-4
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,15 @@
3737
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
3838
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
3939
import org.springframework.security.web.util.matcher.RequestMatcher;
40+
import org.springframework.util.Assert;
4041
import org.springframework.web.filter.OncePerRequestFilter;
4142
import org.springframework.web.util.UriComponentsBuilder;
4243

4344
/**
4445
* A {@code Filter} that processes OAuth 2.0 Authorization Server Metadata Requests.
4546
*
4647
* @author Daniel Garnier-Moiroux
48+
* @author Joe Grandja
4749
* @since 0.1.1
4850
* @see OAuth2AuthorizationServerMetadata
4951
* @see AuthorizationServerSettings
@@ -60,6 +62,19 @@ public final class OAuth2AuthorizationServerMetadataEndpointFilter extends OnceP
6062
HttpMethod.GET.name());
6163
private final OAuth2AuthorizationServerMetadataHttpMessageConverter authorizationServerMetadataHttpMessageConverter =
6264
new OAuth2AuthorizationServerMetadataHttpMessageConverter();
65+
private Consumer<OAuth2AuthorizationServerMetadata.Builder> authorizationServerMetadataCustomizer = (authorizationServerMetadata) -> {};
66+
67+
/**
68+
* Sets the {@code Consumer} providing access to the {@link OAuth2AuthorizationServerMetadata.Builder}
69+
* allowing the ability to customize the claims of the Authorization Server's configuration.
70+
*
71+
* @param authorizationServerMetadataCustomizer the {@code Consumer} providing access to the {@link OAuth2AuthorizationServerMetadata.Builder}
72+
* @since 0.4.0
73+
*/
74+
public void setAuthorizationServerMetadataCustomizer(Consumer<OAuth2AuthorizationServerMetadata.Builder> authorizationServerMetadataCustomizer) {
75+
Assert.notNull(authorizationServerMetadataCustomizer, "authorizationServerMetadataCustomizer cannot be null");
76+
this.authorizationServerMetadataCustomizer = authorizationServerMetadataCustomizer;
77+
}
6378

6479
@Override
6580
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
@@ -74,7 +89,7 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse
7489
String issuer = authorizationServerContext.getIssuer();
7590
AuthorizationServerSettings authorizationServerSettings = authorizationServerContext.getAuthorizationServerSettings();
7691

77-
OAuth2AuthorizationServerMetadata authorizationServerMetadata = OAuth2AuthorizationServerMetadata.builder()
92+
OAuth2AuthorizationServerMetadata.Builder authorizationServerMetadata = OAuth2AuthorizationServerMetadata.builder()
7893
.issuer(issuer)
7994
.authorizationEndpoint(asUrl(issuer, authorizationServerSettings.getAuthorizationEndpoint()))
8095
.tokenEndpoint(asUrl(issuer, authorizationServerSettings.getTokenEndpoint()))
@@ -88,12 +103,13 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse
88103
.tokenRevocationEndpointAuthenticationMethods(clientAuthenticationMethods())
89104
.tokenIntrospectionEndpoint(asUrl(issuer, authorizationServerSettings.getTokenIntrospectionEndpoint()))
90105
.tokenIntrospectionEndpointAuthenticationMethods(clientAuthenticationMethods())
91-
.codeChallengeMethod("S256")
92-
.build();
106+
.codeChallengeMethod("S256");
107+
108+
this.authorizationServerMetadataCustomizer.accept(authorizationServerMetadata);
93109

94110
ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response);
95111
this.authorizationServerMetadataHttpMessageConverter.write(
96-
authorizationServerMetadata, MediaType.APPLICATION_JSON, httpResponse);
112+
authorizationServerMetadata.build(), MediaType.APPLICATION_JSON, httpResponse);
97113
}
98114

99115
private static Consumer<List<String>> clientAuthenticationMethods() {

0 commit comments

Comments
 (0)