From c57fc309c245de8171e67be1ef1ebb6c5b6b0d4c Mon Sep 17 00:00:00 2001 From: Karl Tinawi Date: Sun, 11 Apr 2021 15:13:13 +0100 Subject: [PATCH] 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 --- .../web/configurers/ServletApiConfigurer.java | 6 +++++ .../ServletApiConfigurerTests.java | 26 +++++++++++++++++++ .../HttpServlet3RequestFactory.java | 22 +++++++++++++++- ...curityContextHolderAwareRequestFilter.java | 18 +++++++++++++ 4 files changed, 71 insertions(+), 1 deletion(-) diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/ServletApiConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/ServletApiConfigurer.java index e41d323042..29482fba94 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/ServletApiConfigurer.java +++ b/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> if (trustResolver != null) { this.securityContextRequestFilter.setTrustResolver(trustResolver); } + AuthenticationDetailsSource 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); diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/ServletApiConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/ServletApiConfigurerTests.java index 4b48ce8ec8..a2167c5a13 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/ServletApiConfigurerTests.java +++ b/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 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 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 { diff --git a/web/src/main/java/org/springframework/security/web/servletapi/HttpServlet3RequestFactory.java b/web/src/main/java/org/springframework/security/web/servletapi/HttpServlet3RequestFactory.java index 360d4746b8..af5b4ea743 100644 --- a/web/src/main/java/org/springframework/security/web/servletapi/HttpServlet3RequestFactory.java +++ b/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 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 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(); diff --git a/web/src/main/java/org/springframework/security/web/servletapi/SecurityContextHolderAwareRequestFilter.java b/web/src/main/java/org/springframework/security/web/servletapi/SecurityContextHolderAwareRequestFilter.java index eeb4f4de82..a76bfb4e34 100644 --- a/web/src/main/java/org/springframework/security/web/servletapi/SecurityContextHolderAwareRequestFilter.java +++ b/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 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 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);