Browse Source

Set details on authentication token created by HttpServlet3RequestFactory

Currently the login mechanism when triggered by executing HttpServlet3RequestFactory#login does not set any details on the underlying authentication token that is authenticated.

This change adds an AuthenticationDetailsSource on the HttpServlet3RequestFactory, which defaults to a WebAuthenticationDetailsSource.

Closes gh-9579
Karl Tinawi 4 years ago
parent
commit
c57fc309c2

+ 6 - 0
config/src/main/java/org/springframework/security/config/annotation/web/configurers/ServletApiConfigurer.java

@@ -21,6 +21,7 @@ import java.util.List;
 import jakarta.servlet.http.HttpServletRequest;
 
 import org.springframework.context.ApplicationContext;
+import org.springframework.security.authentication.AuthenticationDetailsSource;
 import org.springframework.security.authentication.AuthenticationManager;
 import org.springframework.security.authentication.AuthenticationTrustResolver;
 import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
@@ -90,6 +91,11 @@ public final class ServletApiConfigurer<H extends HttpSecurityBuilder<H>>
 		if (trustResolver != null) {
 			this.securityContextRequestFilter.setTrustResolver(trustResolver);
 		}
+		AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource = http
+				.getSharedObject(AuthenticationDetailsSource.class);
+		if (authenticationDetailsSource != null) {
+			this.securityContextRequestFilter.setAuthenticationDetailsSource(authenticationDetailsSource);
+		}
 		ApplicationContext context = http.getSharedObject(ApplicationContext.class);
 		if (context != null) {
 			String[] grantedAuthorityDefaultsBeanNames = context.getBeanNamesForType(GrantedAuthorityDefaults.class);

+ 26 - 0
config/src/test/java/org/springframework/security/config/annotation/web/configurers/ServletApiConfigurerTests.java

@@ -30,6 +30,7 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.security.access.AccessDeniedException;
+import org.springframework.security.authentication.AuthenticationDetailsSource;
 import org.springframework.security.authentication.AuthenticationManager;
 import org.springframework.security.authentication.AuthenticationTrustResolver;
 import org.springframework.security.authentication.TestingAuthenticationToken;
@@ -149,6 +150,15 @@ public class ServletApiConfigurerTests {
 		verify(SharedTrustResolverConfig.TR, atLeastOnce()).isAnonymous(any());
 	}
 
+	@Test
+	public void configureWhenSharedObjectAuthenticationDetailsSourceThenAuthenticationDetailsSourceUsed() {
+		this.spring.register(SharedAuthenticationDetailsSourceConfig.class).autowire();
+		SecurityContextHolderAwareRequestFilter scaFilter = getFilter(SecurityContextHolderAwareRequestFilter.class);
+		AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource = getFieldValue(scaFilter,
+				"authenticationDetailsSource");
+		assertThat(authenticationDetailsSource).isEqualTo(SharedAuthenticationDetailsSourceConfig.ADS);
+	}
+
 	@Test
 	public void requestWhenServletApiWithDefaultsInLambdaThenUsesDefaultRolePrefix() throws Exception {
 		this.spring.register(ServletApiWithDefaultsInLambdaConfig.class, AdminController.class).autowire();
@@ -321,6 +331,22 @@ public class ServletApiConfigurerTests {
 
 	}
 
+	@EnableWebSecurity
+	static class SharedAuthenticationDetailsSourceConfig extends WebSecurityConfigurerAdapter {
+
+		@SuppressWarnings("unchecked")
+		static AuthenticationDetailsSource<HttpServletRequest, ?> ADS = spy(AuthenticationDetailsSource.class);
+
+		@Override
+		protected void configure(HttpSecurity http) {
+			// @formatter:off
+			http
+				.setSharedObject(AuthenticationDetailsSource.class, ADS);
+			// @formatter:on
+		}
+
+	}
+
 	@EnableWebSecurity
 	static class ServletApiWithDefaultsInLambdaConfig extends WebSecurityConfigurerAdapter {
 

+ 21 - 1
web/src/main/java/org/springframework/security/web/servletapi/HttpServlet3RequestFactory.java

@@ -32,6 +32,7 @@ import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 
 import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
+import org.springframework.security.authentication.AuthenticationDetailsSource;
 import org.springframework.security.authentication.AuthenticationManager;
 import org.springframework.security.authentication.AuthenticationTrustResolver;
 import org.springframework.security.authentication.AuthenticationTrustResolverImpl;
@@ -42,6 +43,7 @@ import org.springframework.security.core.AuthenticationException;
 import org.springframework.security.core.context.SecurityContext;
 import org.springframework.security.core.context.SecurityContextHolder;
 import org.springframework.security.web.AuthenticationEntryPoint;
+import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
 import org.springframework.security.web.authentication.logout.LogoutHandler;
 import org.springframework.util.Assert;
 import org.springframework.util.CollectionUtils;
@@ -79,6 +81,8 @@ final class HttpServlet3RequestFactory implements HttpServletRequestFactory {
 
 	private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();
 
+	private AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource = new WebAuthenticationDetailsSource();
+
 	private AuthenticationEntryPoint authenticationEntryPoint;
 
 	private AuthenticationManager authenticationManager;
@@ -158,6 +162,18 @@ final class HttpServlet3RequestFactory implements HttpServletRequestFactory {
 		this.trustResolver = trustResolver;
 	}
 
+	/**
+	 * Sets the {@link AuthenticationDetailsSource} to be used. The default is
+	 * {@link WebAuthenticationDetailsSource}.
+	 * @param authenticationDetailsSource the {@link AuthenticationDetailsSource} to use.
+	 * Cannot be null.
+	 */
+	void setAuthenticationDetailsSource(
+			AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource) {
+		Assert.notNull(authenticationDetailsSource, "authenticationDetailsSource cannot be null");
+		this.authenticationDetailsSource = authenticationDetailsSource;
+	}
+
 	@Override
 	public HttpServletRequest create(HttpServletRequest request, HttpServletResponse response) {
 		return new Servlet3SecurityContextHolderAwareRequestWrapper(request, this.rolePrefix, response);
@@ -233,7 +249,11 @@ final class HttpServlet3RequestFactory implements HttpServletRequestFactory {
 		private Authentication getAuthentication(AuthenticationManager authManager, String username, String password)
 				throws ServletException {
 			try {
-				return authManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
+				UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(username,
+						password);
+				Object details = HttpServlet3RequestFactory.this.authenticationDetailsSource.buildDetails(this);
+				authentication.setDetails(details);
+				return authManager.authenticate(authentication);
 			}
 			catch (AuthenticationException ex) {
 				SecurityContextHolder.clearContext();

+ 18 - 0
web/src/main/java/org/springframework/security/web/servletapi/SecurityContextHolderAwareRequestFilter.java

@@ -27,12 +27,14 @@ import jakarta.servlet.ServletResponse;
 import jakarta.servlet.http.HttpServletRequest;
 import jakarta.servlet.http.HttpServletResponse;
 
+import org.springframework.security.authentication.AuthenticationDetailsSource;
 import org.springframework.security.authentication.AuthenticationManager;
 import org.springframework.security.authentication.AuthenticationTrustResolver;
 import org.springframework.security.authentication.AuthenticationTrustResolverImpl;
 import org.springframework.security.core.context.SecurityContext;
 import org.springframework.security.core.context.SecurityContextHolder;
 import org.springframework.security.web.AuthenticationEntryPoint;
+import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
 import org.springframework.security.web.authentication.logout.LogoutHandler;
 import org.springframework.util.Assert;
 import org.springframework.web.filter.GenericFilterBean;
@@ -80,6 +82,8 @@ public class SecurityContextHolderAwareRequestFilter extends GenericFilterBean {
 
 	private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();
 
+	private AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource = new WebAuthenticationDetailsSource();
+
 	public void setRolePrefix(String rolePrefix) {
 		Assert.notNull(rolePrefix, "Role prefix must not be null");
 		this.rolePrefix = rolePrefix;
@@ -172,9 +176,23 @@ public class SecurityContextHolderAwareRequestFilter extends GenericFilterBean {
 		updateFactory();
 	}
 
+	/**
+	 * Sets the {@link AuthenticationDetailsSource} to be used. The default is
+	 * {@link WebAuthenticationDetailsSource}.
+	 * @param authenticationDetailsSource the {@link AuthenticationDetailsSource} to use.
+	 * Cannot be null.
+	 */
+	public void setAuthenticationDetailsSource(
+			AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource) {
+		Assert.notNull(authenticationDetailsSource, "authenticationDetailsSource cannot be null");
+		this.authenticationDetailsSource = authenticationDetailsSource;
+		updateFactory();
+	}
+
 	private HttpServletRequestFactory createServlet3Factory(String rolePrefix) {
 		HttpServlet3RequestFactory factory = new HttpServlet3RequestFactory(rolePrefix);
 		factory.setTrustResolver(this.trustResolver);
+		factory.setAuthenticationDetailsSource(this.authenticationDetailsSource);
 		factory.setAuthenticationEntryPoint(this.authenticationEntryPoint);
 		factory.setAuthenticationManager(this.authenticationManager);
 		factory.setLogoutHandlers(this.logoutHandlers);