Skip to content

Commit c999de7

Browse files
paurav-munshijgrandja
authored andcommitted
Add Authorization Endpoint filter
Fixes spring-projectsgh-66
1 parent 49212b0 commit c999de7

File tree

4 files changed

+668
-8
lines changed

4 files changed

+668
-8
lines changed

core/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2AuthorizationEndpointFilter.java

+206-8
Original file line numberDiff line numberDiff line change
@@ -15,33 +15,231 @@
1515
*/
1616
package org.springframework.security.oauth2.server.authorization.web;
1717

18+
import java.io.IOException;
19+
import java.util.stream.Stream;
20+
21+
import javax.servlet.FilterChain;
22+
import javax.servlet.ServletException;
23+
import javax.servlet.http.HttpServletRequest;
24+
import javax.servlet.http.HttpServletResponse;
25+
1826
import org.springframework.core.convert.converter.Converter;
27+
import org.springframework.http.HttpStatus;
28+
import org.springframework.security.core.Authentication;
29+
import org.springframework.security.core.context.SecurityContextHolder;
30+
import org.springframework.security.crypto.keygen.Base64StringKeyGenerator;
1931
import org.springframework.security.crypto.keygen.StringKeyGenerator;
32+
import org.springframework.security.oauth2.core.AuthorizationGrantType;
33+
import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
34+
import org.springframework.security.oauth2.core.OAuth2Error;
35+
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
2036
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
37+
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
38+
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
2139
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
40+
import org.springframework.security.oauth2.server.authorization.TokenType;
41+
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
2242
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
43+
import org.springframework.security.web.DefaultRedirectStrategy;
44+
import org.springframework.security.web.RedirectStrategy;
45+
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
46+
import org.springframework.security.web.util.matcher.RequestMatcher;
47+
import org.springframework.util.Assert;
48+
import org.springframework.util.StringUtils;
2349
import org.springframework.web.filter.OncePerRequestFilter;
24-
25-
import javax.servlet.FilterChain;
26-
import javax.servlet.ServletException;
27-
import javax.servlet.http.HttpServletRequest;
28-
import javax.servlet.http.HttpServletResponse;
29-
import java.io.IOException;
50+
import org.springframework.web.util.UriComponentsBuilder;
3051

3152
/**
3253
* @author Joe Grandja
54+
* @author Paurav Munshi
55+
* @since 0.0.1
3356
*/
3457
public class OAuth2AuthorizationEndpointFilter extends OncePerRequestFilter {
35-
private Converter<HttpServletRequest, OAuth2AuthorizationRequest> authorizationRequestConverter;
58+
59+
private static final String DEFAULT_ENDPOINT = "/oauth2/authorize";
60+
61+
private Converter<HttpServletRequest, OAuth2AuthorizationRequest> authorizationRequestConverter = new OAuth2AuthorizationRequestConverter();
3662
private RegisteredClientRepository registeredClientRepository;
3763
private OAuth2AuthorizationService authorizationService;
38-
private StringKeyGenerator codeGenerator;
64+
private StringKeyGenerator codeGenerator = new Base64StringKeyGenerator();
65+
private RedirectStrategy authorizationRedirectStrategy = new DefaultRedirectStrategy();
66+
private RequestMatcher authorizationEndpointMatcher = new AntPathRequestMatcher(DEFAULT_ENDPOINT);
67+
68+
public OAuth2AuthorizationEndpointFilter(RegisteredClientRepository registeredClientRepository,
69+
OAuth2AuthorizationService authorizationService) {
70+
Assert.notNull(registeredClientRepository, "registeredClientRepository cannot be null.");
71+
Assert.notNull(authorizationService, "authorizationService cannot be null.");
72+
this.registeredClientRepository = registeredClientRepository;
73+
this.authorizationService = authorizationService;
74+
}
75+
76+
public final void setAuthorizationRequestConverter(
77+
Converter<HttpServletRequest, OAuth2AuthorizationRequest> authorizationRequestConverter) {
78+
Assert.notNull(authorizationRequestConverter, "authorizationRequestConverter cannot be set to null");
79+
this.authorizationRequestConverter = authorizationRequestConverter;
80+
}
81+
82+
public final void setCodeGenerator(StringKeyGenerator codeGenerator) {
83+
Assert.notNull(codeGenerator, "codeGenerator cannot be set to null");
84+
this.codeGenerator = codeGenerator;
85+
}
86+
87+
public final void setAuthorizationRedirectStrategy(RedirectStrategy authorizationRedirectStrategy) {
88+
Assert.notNull(authorizationRedirectStrategy, "authorizationRedirectStrategy cannot be set to null");
89+
this.authorizationRedirectStrategy = authorizationRedirectStrategy;
90+
}
91+
92+
public final void setAuthorizationEndpointMatcher(RequestMatcher authorizationEndpointMatcher) {
93+
Assert.notNull(authorizationEndpointMatcher, "authorizationEndpointMatcher cannot be set to null");
94+
this.authorizationEndpointMatcher = authorizationEndpointMatcher;
95+
}
96+
97+
@Override
98+
protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
99+
boolean pathMatch = this.authorizationEndpointMatcher.matches(request);
100+
String responseType = request.getParameter(OAuth2ParameterNames.RESPONSE_TYPE);
101+
boolean responseTypeMatch = OAuth2ParameterNames.CODE.equals(responseType);
102+
if (pathMatch && responseTypeMatch) {
103+
return false;
104+
}else {
105+
return true;
106+
}
107+
}
39108

40109
@Override
41110
protected void doFilterInternal(HttpServletRequest request,
42111
HttpServletResponse response, FilterChain filterChain)
43112
throws ServletException, IOException {
44113

114+
RegisteredClient client = null;
115+
OAuth2AuthorizationRequest authorizationRequest = null;
116+
OAuth2Authorization authorization = null;
117+
118+
try {
119+
checkUserAuthenticated();
120+
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
121+
client = fetchRegisteredClient(request);
122+
123+
authorizationRequest = this.authorizationRequestConverter.convert(request);
124+
validateAuthorizationRequest(authorizationRequest, client);
125+
126+
String code = this.codeGenerator.generateKey();
127+
authorization = buildOAuth2Authorization(auth, client, authorizationRequest, code);
128+
this.authorizationService.save(authorization);
129+
130+
String redirectUri = getRedirectUri(authorizationRequest, client);
131+
sendCodeOnSuccess(request, response, authorizationRequest, redirectUri, code);
132+
}
133+
catch(OAuth2AuthorizationException authorizationException) {
134+
OAuth2Error authorizationError = authorizationException.getError();
135+
136+
if (authorizationError.getErrorCode().equals(OAuth2ErrorCodes.INVALID_REQUEST)
137+
|| authorizationError.getErrorCode().equals(OAuth2ErrorCodes.ACCESS_DENIED)) {
138+
sendErrorInResponse(response, authorizationError);
139+
}
140+
else if (authorizationError.getErrorCode().equals(OAuth2ErrorCodes.UNSUPPORTED_RESPONSE_TYPE)
141+
|| authorizationError.getErrorCode().equals(OAuth2ErrorCodes.UNAUTHORIZED_CLIENT)) {
142+
String redirectUri = getRedirectUri(authorizationRequest, client);
143+
sendErrorInRedirect(request, response, authorizationRequest, authorizationError, redirectUri);
144+
}
145+
else {
146+
throw new ServletException(authorizationException);
147+
}
148+
}
149+
150+
}
151+
152+
private void checkUserAuthenticated() {
153+
Authentication currentAuth = SecurityContextHolder.getContext().getAuthentication();
154+
if (currentAuth==null || !currentAuth.isAuthenticated()) {
155+
throw new OAuth2AuthorizationException(new OAuth2Error(OAuth2ErrorCodes.ACCESS_DENIED));
156+
}
45157
}
46158

159+
private RegisteredClient fetchRegisteredClient(HttpServletRequest request) throws OAuth2AuthorizationException {
160+
String clientId = request.getParameter(OAuth2ParameterNames.CLIENT_ID);
161+
if (StringUtils.isEmpty(clientId)) {
162+
throw new OAuth2AuthorizationException(new OAuth2Error(OAuth2ErrorCodes.INVALID_REQUEST));
163+
}
164+
165+
RegisteredClient client = this.registeredClientRepository.findByClientId(clientId);
166+
if (client==null) {
167+
throw new OAuth2AuthorizationException(new OAuth2Error(OAuth2ErrorCodes.ACCESS_DENIED));
168+
}
169+
170+
boolean isAuthorizationGrantAllowed = Stream.of(client.getAuthorizationGrantTypes())
171+
.anyMatch(grantType -> grantType.contains(AuthorizationGrantType.AUTHORIZATION_CODE));
172+
if (!isAuthorizationGrantAllowed) {
173+
throw new OAuth2AuthorizationException(new OAuth2Error(OAuth2ErrorCodes.ACCESS_DENIED));
174+
}
175+
176+
return client;
177+
178+
}
179+
180+
private OAuth2Authorization buildOAuth2Authorization(Authentication auth, RegisteredClient client,
181+
OAuth2AuthorizationRequest authorizationRequest, String code) {
182+
OAuth2Authorization authorization = OAuth2Authorization.withRegisteredClient(client)
183+
.principalName(auth.getPrincipal().toString())
184+
.attribute(TokenType.AUTHORIZATION_CODE.getValue(), code)
185+
.attributes(attirbutesMap -> attirbutesMap.putAll(authorizationRequest.getAttributes()))
186+
.build();
187+
188+
return authorization;
189+
}
190+
191+
192+
private void validateAuthorizationRequest(OAuth2AuthorizationRequest authorizationRequest, RegisteredClient client) {
193+
String redirectUri = authorizationRequest.getRedirectUri();
194+
if (StringUtils.isEmpty(redirectUri) && client.getRedirectUris().size() > 1) {
195+
throw new OAuth2AuthorizationException(new OAuth2Error(OAuth2ErrorCodes.INVALID_REQUEST));
196+
}
197+
if (!StringUtils.isEmpty(redirectUri) && !client.getRedirectUris().contains(redirectUri)) {
198+
throw new OAuth2AuthorizationException(new OAuth2Error(OAuth2ErrorCodes.INVALID_REQUEST));
199+
}
200+
}
201+
202+
private String getRedirectUri(OAuth2AuthorizationRequest authorizationRequest, RegisteredClient client) {
203+
return !StringUtils.isEmpty(authorizationRequest.getRedirectUri())
204+
? authorizationRequest.getRedirectUri()
205+
: client.getRedirectUris().stream().findFirst().get();
206+
}
207+
208+
private void sendCodeOnSuccess(HttpServletRequest request, HttpServletResponse response,
209+
OAuth2AuthorizationRequest authorizationRequest, String redirectUri, String code) throws IOException {
210+
UriComponentsBuilder redirectUriBuilder = UriComponentsBuilder.fromUriString(redirectUri)
211+
.queryParam(OAuth2ParameterNames.CODE, code);
212+
if (!StringUtils.isEmpty(authorizationRequest.getState())) {
213+
redirectUriBuilder.queryParam(OAuth2ParameterNames.STATE, authorizationRequest.getState());
214+
}
215+
216+
String finalRedirectUri = redirectUriBuilder.toUriString();
217+
this.authorizationRedirectStrategy.sendRedirect(request, response, finalRedirectUri);
218+
}
219+
220+
private void sendErrorInResponse(HttpServletResponse response, OAuth2Error authorizationError) throws IOException {
221+
int errorStatus = -1;
222+
String errorCode = authorizationError.getErrorCode();
223+
if (errorCode.equals(OAuth2ErrorCodes.ACCESS_DENIED)) {
224+
errorStatus=HttpStatus.FORBIDDEN.value();
225+
}
226+
else {
227+
errorStatus=HttpStatus.INTERNAL_SERVER_ERROR.value();
228+
}
229+
response.sendError(errorStatus, authorizationError.getErrorCode());
230+
}
231+
232+
private void sendErrorInRedirect(HttpServletRequest request, HttpServletResponse response,
233+
OAuth2AuthorizationRequest authorizationRequest, OAuth2Error authorizationError,
234+
String redirectUri) throws IOException {
235+
UriComponentsBuilder redirectUriBuilder = UriComponentsBuilder.fromUriString(redirectUri)
236+
.queryParam(OAuth2ParameterNames.ERROR, authorizationError.getErrorCode());
237+
238+
if (!StringUtils.isEmpty(authorizationRequest.getState())) {
239+
redirectUriBuilder.queryParam(OAuth2ParameterNames.STATE, authorizationRequest.getState());
240+
}
241+
242+
String finalRedirectURI = redirectUriBuilder.toUriString();
243+
this.authorizationRedirectStrategy.sendRedirect(request, response, finalRedirectURI);
244+
}
47245
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
* Copyright 2020 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.web;
17+
18+
import java.util.Arrays;
19+
import java.util.Collections;
20+
import java.util.LinkedHashSet;
21+
import java.util.Set;
22+
23+
import javax.servlet.http.HttpServletRequest;
24+
25+
import org.springframework.core.convert.converter.Converter;
26+
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
27+
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
28+
import org.springframework.util.StringUtils;
29+
30+
/**
31+
* @author Paurav Munshi
32+
* @since 0.0.1
33+
* @see Converter
34+
*/
35+
public class OAuth2AuthorizationRequestConverter implements Converter<HttpServletRequest, OAuth2AuthorizationRequest> {
36+
37+
@Override
38+
public OAuth2AuthorizationRequest convert(HttpServletRequest request) {
39+
String scope = request.getParameter(OAuth2ParameterNames.SCOPE);
40+
Set<String> scopes = !StringUtils.isEmpty(scope)
41+
? new LinkedHashSet<String>(Arrays.asList(scope.split(" ")))
42+
: Collections.emptySet();
43+
44+
OAuth2AuthorizationRequest authorizationRequest = OAuth2AuthorizationRequest.authorizationCode()
45+
.clientId(request.getParameter(OAuth2ParameterNames.CLIENT_ID))
46+
.redirectUri(request.getParameter(OAuth2ParameterNames.REDIRECT_URI))
47+
.scopes(scopes)
48+
.state(request.getParameter(OAuth2ParameterNames.STATE))
49+
.authorizationUri(request.getServletPath())
50+
.build();
51+
52+
return authorizationRequest;
53+
}
54+
55+
}

core/src/test/java/org/springframework/security/oauth2/server/authorization/client/TestRegisteredClients.java

+36
Original file line numberDiff line numberDiff line change
@@ -46,4 +46,40 @@ public static RegisteredClient.Builder registeredClient2() {
4646
.scope("profile")
4747
.scope("email");
4848
}
49+
50+
public static RegisteredClient.Builder validAuthorizationGrantRegisteredClient() {
51+
return RegisteredClient.withId("valid_client_id")
52+
.clientId("valid_client")
53+
.clientSecret("valid_secret")
54+
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
55+
.clientAuthenticationMethod(ClientAuthenticationMethod.BASIC)
56+
.redirectUri("http://localhost:8080/test-application/callback")
57+
.scope("openid")
58+
.scope("profile")
59+
.scope("email");
60+
}
61+
62+
public static RegisteredClient.Builder validAuthorizationGrantClientMultiRedirectUris() {
63+
return RegisteredClient.withId("valid_client_multi_uri_id")
64+
.clientId("valid_client_multi_uri")
65+
.clientSecret("valid_secret")
66+
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
67+
.clientAuthenticationMethod(ClientAuthenticationMethod.BASIC)
68+
.redirectUri("http://localhost:8080/test-application/callback")
69+
.redirectUri("http://localhost:8080/another-test-application/callback")
70+
.scope("openid")
71+
.scope("profile")
72+
.scope("email");
73+
}
74+
75+
public static RegisteredClient.Builder validClientCredentialsGrantRegisteredClient() {
76+
return RegisteredClient.withId("valid_cc_client_id")
77+
.clientId("valid_cc_client")
78+
.clientSecret("valid_secret")
79+
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
80+
.clientAuthenticationMethod(ClientAuthenticationMethod.BASIC)
81+
.scope("openid")
82+
.scope("profile")
83+
.scope("email");
84+
}
4985
}

0 commit comments

Comments
 (0)