diff --git a/ldap/src/integration-test/java/org/springframework/security/ldap/userdetails/LdapUserDetailsManagerTests.java b/ldap/src/integration-test/java/org/springframework/security/ldap/userdetails/LdapUserDetailsManagerTests.java index db29d64880..7dbb621690 100644 --- a/ldap/src/integration-test/java/org/springframework/security/ldap/userdetails/LdapUserDetailsManagerTests.java +++ b/ldap/src/integration-test/java/org/springframework/security/ldap/userdetails/LdapUserDetailsManagerTests.java @@ -31,6 +31,8 @@ import org.springframework.security.authentication.UsernamePasswordAuthenticatio import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.context.SecurityContextHolderStrategy; +import org.springframework.security.core.context.SecurityContextImpl; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.ldap.ApacheDsContainerConfig; import org.springframework.security.ldap.DefaultLdapUsernameToDnMapper; @@ -40,6 +42,9 @@ import org.springframework.test.context.junit.jupiter.SpringExtension; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; /** * @author Luke Taylor @@ -201,6 +206,30 @@ public class LdapUserDetailsManagerTests { .isTrue(); } + @Test + public void testPasswordChangeUsesCustomSecurityContextHolderStrategy() { + InetOrgPerson.Essence p = new InetOrgPerson.Essence(); + p.setDn("whocares"); + p.setCn(new String[] { "John Yossarian" }); + p.setSn("Yossarian"); + p.setUid("johnyossarian"); + p.setPassword("yossarianspassword"); + p.setAuthorities(TEST_AUTHORITIES); + + this.mgr.createUser(p.createUserDetails()); + + SecurityContextHolderStrategy strategy = mock(SecurityContextHolderStrategy.class); + given(strategy.getContext()).willReturn(new SecurityContextImpl(UsernamePasswordAuthenticationToken + .authenticated("johnyossarian", "yossarianspassword", TEST_AUTHORITIES))); + this.mgr.setSecurityContextHolderStrategy(strategy); + + this.mgr.changePassword("yossarianspassword", "yossariansnewpassword"); + + assertThat(this.template.compare("uid=johnyossarian,ou=test people", "userPassword", "yossariansnewpassword")) + .isTrue(); + verify(strategy).getContext(); + } + @Test public void testPasswordChangeWithWrongOldPasswordFails() { InetOrgPerson.Essence p = new InetOrgPerson.Essence(); diff --git a/ldap/src/main/java/org/springframework/security/ldap/authentication/SpringSecurityAuthenticationSource.java b/ldap/src/main/java/org/springframework/security/ldap/authentication/SpringSecurityAuthenticationSource.java index 0db213cfaa..0d0b457d81 100644 --- a/ldap/src/main/java/org/springframework/security/ldap/authentication/SpringSecurityAuthenticationSource.java +++ b/ldap/src/main/java/org/springframework/security/ldap/authentication/SpringSecurityAuthenticationSource.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2022 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. @@ -23,7 +23,9 @@ import org.springframework.ldap.core.AuthenticationSource; import org.springframework.security.authentication.AnonymousAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.context.SecurityContextHolderStrategy; import org.springframework.security.ldap.userdetails.LdapUserDetails; +import org.springframework.util.Assert; /** * An AuthenticationSource to retrieve authentication information stored in Spring @@ -40,13 +42,16 @@ public class SpringSecurityAuthenticationSource implements AuthenticationSource private static final Log log = LogFactory.getLog(SpringSecurityAuthenticationSource.class); + private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder + .getContextHolderStrategy(); + /** * Get the principals of the logged in user, in this case the distinguished name. * @return the distinguished name of the logged in user. */ @Override public String getPrincipal() { - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + Authentication authentication = this.securityContextHolderStrategy.getContext().getAuthentication(); if (authentication == null) { log.debug("Returning empty String as Principal since authentication is null"); return ""; @@ -69,7 +74,7 @@ public class SpringSecurityAuthenticationSource implements AuthenticationSource */ @Override public String getCredentials() { - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + Authentication authentication = this.securityContextHolderStrategy.getContext().getAuthentication(); if (authentication == null) { log.debug("Returning empty String as Credentials since authentication is null"); return ""; @@ -77,4 +82,15 @@ public class SpringSecurityAuthenticationSource implements AuthenticationSource return (String) authentication.getCredentials(); } + /** + * Sets the {@link SecurityContextHolderStrategy} to use. The default action is to use + * the {@link SecurityContextHolderStrategy} stored in {@link SecurityContextHolder}. + * + * @since 5.8 + */ + public void setSecurityContextHolderStrategy(SecurityContextHolderStrategy securityContextHolderStrategy) { + Assert.notNull(securityContextHolderStrategy, "securityContextHolderStrategy cannot be null"); + this.securityContextHolderStrategy = securityContextHolderStrategy; + } + } diff --git a/ldap/src/main/java/org/springframework/security/ldap/userdetails/LdapUserDetailsManager.java b/ldap/src/main/java/org/springframework/security/ldap/userdetails/LdapUserDetailsManager.java index b2baff26a3..e8d24c8f1f 100644 --- a/ldap/src/main/java/org/springframework/security/ldap/userdetails/LdapUserDetailsManager.java +++ b/ldap/src/main/java/org/springframework/security/ldap/userdetails/LdapUserDetailsManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2022 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. @@ -54,6 +54,7 @@ import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.context.SecurityContextHolderStrategy; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.ldap.DefaultLdapUsernameToDnMapper; @@ -82,6 +83,9 @@ public class LdapUserDetailsManager implements UserDetailsManager { private final Log logger = LogFactory.getLog(LdapUserDetailsManager.class); + private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder + .getContextHolderStrategy(); + /** * The strategy for mapping usernames to LDAP distinguished names. This will be used * when building DNs for creating new users etc. @@ -179,7 +183,7 @@ public class LdapUserDetailsManager implements UserDetailsManager { */ @Override public void changePassword(final String oldPassword, final String newPassword) { - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + Authentication authentication = this.securityContextHolderStrategy.getContext().getAuthentication(); Assert.notNull(authentication, "No authentication object found in security context. Can't change current user's password!"); String username = authentication.getName(); @@ -388,6 +392,17 @@ public class LdapUserDetailsManager implements UserDetailsManager { this.usePasswordModifyExtensionOperation = usePasswordModifyExtensionOperation; } + /** + * Sets the {@link SecurityContextHolderStrategy} to use. The default action is to use + * the {@link SecurityContextHolderStrategy} stored in {@link SecurityContextHolder}. + * + * @since 5.8 + */ + public void setSecurityContextHolderStrategy(SecurityContextHolderStrategy securityContextHolderStrategy) { + Assert.notNull(securityContextHolderStrategy, "securityContextHolderStrategy cannot be null"); + this.securityContextHolderStrategy = securityContextHolderStrategy; + } + private void changePasswordUsingAttributeModification(DistinguishedName userDn, String oldPassword, String newPassword) { ModificationItem[] passwordChange = new ModificationItem[] { new ModificationItem(DirContext.REPLACE_ATTRIBUTE, diff --git a/ldap/src/test/java/org/springframework/security/ldap/SpringSecurityAuthenticationSourceTests.java b/ldap/src/test/java/org/springframework/security/ldap/SpringSecurityAuthenticationSourceTests.java index adb208572c..27a0dcb215 100644 --- a/ldap/src/test/java/org/springframework/security/ldap/SpringSecurityAuthenticationSourceTests.java +++ b/ldap/src/test/java/org/springframework/security/ldap/SpringSecurityAuthenticationSourceTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2022 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. @@ -26,11 +26,16 @@ import org.springframework.security.authentication.AnonymousAuthenticationToken; import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.context.SecurityContextHolderStrategy; +import org.springframework.security.core.context.SecurityContextImpl; import org.springframework.security.ldap.authentication.SpringSecurityAuthenticationSource; import org.springframework.security.ldap.userdetails.LdapUserDetailsImpl; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; /** * @author Luke Taylor @@ -83,4 +88,18 @@ public class SpringSecurityAuthenticationSourceTests { assertThat(source.getPrincipal()).isEqualTo("uid=joe,ou=users"); } + @Test + public void getPrincipalWhenCustomSecurityContextHolderStrategyThenExpectedPrincipalIsReturned() { + LdapUserDetailsImpl.Essence user = new LdapUserDetailsImpl.Essence(); + user.setUsername("joe"); + user.setDn(new DistinguishedName("uid=joe,ou=users")); + SecurityContextHolderStrategy strategy = mock(SecurityContextHolderStrategy.class); + given(strategy.getContext()) + .willReturn(new SecurityContextImpl(new TestingAuthenticationToken(user.createUserDetails(), null))); + SpringSecurityAuthenticationSource source = new SpringSecurityAuthenticationSource(); + source.setSecurityContextHolderStrategy(strategy); + assertThat(source.getPrincipal()).isEqualTo("uid=joe,ou=users"); + verify(strategy).getContext(); + } + }