Skip to content

Commit 1d888d5

Browse files
committed
Remember user consent and make consent page configurable
1 parent 2d8d568 commit 1d888d5

File tree

18 files changed

+1812
-41
lines changed

18 files changed

+1812
-41
lines changed

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

+58-1
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,11 @@
3535
import org.springframework.security.oauth2.jwt.JwtEncoder;
3636
import org.springframework.security.oauth2.jwt.NimbusJwsEncoder;
3737
import org.springframework.security.oauth2.server.authorization.InMemoryOAuth2AuthorizationService;
38+
import org.springframework.security.oauth2.server.authorization.InMemoryOAuth2AuthorizationConsentService;
3839
import org.springframework.security.oauth2.server.authorization.JwtEncodingContext;
3940
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
4041
import org.springframework.security.oauth2.server.authorization.OAuth2TokenCustomizer;
42+
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService;
4143
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeAuthenticationProvider;
4244
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationProvider;
4345
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientCredentialsAuthenticationProvider;
@@ -90,6 +92,7 @@ public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBui
9092
this.tokenRevocationEndpointMatcher.matches(request) ||
9193
this.jwkSetEndpointMatcher.matches(request) ||
9294
this.oidcProviderConfigurationEndpointMatcher.matches(request);
95+
private String consentPage = null;
9396

9497
/**
9598
* Sets the repository of registered clients.
@@ -127,6 +130,43 @@ public OAuth2AuthorizationServerConfigurer<B> providerSettings(ProviderSettings
127130
return this;
128131
}
129132

133+
/**
134+
* Specify the URL to redirect {@code Resource Owners} to if consent is required during
135+
* the {@code authorization_code} flow. A default consent page will be generated when
136+
* this attribute is not specified.
137+
*
138+
* If a URL is specified, users are required to process the specified URL to generate
139+
* a consent page. The query string will contain the following parameters:
140+
*
141+
* <ul>
142+
* <li>{@code client_id} the client identifier</li>
143+
* <li>{@code scope} the space separated list of scopes present in the authorization request</li>
144+
* <li>{@code state} a CSRF protection token</li>
145+
* </ul>
146+
*
147+
* In general, the consent page should create a form that submits
148+
* a request with the following requirements:
149+
*
150+
* <ul>
151+
* <li>It must be an HTTP POST</li>
152+
* <li>It must be submitted to {@link ProviderSettings#authorizationEndpoint()}</li>
153+
* <li>It must include the received {@code client_id} as an HTTP parameter</li>
154+
* <li>It must include the received {@code state} as an HTTP parameter</li>
155+
* <li>It must include the list of {@code scope}s the {@code Resource Owners}
156+
* consents to as an HTTP parameter</li>
157+
* <li>It must include the {@code consent_action} parameter, with value either
158+
* {@code approve} or {@code cancel} as an HTTP parameter</li>
159+
* </ul>
160+
*
161+
*
162+
* @param consentPage the consent page to redirect to if consent is required (e.g. "/consent")
163+
* @return the {@link OAuth2AuthorizationServerConfigurer} for further configuration
164+
*/
165+
public OAuth2AuthorizationServerConfigurer<B> consentPage(String consentPage) {
166+
this.consentPage = consentPage;
167+
return this;
168+
}
169+
130170
/**
131171
* Returns a {@link RequestMatcher} for the authorization server endpoints.
132172
*
@@ -223,7 +263,12 @@ public void configure(B builder) {
223263
new OAuth2AuthorizationEndpointFilter(
224264
getRegisteredClientRepository(builder),
225265
getAuthorizationService(builder),
226-
providerSettings.authorizationEndpoint());
266+
getAuthorizationConsentService(builder),
267+
providerSettings.authorizationEndpoint()
268+
);
269+
if (this.consentPage != null) {
270+
authorizationEndpointFilter.setCustomUserConsentUri(this.consentPage);
271+
}
227272
builder.addFilterBefore(postProcess(authorizationEndpointFilter), AbstractPreAuthenticatedProcessingFilter.class);
228273

229274
OAuth2TokenEndpointFilter tokenEndpointFilter =
@@ -288,6 +333,18 @@ private static <B extends HttpSecurityBuilder<B>> OAuth2AuthorizationService get
288333
return authorizationService;
289334
}
290335

336+
private static <B extends HttpSecurityBuilder<B>> OAuth2AuthorizationConsentService getAuthorizationConsentService(B builder) {
337+
OAuth2AuthorizationConsentService authorizationConsentService = builder.getSharedObject(OAuth2AuthorizationConsentService.class);
338+
if (authorizationConsentService == null) {
339+
authorizationConsentService = getOptionalBean(builder, OAuth2AuthorizationConsentService.class);
340+
if (authorizationConsentService == null) {
341+
authorizationConsentService = new InMemoryOAuth2AuthorizationConsentService();
342+
}
343+
builder.setSharedObject(OAuth2AuthorizationConsentService.class, authorizationConsentService);
344+
}
345+
return authorizationConsentService;
346+
}
347+
291348
private static <B extends HttpSecurityBuilder<B>> JwtEncoder getJwtEncoder(B builder) {
292349
JwtEncoder jwtEncoder = builder.getSharedObject(JwtEncoder.class);
293350
if (jwtEncoder == null) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
* Copyright 2020-2021 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;
17+
18+
import org.springframework.lang.NonNull;
19+
import org.springframework.lang.Nullable;
20+
21+
import java.util.Map;
22+
import java.util.concurrent.ConcurrentHashMap;
23+
24+
/**
25+
* An {@link OAuth2AuthorizationConsentService} that stores {@link OAuth2AuthorizationConsent}'s in-memory.
26+
*
27+
* <p>
28+
* <b>NOTE:</b> This implementation should ONLY be used during development/testing.
29+
*
30+
* @author Daniel Garnier-Moiroux
31+
* @since 0.1.1
32+
* @see OAuth2AuthorizationConsent
33+
*/
34+
public class InMemoryOAuth2AuthorizationConsentService implements OAuth2AuthorizationConsentService {
35+
private final Map<OAuth2AuthorizationConsent.Key, OAuth2AuthorizationConsent> consents = new ConcurrentHashMap<>();
36+
37+
@Override
38+
public void save(@NonNull OAuth2AuthorizationConsent consent) {
39+
OAuth2AuthorizationConsent.Key key = OAuth2AuthorizationConsent.Key.from(consent);
40+
this.consents.put(key, consent);
41+
}
42+
43+
@Override
44+
public void remove(@NonNull OAuth2AuthorizationConsent consent) {
45+
OAuth2AuthorizationConsent.Key key = OAuth2AuthorizationConsent.Key.from(consent);
46+
this.consents.remove(key, consent);
47+
}
48+
49+
@Override
50+
@Nullable
51+
public OAuth2AuthorizationConsent findByRegisteredClientIdAndPrincipalName(String registeredClientId, String principalName) {
52+
OAuth2AuthorizationConsent.Key key = new OAuth2AuthorizationConsent.Key(registeredClientId, principalName);
53+
return this.consents.get(key);
54+
}
55+
}

0 commit comments

Comments
 (0)