Skip to content

Commit db8a3c3

Browse files
committed
Added token endpoint implementation
Closes spring-projectsgh-67
1 parent e25e116 commit db8a3c3

File tree

5 files changed

+393
-1
lines changed

5 files changed

+393
-1
lines changed

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

+9
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,20 @@
2121

2222
/**
2323
* @author Joe Grandja
24+
* @author Madhu Bhat
2425
*/
2526
public class OAuth2Authorization {
2627
private String registeredClientId;
2728
private String principalName;
2829
private OAuth2AccessToken accessToken;
2930
private Map<String, Object> attributes;
3031

32+
/**
33+
* Sets the access token {@link OAuth2AccessToken} in the {@link OAuth2Authorization}.
34+
*
35+
* @param accessToken the access token
36+
*/
37+
public final void setAccessToken(OAuth2AccessToken accessToken) {
38+
this.accessToken = accessToken;
39+
}
3140
}

core/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AccessTokenAuthenticationToken.java

+10
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
/**
2727
* @author Joe Grandja
28+
* @author Madhu Bhat
2829
*/
2930
public class OAuth2AccessTokenAuthenticationToken extends AbstractAuthenticationToken {
3031
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
@@ -49,4 +50,13 @@ public Object getCredentials() {
4950
public Object getPrincipal() {
5051
return null;
5152
}
53+
54+
/**
55+
* Returns the access token {@link OAuth2AccessToken}.
56+
*
57+
* @return the access token
58+
*/
59+
public OAuth2AccessToken getAccessToken() {
60+
return accessToken;
61+
}
5262
}

core/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeAuthenticationToken.java

+10
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424

2525
/**
2626
* @author Joe Grandja
27+
* @author Madhu Bhat
2728
*/
2829
public class OAuth2AuthorizationCodeAuthenticationToken extends AbstractAuthenticationToken {
2930
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
@@ -57,4 +58,13 @@ public Object getCredentials() {
5758
public Object getPrincipal() {
5859
return null;
5960
}
61+
62+
/**
63+
* Returns the code.
64+
*
65+
* @return the code
66+
*/
67+
public String getCode() {
68+
return code;
69+
}
6070
}

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

+144-1
Original file line numberDiff line numberDiff line change
@@ -15,30 +15,173 @@
1515
*/
1616
package org.springframework.security.oauth2.server.authorization.web;
1717

18+
import com.fasterxml.jackson.annotation.JsonInclude;
19+
import com.fasterxml.jackson.databind.ObjectMapper;
1820
import org.springframework.core.convert.converter.Converter;
21+
import org.springframework.http.HttpHeaders;
22+
import org.springframework.http.HttpMethod;
23+
import org.springframework.http.HttpStatus;
24+
import org.springframework.http.MediaType;
1925
import org.springframework.security.authentication.AuthenticationManager;
2026
import org.springframework.security.core.Authentication;
27+
import org.springframework.security.core.context.SecurityContextHolder;
28+
import org.springframework.security.oauth2.core.AuthorizationGrantType;
29+
import org.springframework.security.oauth2.core.OAuth2AccessToken;
30+
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
31+
import org.springframework.security.oauth2.core.OAuth2Error;
32+
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
33+
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
34+
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
2135
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
36+
import org.springframework.security.oauth2.server.authorization.TokenType;
37+
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AccessTokenAuthenticationToken;
38+
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeAuthenticationToken;
39+
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
40+
import org.springframework.security.web.util.matcher.RequestMatcher;
41+
import org.springframework.util.Assert;
42+
import org.springframework.util.StringUtils;
2243
import org.springframework.web.filter.OncePerRequestFilter;
2344

2445
import javax.servlet.FilterChain;
2546
import javax.servlet.ServletException;
2647
import javax.servlet.http.HttpServletRequest;
2748
import javax.servlet.http.HttpServletResponse;
2849
import java.io.IOException;
50+
import java.io.Writer;
2951

3052
/**
53+
* This {@code Filter} is used by the client to obtain an access token by presenting
54+
* its authorization grant.
55+
*
56+
* <p>
57+
* It converts the OAuth 2.0 Access Token Request to {@link OAuth2AuthorizationCodeAuthenticationToken},
58+
* which is then authenticated by the {@link AuthenticationManager} and gets back
59+
* {@link OAuth2AccessTokenAuthenticationToken} which has the {@link OAuth2AccessToken} if the request
60+
* was successfully authenticated. The {@link OAuth2AccessToken} is then updated in the in-flight {@link OAuth2Authorization}
61+
* and sent back to the client. In case the authentication fails, an HTTP 401 (Unauthorized) response is returned.
62+
*
63+
* <p>
64+
* By default, this {@code Filter} responds to access token requests
65+
* at the {@code URI} {@code /oauth2/token} and {@code HttpMethod} {@code POST}
66+
* using the default {@link AntPathRequestMatcher}.
67+
*
68+
* <p>
69+
* The default base {@code URI} {@code /oauth2/token} may be overridden
70+
* via the constructor {@link #OAuth2TokenEndpointFilter(OAuth2AuthorizationService, AuthenticationManager, String)}.
71+
*
3172
* @author Joe Grandja
73+
* @author Madhu Bhat
3274
*/
3375
public class OAuth2TokenEndpointFilter extends OncePerRequestFilter {
34-
private Converter<HttpServletRequest, Authentication> authorizationGrantConverter;
76+
private static final String DEFAULT_TOKEN_ENDPOINT_URI = "/oauth2/token";
77+
private Converter<HttpServletRequest, Authentication> authorizationGrantConverter = this::convert;
3578
private AuthenticationManager authenticationManager;
3679
private OAuth2AuthorizationService authorizationService;
80+
private RequestMatcher uriMatcher;
81+
private ObjectMapper objectMapper = new ObjectMapper().setSerializationInclusion(JsonInclude.Include.NON_NULL);
82+
83+
/**
84+
* Constructs an {@code OAuth2TokenEndpointFilter} using the provided parameters.
85+
*
86+
* @param authorizationService the authorization service implementation
87+
* @param authenticationManager the authentication manager implementation
88+
*/
89+
public OAuth2TokenEndpointFilter(OAuth2AuthorizationService authorizationService, AuthenticationManager authenticationManager) {
90+
Assert.notNull(authorizationService, "authorizationService cannot be null");
91+
Assert.notNull(authenticationManager, "authenticationManager cannot be null");
92+
this.authenticationManager = authenticationManager;
93+
this.authorizationService = authorizationService;
94+
this.uriMatcher = new AntPathRequestMatcher(DEFAULT_TOKEN_ENDPOINT_URI, HttpMethod.POST.name());
95+
}
96+
97+
/**
98+
* Constructs an {@code OAuth2TokenEndpointFilter} using the provided parameters.
99+
*
100+
* @param authorizationService the authorization service implementation
101+
* @param authenticationManager the authentication manager implementation
102+
* @param tokenEndpointUri the token endpoint's uri
103+
*/
104+
public OAuth2TokenEndpointFilter(OAuth2AuthorizationService authorizationService, AuthenticationManager authenticationManager,
105+
String tokenEndpointUri) {
106+
Assert.notNull(authorizationService, "authorizationService cannot be null");
107+
Assert.notNull(authenticationManager, "authenticationManager cannot be null");
108+
Assert.hasText(tokenEndpointUri, "tokenEndpointUri cannot be empty");
109+
this.authenticationManager = authenticationManager;
110+
this.authorizationService = authorizationService;
111+
this.uriMatcher = new AntPathRequestMatcher(tokenEndpointUri, HttpMethod.POST.name());
112+
}
37113

38114
@Override
39115
protected void doFilterInternal(HttpServletRequest request,
40116
HttpServletResponse response, FilterChain filterChain)
41117
throws ServletException, IOException {
118+
if (uriMatcher.matches(request)) {
119+
try {
120+
if (validateAccessTokenRequest(request)) {
121+
OAuth2AuthorizationCodeAuthenticationToken authCodeAuthToken =
122+
(OAuth2AuthorizationCodeAuthenticationToken) authorizationGrantConverter.convert(request);
123+
OAuth2AccessTokenAuthenticationToken accessTokenAuthenticationToken =
124+
(OAuth2AccessTokenAuthenticationToken) authenticationManager.authenticate(authCodeAuthToken);
125+
if (accessTokenAuthenticationToken.isAuthenticated()) {
126+
OAuth2Authorization authorization = authorizationService
127+
.findByTokenAndTokenType(authCodeAuthToken.getCode(), TokenType.AUTHORIZATION_CODE);
128+
authorization.setAccessToken(accessTokenAuthenticationToken.getAccessToken());
129+
authorizationService.save(authorization);
130+
writeSuccessResponse(response, accessTokenAuthenticationToken.getAccessToken());
131+
} else {
132+
throw new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.INVALID_CLIENT));
133+
}
134+
}
135+
} catch (OAuth2AuthenticationException exception) {
136+
SecurityContextHolder.clearContext();
137+
writeFailureResponse(response, exception.getError());
138+
}
139+
} else {
140+
filterChain.doFilter(request, response);
141+
}
142+
}
143+
144+
private boolean validateAccessTokenRequest(HttpServletRequest request) {
145+
if (StringUtils.isEmpty(request.getParameter(OAuth2ParameterNames.CODE))
146+
|| StringUtils.isEmpty(request.getParameter(OAuth2ParameterNames.REDIRECT_URI))
147+
|| StringUtils.isEmpty(request.getParameter(OAuth2ParameterNames.GRANT_TYPE))) {
148+
throw new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.INVALID_REQUEST));
149+
} else if (!AuthorizationGrantType.AUTHORIZATION_CODE.getValue().equals(request.getParameter(OAuth2ParameterNames.GRANT_TYPE))) {
150+
throw new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.UNSUPPORTED_GRANT_TYPE));
151+
}
152+
return true;
153+
}
154+
155+
private OAuth2AuthorizationCodeAuthenticationToken convert(HttpServletRequest request) {
156+
Authentication clientPrincipal = SecurityContextHolder.getContext().getAuthentication();
157+
return new OAuth2AuthorizationCodeAuthenticationToken(
158+
request.getParameter(OAuth2ParameterNames.CODE),
159+
clientPrincipal,
160+
request.getParameter(OAuth2ParameterNames.REDIRECT_URI)
161+
);
162+
}
163+
164+
private void writeSuccessResponse(HttpServletResponse response, OAuth2AccessToken body) throws IOException {
165+
try (Writer out = response.getWriter()) {
166+
response.setStatus(HttpStatus.OK.value());
167+
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
168+
response.setCharacterEncoding("UTF-8");
169+
response.setHeader(HttpHeaders.CACHE_CONTROL, "no-store");
170+
response.setHeader(HttpHeaders.PRAGMA, "no-cache");
171+
out.write(objectMapper.writeValueAsString(body));
172+
}
173+
}
42174

175+
private void writeFailureResponse(HttpServletResponse response, OAuth2Error error) throws IOException {
176+
try (Writer out = response.getWriter()) {
177+
if (error.getErrorCode().equals(OAuth2ErrorCodes.INVALID_CLIENT)) {
178+
response.setStatus(HttpStatus.UNAUTHORIZED.value());
179+
} else {
180+
response.setStatus(HttpStatus.BAD_REQUEST.value());
181+
}
182+
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
183+
response.setCharacterEncoding("UTF-8");
184+
out.write(objectMapper.writeValueAsString(error));
185+
}
43186
}
44187
}

0 commit comments

Comments
 (0)