Include WebAuthenticationDetails in token requests
Closes gh-322
This commit is contained in:
committed by
Joe Grandja
parent
9def059e29
commit
b62b161b95
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020 the original author or authors.
|
||||
* Copyright 2020-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -15,9 +15,19 @@
|
||||
*/
|
||||
package org.springframework.security.oauth2.server.authorization.web;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.http.server.ServletServerHttpResponse;
|
||||
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
||||
import org.springframework.security.authentication.AuthenticationDetailsSource;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
@@ -32,17 +42,11 @@ import org.springframework.security.oauth2.server.authorization.authentication.O
|
||||
import org.springframework.security.web.authentication.AuthenticationConverter;
|
||||
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
|
||||
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
|
||||
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* A {@code Filter} that processes an authentication request for an OAuth 2.0 Client.
|
||||
*
|
||||
@@ -58,6 +62,8 @@ public class OAuth2ClientAuthenticationFilter extends OncePerRequestFilter {
|
||||
private final AuthenticationManager authenticationManager;
|
||||
private final RequestMatcher requestMatcher;
|
||||
private final HttpMessageConverter<OAuth2Error> errorHttpResponseConverter = new OAuth2ErrorHttpMessageConverter();
|
||||
private final AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource =
|
||||
new WebAuthenticationDetailsSource();
|
||||
private AuthenticationConverter authenticationConverter;
|
||||
private AuthenticationSuccessHandler authenticationSuccessHandler;
|
||||
private AuthenticationFailureHandler authenticationFailureHandler;
|
||||
@@ -90,6 +96,10 @@ public class OAuth2ClientAuthenticationFilter extends OncePerRequestFilter {
|
||||
if (this.requestMatcher.matches(request)) {
|
||||
try {
|
||||
Authentication authenticationRequest = this.authenticationConverter.convert(request);
|
||||
if (authenticationRequest instanceof AbstractAuthenticationToken) {
|
||||
((AbstractAuthenticationToken) authenticationRequest).setDetails(
|
||||
this.authenticationDetailsSource.buildDetails(request));
|
||||
}
|
||||
if (authenticationRequest != null) {
|
||||
Authentication authenticationResult = this.authenticationManager.authenticate(authenticationRequest);
|
||||
this.authenticationSuccessHandler.onAuthenticationSuccess(request, response, authenticationResult);
|
||||
|
||||
@@ -29,6 +29,8 @@ import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.http.server.ServletServerHttpResponse;
|
||||
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
||||
import org.springframework.security.authentication.AuthenticationDetailsSource;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
@@ -53,6 +55,7 @@ import org.springframework.security.oauth2.server.authorization.web.authenticati
|
||||
import org.springframework.security.web.authentication.AuthenticationConverter;
|
||||
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
|
||||
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
|
||||
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
|
||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -101,6 +104,8 @@ public class OAuth2TokenEndpointFilter extends OncePerRequestFilter {
|
||||
new OAuth2AccessTokenResponseHttpMessageConverter();
|
||||
private final HttpMessageConverter<OAuth2Error> errorHttpResponseConverter =
|
||||
new OAuth2ErrorHttpMessageConverter();
|
||||
private final AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource =
|
||||
new WebAuthenticationDetailsSource();
|
||||
private AuthenticationConverter authenticationConverter;
|
||||
private AuthenticationSuccessHandler authenticationSuccessHandler = this::sendAccessTokenResponse;
|
||||
private AuthenticationFailureHandler authenticationFailureHandler = this::sendErrorResponse;
|
||||
@@ -151,6 +156,10 @@ public class OAuth2TokenEndpointFilter extends OncePerRequestFilter {
|
||||
if (authorizationGrantAuthentication == null) {
|
||||
throwError(OAuth2ErrorCodes.UNSUPPORTED_GRANT_TYPE, OAuth2ParameterNames.GRANT_TYPE);
|
||||
}
|
||||
if (authorizationGrantAuthentication instanceof AbstractAuthenticationToken) {
|
||||
((AbstractAuthenticationToken) authorizationGrantAuthentication)
|
||||
.setDetails(this.authenticationDetailsSource.buildDetails(request));
|
||||
}
|
||||
|
||||
OAuth2AccessTokenAuthenticationToken accessTokenAuthentication =
|
||||
(OAuth2AccessTokenAuthenticationToken) this.authenticationManager.authenticate(authorizationGrantAuthentication);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020 the original author or authors.
|
||||
* Copyright 2020-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -15,9 +15,15 @@
|
||||
*/
|
||||
package org.springframework.security.oauth2.server.authorization.web;
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
@@ -36,15 +42,13 @@ import org.springframework.security.oauth2.server.authorization.authentication.O
|
||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
||||
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
|
||||
import org.springframework.security.web.authentication.AuthenticationConverter;
|
||||
import org.springframework.security.web.authentication.WebAuthenticationDetails;
|
||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.assertj.core.api.InstanceOfAssertFactories.type;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
@@ -184,6 +188,8 @@ public class OAuth2ClientAuthenticationFilterTests {
|
||||
|
||||
@Test
|
||||
public void doFilterWhenRequestMatchesAndValidCredentialsThenProcessed() throws Exception {
|
||||
final String remoteAddress = "remote-address";
|
||||
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||
when(this.authenticationConverter.convert(any(HttpServletRequest.class))).thenReturn(
|
||||
new OAuth2ClientAuthenticationToken(registeredClient.getClientId(), registeredClient.getClientSecret(), ClientAuthenticationMethod.BASIC, null));
|
||||
@@ -192,6 +198,7 @@ public class OAuth2ClientAuthenticationFilterTests {
|
||||
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("POST", this.filterProcessesUrl);
|
||||
request.setServletPath(this.filterProcessesUrl);
|
||||
request.setRemoteAddr(remoteAddress);
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
FilterChain filterChain = mock(FilterChain.class);
|
||||
|
||||
@@ -202,6 +209,16 @@ public class OAuth2ClientAuthenticationFilterTests {
|
||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
assertThat(authentication).isInstanceOf(OAuth2ClientAuthenticationToken.class);
|
||||
assertThat(((OAuth2ClientAuthenticationToken) authentication).getRegisteredClient()).isEqualTo(registeredClient);
|
||||
|
||||
ArgumentCaptor<OAuth2ClientAuthenticationToken> authenticationRequestCaptor =
|
||||
ArgumentCaptor.forClass(OAuth2ClientAuthenticationToken.class);
|
||||
verify(this.authenticationManager).authenticate(authenticationRequestCaptor.capture());
|
||||
assertThat(authenticationRequestCaptor)
|
||||
.extracting(ArgumentCaptor::getValue)
|
||||
.extracting(OAuth2ClientAuthenticationToken::getDetails)
|
||||
.asInstanceOf(type(WebAuthenticationDetails.class))
|
||||
.extracting(WebAuthenticationDetails::getRemoteAddress)
|
||||
.isEqualTo(remoteAddress);
|
||||
}
|
||||
|
||||
private OAuth2Error readError(MockHttpServletResponse response) throws Exception {
|
||||
|
||||
@@ -60,11 +60,13 @@ import org.springframework.security.oauth2.server.authorization.client.TestRegis
|
||||
import org.springframework.security.web.authentication.AuthenticationConverter;
|
||||
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
|
||||
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
|
||||
import org.springframework.security.web.authentication.WebAuthenticationDetails;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.assertj.core.api.Assertions.entry;
|
||||
import static org.assertj.core.api.InstanceOfAssertFactories.type;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
@@ -79,6 +81,7 @@ import static org.mockito.Mockito.when;
|
||||
* @author Daniel Garnier-Moiroux
|
||||
*/
|
||||
public class OAuth2TokenEndpointFilterTests {
|
||||
private static final String REMOTE_ADDRESS = "remote-address";
|
||||
private AuthenticationManager authenticationManager;
|
||||
private OAuth2TokenEndpointFilter filter;
|
||||
private final HttpMessageConverter<OAuth2Error> errorHttpResponseConverter =
|
||||
@@ -260,6 +263,10 @@ public class OAuth2TokenEndpointFilterTests {
|
||||
request.getParameter(OAuth2ParameterNames.REDIRECT_URI));
|
||||
assertThat(authorizationCodeAuthentication.getAdditionalParameters())
|
||||
.containsExactly(entry("custom-param-1", "custom-value-1"));
|
||||
assertThat(authorizationCodeAuthentication.getDetails())
|
||||
.asInstanceOf(type(WebAuthenticationDetails.class))
|
||||
.extracting(WebAuthenticationDetails::getRemoteAddress)
|
||||
.isEqualTo(REMOTE_ADDRESS);
|
||||
|
||||
assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value());
|
||||
OAuth2AccessTokenResponse accessTokenResponse = readAccessTokenResponse(response);
|
||||
@@ -322,6 +329,10 @@ public class OAuth2TokenEndpointFilterTests {
|
||||
assertThat(clientCredentialsAuthentication.getScopes()).isEqualTo(registeredClient.getScopes());
|
||||
assertThat(clientCredentialsAuthentication.getAdditionalParameters())
|
||||
.containsExactly(entry("custom-param-1", "custom-value-1"));
|
||||
assertThat(clientCredentialsAuthentication.getDetails())
|
||||
.asInstanceOf(type(WebAuthenticationDetails.class))
|
||||
.extracting(WebAuthenticationDetails::getRemoteAddress)
|
||||
.isEqualTo(REMOTE_ADDRESS);
|
||||
|
||||
assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value());
|
||||
OAuth2AccessTokenResponse accessTokenResponse = readAccessTokenResponse(response);
|
||||
@@ -404,6 +415,11 @@ public class OAuth2TokenEndpointFilterTests {
|
||||
assertThat(refreshTokenAuthenticationToken.getScopes()).isEqualTo(registeredClient.getScopes());
|
||||
assertThat(refreshTokenAuthenticationToken.getAdditionalParameters())
|
||||
.containsExactly(entry("custom-param-1", "custom-value-1"));
|
||||
assertThat(refreshTokenAuthenticationToken.getDetails())
|
||||
.asInstanceOf(type(WebAuthenticationDetails.class))
|
||||
.extracting(WebAuthenticationDetails::getRemoteAddress)
|
||||
.isEqualTo(REMOTE_ADDRESS);
|
||||
|
||||
|
||||
assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value());
|
||||
OAuth2AccessTokenResponse accessTokenResponse = readAccessTokenResponse(response);
|
||||
@@ -536,6 +552,7 @@ public class OAuth2TokenEndpointFilterTests {
|
||||
String requestUri = OAuth2TokenEndpointFilter.DEFAULT_TOKEN_ENDPOINT_URI;
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("POST", requestUri);
|
||||
request.setServletPath(requestUri);
|
||||
request.setRemoteAddr(REMOTE_ADDRESS);
|
||||
|
||||
request.addParameter(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.AUTHORIZATION_CODE.getValue());
|
||||
request.addParameter(OAuth2ParameterNames.CODE, "code");
|
||||
@@ -551,6 +568,7 @@ public class OAuth2TokenEndpointFilterTests {
|
||||
String requestUri = OAuth2TokenEndpointFilter.DEFAULT_TOKEN_ENDPOINT_URI;
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("POST", requestUri);
|
||||
request.setServletPath(requestUri);
|
||||
request.setRemoteAddr(REMOTE_ADDRESS);
|
||||
|
||||
request.addParameter(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.CLIENT_CREDENTIALS.getValue());
|
||||
request.addParameter(OAuth2ParameterNames.SCOPE,
|
||||
@@ -564,6 +582,7 @@ public class OAuth2TokenEndpointFilterTests {
|
||||
String requestUri = OAuth2TokenEndpointFilter.DEFAULT_TOKEN_ENDPOINT_URI;
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("POST", requestUri);
|
||||
request.setServletPath(requestUri);
|
||||
request.setRemoteAddr(REMOTE_ADDRESS);
|
||||
|
||||
request.addParameter(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.REFRESH_TOKEN.getValue());
|
||||
request.addParameter(OAuth2ParameterNames.REFRESH_TOKEN, "refresh-token");
|
||||
|
||||
Reference in New Issue
Block a user