Skip to content

Commit 85d5d40

Browse files
committed
Add ServerOAuth2AuthorizationRequestResolver
Fixes: gh-5610
1 parent b9ab492 commit 85d5d40

File tree

5 files changed

+319
-118
lines changed

5 files changed

+319
-118
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
/*
2+
* Copyright 2002-2018 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+
* http://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+
17+
package org.springframework.security.oauth2.client.web.server;
18+
19+
import org.springframework.http.HttpStatus;
20+
import org.springframework.http.server.reactive.ServerHttpRequest;
21+
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
22+
import org.springframework.security.crypto.keygen.Base64StringKeyGenerator;
23+
import org.springframework.security.crypto.keygen.StringKeyGenerator;
24+
import org.springframework.security.oauth2.client.registration.ClientRegistration;
25+
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
26+
import org.springframework.security.oauth2.core.AuthorizationGrantType;
27+
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
28+
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
29+
import org.springframework.security.web.server.util.matcher.PathPatternParserServerWebExchangeMatcher;
30+
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher;
31+
import org.springframework.util.Assert;
32+
import org.springframework.web.server.ResponseStatusException;
33+
import org.springframework.web.server.ServerWebExchange;
34+
import org.springframework.web.util.UriComponentsBuilder;
35+
import reactor.core.publisher.Mono;
36+
37+
import java.util.Base64;
38+
import java.util.HashMap;
39+
import java.util.Map;
40+
41+
/**
42+
* The default implementation of {@link ServerOAuth2AuthorizationRequestResolver}.
43+
*
44+
* The {@link ClientRegistration#getRegistrationId()} is extracted from the request using the
45+
* {@link #DEFAULT_AUTHORIZATION_REQUEST_PATTERN}. The injected {@link ReactiveClientRegistrationRepository} is then
46+
* used to resolve the {@link ClientRegistration} and create the {@link OAuth2AuthorizationRequest}.
47+
*
48+
* @author Rob Winch
49+
* @since 5.1
50+
*/
51+
public class DefaultServerOAuth2AuthorizationRequestResolver
52+
implements ServerOAuth2AuthorizationRequestResolver {
53+
54+
/**
55+
* The name of the path variable that contains the {@link ClientRegistration#getRegistrationId()}
56+
*/
57+
public static final String DEFAULT_REGISTRATION_ID_URI_VARIABLE_NAME = "registrationId";
58+
59+
/**
60+
* The default pattern used to resolve the {@link ClientRegistration#getRegistrationId()}
61+
*/
62+
public static final String DEFAULT_AUTHORIZATION_REQUEST_PATTERN = "/oauth2/authorization/{" + DEFAULT_REGISTRATION_ID_URI_VARIABLE_NAME
63+
+ "}";
64+
65+
private final ServerWebExchangeMatcher authorizationRequestMatcher;
66+
67+
private final ReactiveClientRegistrationRepository clientRegistrationRepository;
68+
69+
private final StringKeyGenerator stateGenerator = new Base64StringKeyGenerator(Base64.getUrlEncoder());
70+
71+
/**
72+
* Creates a new instance
73+
* @param clientRegistrationRepository the repository to resolve the {@link ClientRegistration}
74+
*/
75+
public DefaultServerOAuth2AuthorizationRequestResolver(ReactiveClientRegistrationRepository clientRegistrationRepository) {
76+
this(clientRegistrationRepository, new PathPatternParserServerWebExchangeMatcher(
77+
DEFAULT_AUTHORIZATION_REQUEST_PATTERN));
78+
}
79+
80+
/**
81+
* Creates a new instance
82+
* @param clientRegistrationRepository the repository to resolve the {@link ClientRegistration}
83+
* @param authorizationRequestMatcher the matcher that determines if the request is a match and extracts the
84+
* {@link #DEFAULT_REGISTRATION_ID_URI_VARIABLE_NAME} from the path variables.
85+
*/
86+
public DefaultServerOAuth2AuthorizationRequestResolver(ReactiveClientRegistrationRepository clientRegistrationRepository,
87+
ServerWebExchangeMatcher authorizationRequestMatcher) {
88+
Assert.notNull(clientRegistrationRepository, "clientRegistrationRepository cannot be null");
89+
Assert.notNull(authorizationRequestMatcher, "authorizationRequestMatcher cannot be null");
90+
this.clientRegistrationRepository = clientRegistrationRepository;
91+
this.authorizationRequestMatcher = authorizationRequestMatcher;
92+
}
93+
94+
@Override
95+
public Mono<OAuth2AuthorizationRequest> resolve(ServerWebExchange exchange) {
96+
return this.authorizationRequestMatcher.matches(exchange)
97+
.filter(matchResult -> matchResult.isMatch())
98+
.map(ServerWebExchangeMatcher.MatchResult::getVariables)
99+
.map(variables -> variables.get(DEFAULT_REGISTRATION_ID_URI_VARIABLE_NAME))
100+
.cast(String.class)
101+
.flatMap(clientRegistrationId -> resolve(exchange, clientRegistrationId));
102+
}
103+
104+
@Override
105+
public Mono<OAuth2AuthorizationRequest> resolve(ServerWebExchange exchange,
106+
String clientRegistrationId) {
107+
return this.findByRegistrationId(exchange, clientRegistrationId)
108+
.map(clientRegistration -> authorizationRequest(exchange, clientRegistration));
109+
}
110+
111+
private Mono<ClientRegistration> findByRegistrationId(ServerWebExchange exchange, String clientRegistration) {
112+
return this.clientRegistrationRepository.findByRegistrationId(clientRegistration)
113+
.switchIfEmpty(Mono.error(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST, "Invalid client registration id")));
114+
}
115+
116+
private OAuth2AuthorizationRequest authorizationRequest(ServerWebExchange exchange,
117+
ClientRegistration clientRegistration) {
118+
String redirectUriStr = this
119+
.expandRedirectUri(exchange.getRequest(), clientRegistration);
120+
121+
Map<String, Object> additionalParameters = new HashMap<>();
122+
additionalParameters.put(OAuth2ParameterNames.REGISTRATION_ID,
123+
clientRegistration.getRegistrationId());
124+
125+
OAuth2AuthorizationRequest.Builder builder;
126+
if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(clientRegistration.getAuthorizationGrantType())) {
127+
builder = OAuth2AuthorizationRequest.authorizationCode();
128+
}
129+
else if (AuthorizationGrantType.IMPLICIT.equals(clientRegistration.getAuthorizationGrantType())) {
130+
builder = OAuth2AuthorizationRequest.implicit();
131+
}
132+
else {
133+
throw new IllegalArgumentException(
134+
"Invalid Authorization Grant Type (" + clientRegistration.getAuthorizationGrantType().getValue()
135+
+ ") for Client Registration with Id: " + clientRegistration.getRegistrationId());
136+
}
137+
return builder
138+
.clientId(clientRegistration.getClientId())
139+
.authorizationUri(clientRegistration.getProviderDetails().getAuthorizationUri())
140+
.redirectUri(redirectUriStr).scopes(clientRegistration.getScopes())
141+
.state(this.stateGenerator.generateKey())
142+
.additionalParameters(additionalParameters)
143+
.build();
144+
}
145+
146+
private String expandRedirectUri(ServerHttpRequest request, ClientRegistration clientRegistration) {
147+
// Supported URI variables -> baseUrl, action, registrationId
148+
// Used in -> CommonOAuth2Provider.DEFAULT_REDIRECT_URL = "{baseUrl}/{action}/oauth2/code/{registrationId}"
149+
Map<String, String> uriVariables = new HashMap<>();
150+
uriVariables.put("registrationId", clientRegistration.getRegistrationId());
151+
152+
String baseUrl = UriComponentsBuilder.fromHttpRequest(new ServerHttpRequestDecorator(request))
153+
.replacePath(request.getPath().contextPath().value())
154+
.replaceQuery(null)
155+
.build()
156+
.toUriString();
157+
uriVariables.put("baseUrl", baseUrl);
158+
159+
if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(clientRegistration.getAuthorizationGrantType())) {
160+
String loginAction = "login";
161+
uriVariables.put("action", loginAction);
162+
}
163+
164+
return UriComponentsBuilder.fromUriString(clientRegistration.getRedirectUriTemplate())
165+
.buildAndExpand(uriVariables)
166+
.toUriString();
167+
}
168+
}

oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/server/OAuth2AuthorizationRequestRedirectWebFilter.java

+11-104
Original file line numberDiff line numberDiff line change
@@ -15,36 +15,24 @@
1515
*/
1616
package org.springframework.security.oauth2.client.web.server;
1717

18-
import java.net.URI;
19-
import java.util.Base64;
20-
import java.util.HashMap;
21-
import java.util.Map;
22-
23-
import org.springframework.http.HttpStatus;
24-
import org.springframework.http.server.reactive.ServerHttpRequest;
25-
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
26-
import org.springframework.security.crypto.keygen.Base64StringKeyGenerator;
27-
import org.springframework.security.crypto.keygen.StringKeyGenerator;
2818
import org.springframework.security.oauth2.client.ClientAuthorizationRequiredException;
2919
import org.springframework.security.oauth2.client.registration.ClientRegistration;
3020
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
3121
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
3222
import org.springframework.security.oauth2.client.web.AuthorizationRequestRepository;
3323
import org.springframework.security.oauth2.core.AuthorizationGrantType;
3424
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
35-
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
3625
import org.springframework.security.web.server.DefaultServerRedirectStrategy;
3726
import org.springframework.security.web.server.ServerRedirectStrategy;
38-
import org.springframework.security.web.server.util.matcher.PathPatternParserServerWebExchangeMatcher;
39-
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher;
4027
import org.springframework.util.Assert;
4128
import org.springframework.web.server.ServerWebExchange;
4229
import org.springframework.web.server.WebFilter;
4330
import org.springframework.web.server.WebFilterChain;
4431
import org.springframework.web.util.UriComponentsBuilder;
45-
4632
import reactor.core.publisher.Mono;
4733

34+
import java.net.URI;
35+
4836
/**
4937
* This {@code WebFilter} initiates the authorization code grant or implicit grant flow
5038
* by redirecting the End-User's user-agent to the Authorization Server's Authorization Endpoint.
@@ -63,10 +51,6 @@
6351
* {@link ClientRegistration#getRegistrationId() registration identifier} of the client
6452
* that is used for initiating the OAuth 2.0 Authorization Request.
6553
*
66-
* <p>
67-
* <b>NOTE:</b> The default base {@code URI} {@code /oauth2/authorization} may be overridden
68-
* via it's constructor {@link #OAuth2AuthorizationRequestRedirectWebFilter(ReactiveClientRegistrationRepository, String)}.
69-
7054
* @author Rob Winch
7155
* @since 5.1
7256
* @see OAuth2AuthorizationRequest
@@ -79,17 +63,8 @@
7963
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.2.1">Section 4.2.1 Authorization Request (Implicit)</a>
8064
*/
8165
public class OAuth2AuthorizationRequestRedirectWebFilter implements WebFilter {
82-
/**
83-
* The default base {@code URI} used for authorization requests.
84-
*/
85-
public static final String DEFAULT_AUTHORIZATION_REQUEST_BASE_URI = "/oauth2/authorization";
86-
private static final String REGISTRATION_ID_URI_VARIABLE_NAME = "registrationId";
87-
private static final String AUTHORIZATION_REQUIRED_EXCEPTION_ATTR_NAME =
88-
ClientAuthorizationRequiredException.class.getName() + ".AUTHORIZATION_REQUIRED_EXCEPTION";
89-
private final ServerWebExchangeMatcher authorizationRequestMatcher;
90-
private final ReactiveClientRegistrationRepository clientRegistrationRepository;
9166
private final ServerRedirectStrategy authorizationRedirectStrategy = new DefaultServerRedirectStrategy();
92-
private final StringKeyGenerator stateGenerator = new Base64StringKeyGenerator(Base64.getUrlEncoder());
67+
private final ServerOAuth2AuthorizationRequestResolver authorizationRequestResolver;
9368
private ServerAuthorizationRequestRepository<OAuth2AuthorizationRequest> authorizationRequestRepository =
9469
new WebSessionOAuth2ServerAuthorizationRequestRepository();
9570

@@ -99,23 +74,17 @@ public class OAuth2AuthorizationRequestRedirectWebFilter implements WebFilter {
9974
* @param clientRegistrationRepository the repository of client registrations
10075
*/
10176
public OAuth2AuthorizationRequestRedirectWebFilter(ReactiveClientRegistrationRepository clientRegistrationRepository) {
102-
this(clientRegistrationRepository, DEFAULT_AUTHORIZATION_REQUEST_BASE_URI);
77+
this.authorizationRequestResolver = new DefaultServerOAuth2AuthorizationRequestResolver(clientRegistrationRepository);
10378
}
10479

10580
/**
10681
* Constructs an {@code OAuth2AuthorizationRequestRedirectFilter} using the provided parameters.
10782
*
108-
* @param clientRegistrationRepository the repository of client registrations
109-
* @param authorizationRequestBaseUri the base {@code URI} used for authorization requests
83+
* @param authorizationRequestResolver the resolver to use
11084
*/
111-
public OAuth2AuthorizationRequestRedirectWebFilter(
112-
ReactiveClientRegistrationRepository clientRegistrationRepository, String authorizationRequestBaseUri) {
113-
114-
Assert.hasText(authorizationRequestBaseUri, "authorizationRequestBaseUri cannot be empty");
115-
Assert.notNull(clientRegistrationRepository, "clientRegistrationRepository cannot be null");
116-
this.authorizationRequestMatcher = new PathPatternParserServerWebExchangeMatcher(
117-
authorizationRequestBaseUri + "/{" + REGISTRATION_ID_URI_VARIABLE_NAME + "}");
118-
this.clientRegistrationRepository = clientRegistrationRepository;
85+
public OAuth2AuthorizationRequestRedirectWebFilter(ServerOAuth2AuthorizationRequestResolver authorizationRequestResolver) {
86+
Assert.notNull(authorizationRequestResolver, "authorizationRequestResolver cannot be null");
87+
this.authorizationRequestResolver = authorizationRequestResolver;
11988
}
12089

12190
/**
@@ -131,54 +100,15 @@ public final void setAuthorizationRequestRepository(
131100

132101
@Override
133102
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
134-
return this.authorizationRequestMatcher.matches(exchange)
135-
.filter(matchResult -> matchResult.isMatch())
103+
return this.authorizationRequestResolver.resolve(exchange)
136104
.switchIfEmpty(chain.filter(exchange).then(Mono.empty()))
137-
.map(ServerWebExchangeMatcher.MatchResult::getVariables)
138-
.map(variables -> variables.get(REGISTRATION_ID_URI_VARIABLE_NAME))
139-
.cast(String.class)
140-
.onErrorResume(ClientAuthorizationRequiredException.class, e -> Mono.just(e.getClientRegistrationId()))
141-
.flatMap(clientRegistrationId -> this.findByRegistrationId(exchange, clientRegistrationId))
105+
.onErrorResume(ClientAuthorizationRequiredException.class, e -> this.authorizationRequestResolver.resolve(exchange, e.getClientRegistrationId()))
142106
.flatMap(clientRegistration -> sendRedirectForAuthorization(exchange, clientRegistration));
143107
}
144108

145-
private Mono<ClientRegistration> findByRegistrationId(ServerWebExchange exchange, String clientRegistration) {
146-
return this.clientRegistrationRepository.findByRegistrationId(clientRegistration)
147-
.switchIfEmpty(Mono.defer(() -> {
148-
exchange.getResponse().setStatusCode(HttpStatus.BAD_REQUEST);
149-
return exchange.getResponse().setComplete().then(Mono.empty());
150-
}));
151-
}
152-
153109
private Mono<Void> sendRedirectForAuthorization(ServerWebExchange exchange,
154-
ClientRegistration clientRegistration) {
110+
OAuth2AuthorizationRequest authorizationRequest) {
155111
return Mono.defer(() -> {
156-
String redirectUriStr = this
157-
.expandRedirectUri(exchange.getRequest(), clientRegistration);
158-
159-
Map<String, Object> additionalParameters = new HashMap<>();
160-
additionalParameters.put(OAuth2ParameterNames.REGISTRATION_ID,
161-
clientRegistration.getRegistrationId());
162-
163-
OAuth2AuthorizationRequest.Builder builder;
164-
if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(clientRegistration.getAuthorizationGrantType())) {
165-
builder = OAuth2AuthorizationRequest.authorizationCode();
166-
}
167-
else if (AuthorizationGrantType.IMPLICIT.equals(clientRegistration.getAuthorizationGrantType())) {
168-
builder = OAuth2AuthorizationRequest.implicit();
169-
}
170-
else {
171-
throw new IllegalArgumentException(
172-
"Invalid Authorization Grant Type (" + clientRegistration.getAuthorizationGrantType().getValue()
173-
+ ") for Client Registration with Id: " + clientRegistration.getRegistrationId());
174-
}
175-
OAuth2AuthorizationRequest authorizationRequest = builder
176-
.clientId(clientRegistration.getClientId())
177-
.authorizationUri(clientRegistration.getProviderDetails().getAuthorizationUri())
178-
.redirectUri(redirectUriStr).scopes(clientRegistration.getScopes())
179-
.state(this.stateGenerator.generateKey())
180-
.additionalParameters(additionalParameters).build();
181-
182112
Mono<Void> saveAuthorizationRequest = Mono.empty();
183113
if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(authorizationRequest.getGrantType())) {
184114
saveAuthorizationRequest = this.authorizationRequestRepository
@@ -192,27 +122,4 @@ else if (AuthorizationGrantType.IMPLICIT.equals(clientRegistration.getAuthorizat
192122
.then(this.authorizationRedirectStrategy.sendRedirect(exchange, redirectUri));
193123
});
194124
}
195-
196-
private String expandRedirectUri(ServerHttpRequest request, ClientRegistration clientRegistration) {
197-
// Supported URI variables -> baseUrl, action, registrationId
198-
// Used in -> CommonOAuth2Provider.DEFAULT_REDIRECT_URL = "{baseUrl}/{action}/oauth2/code/{registrationId}"
199-
Map<String, String> uriVariables = new HashMap<>();
200-
uriVariables.put("registrationId", clientRegistration.getRegistrationId());
201-
202-
String baseUrl = UriComponentsBuilder.fromHttpRequest(new ServerHttpRequestDecorator(request))
203-
.replacePath(request.getPath().contextPath().value())
204-
.replaceQuery(null)
205-
.build()
206-
.toUriString();
207-
uriVariables.put("baseUrl", baseUrl);
208-
209-
if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(clientRegistration.getAuthorizationGrantType())) {
210-
String loginAction = "login";
211-
uriVariables.put("action", loginAction);
212-
}
213-
214-
return UriComponentsBuilder.fromUriString(clientRegistration.getRedirectUriTemplate())
215-
.buildAndExpand(uriVariables)
216-
.toUriString();
217-
}
218125
}

0 commit comments

Comments
 (0)