PasswordEncoder as Bean default for XML

Issue: gh-4873
This commit is contained in:
Rob Winch 2017-11-21 15:31:14 -06:00
parent f558b5016c
commit 9afee9e4e2
7 changed files with 177 additions and 31 deletions

View File

@ -26,6 +26,7 @@ import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider; import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.BeanIds; import org.springframework.security.config.BeanIds;
import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import java.util.Arrays; import java.util.Arrays;
@ -49,21 +50,24 @@ public class AuthenticationManagerFactoryBean implements
return (AuthenticationManager) bf.getBean(BeanIds.AUTHENTICATION_MANAGER); return (AuthenticationManager) bf.getBean(BeanIds.AUTHENTICATION_MANAGER);
} }
catch (NoSuchBeanDefinitionException e) { catch (NoSuchBeanDefinitionException e) {
if (BeanIds.AUTHENTICATION_MANAGER.equals(e.getBeanName())) { if (!BeanIds.AUTHENTICATION_MANAGER.equals(e.getBeanName())) {
try { throw e;
UserDetailsService uds = bf.getBean(UserDetailsService.class);
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setUserDetailsService(uds);
provider.afterPropertiesSet();
return new ProviderManager(
Arrays.<AuthenticationProvider> asList(provider));
}
catch (NoSuchBeanDefinitionException noUds) {
}
throw new NoSuchBeanDefinitionException(BeanIds.AUTHENTICATION_MANAGER,
MISSING_BEAN_ERROR_MESSAGE);
} }
throw e;
UserDetailsService uds = getBeanOrNull(UserDetailsService.class);
if(uds == null) {
throw new NoSuchBeanDefinitionException(BeanIds.AUTHENTICATION_MANAGER,
MISSING_BEAN_ERROR_MESSAGE);
}
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setUserDetailsService(uds);
PasswordEncoder passwordEncoder = getBeanOrNull(PasswordEncoder.class);
if (passwordEncoder != null) {
provider.setPasswordEncoder(passwordEncoder);
}
provider.afterPropertiesSet();
return new ProviderManager(Arrays.<AuthenticationProvider> asList(provider));
} }
} }
@ -79,4 +83,11 @@ public class AuthenticationManagerFactoryBean implements
bf = beanFactory; bf = beanFactory;
} }
private <T> T getBeanOrNull(Class<T> type) {
try {
return this.bf.getBean(type);
} catch (NoSuchBeanDefinitionException noUds) {
return null;
}
}
} }

View File

@ -15,6 +15,7 @@
*/ */
package org.springframework.security.config.authentication; package org.springframework.security.config.authentication;
import org.springframework.beans.BeanMetadataElement;
import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.RuntimeBeanReference; import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.beans.factory.support.RootBeanDefinition;
@ -36,17 +37,16 @@ public class AuthenticationProviderBeanDefinitionParser implements BeanDefinitio
private static final String ATT_USER_DETAILS_REF = "user-service-ref"; private static final String ATT_USER_DETAILS_REF = "user-service-ref";
public BeanDefinition parse(Element element, ParserContext pc) { public BeanDefinition parse(Element element, ParserContext pc) {
RootBeanDefinition authProvider = new RootBeanDefinition( RootBeanDefinition authProvider = new RootBeanDefinition(DaoAuthenticationProvider.class);
DaoAuthenticationProvider.class);
authProvider.setSource(pc.extractSource(element)); authProvider.setSource(pc.extractSource(element));
Element passwordEncoderElt = DomUtils.getChildElementByTagName(element, Element passwordEncoderElt = DomUtils.getChildElementByTagName(element, Elements.PASSWORD_ENCODER);
Elements.PASSWORD_ENCODER);
if (passwordEncoderElt != null) { PasswordEncoderParser pep = new PasswordEncoderParser(passwordEncoderElt, pc);
PasswordEncoderParser pep = new PasswordEncoderParser(passwordEncoderElt, pc); BeanMetadataElement passwordEncoder = pep.getPasswordEncoder();
authProvider.getPropertyValues().addPropertyValue("passwordEncoder", if (passwordEncoder != null) {
pep.getPasswordEncoder()); authProvider.getPropertyValues()
.addPropertyValue("passwordEncoder", passwordEncoder);
} }
Element userServiceElt = DomUtils.getChildElementByTagName(element, Element userServiceElt = DomUtils.getChildElementByTagName(element,

View File

@ -56,6 +56,12 @@ public class PasswordEncoderParser {
} }
private void parse(Element element, ParserContext parserContext) { private void parse(Element element, ParserContext parserContext) {
if (element == null) {
if (parserContext.getRegistry().containsBeanDefinition("passwordEncoder")) {
this.passwordEncoder = parserContext.getRegistry().getBeanDefinition("passwordEncoder");
}
return;
}
String hash = element.getAttribute(ATT_HASH); String hash = element.getAttribute(ATT_HASH);
boolean useBase64 = false; boolean useBase64 = false;

View File

@ -15,25 +15,27 @@
*/ */
package org.springframework.security.config.authentication; package org.springframework.security.config.authentication;
import static org.assertj.core.api.Assertions.*;
import java.util.ArrayList;
import java.util.List;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener; import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.AbstractXmlApplicationContext;
import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.DefaultAuthenticationEventPublisher; import org.springframework.security.authentication.DefaultAuthenticationEventPublisher;
import org.springframework.security.authentication.ProviderManager; import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.event.AbstractAuthenticationEvent; import org.springframework.security.authentication.event.AbstractAuthenticationEvent;
import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestRule; import org.springframework.security.config.test.SpringTestRule;
import org.springframework.security.config.util.InMemoryXmlApplicationContext;
import org.springframework.security.util.FieldUtils; import org.springframework.security.util.FieldUtils;
import org.springframework.test.web.servlet.MockMvc;
import java.util.ArrayList;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/** /**
* *
@ -44,7 +46,8 @@ public class AuthenticationManagerBeanDefinitionParserTests {
+ " <authentication-provider>" + " <authentication-provider>"
+ " <user-service>" + " <user-service>"
+ " <user name='bob' password='{noop}bobspassword' authorities='ROLE_A,ROLE_B' />" + " <user name='bob' password='{noop}bobspassword' authorities='ROLE_A,ROLE_B' />"
+ " </user-service>" + " </authentication-provider>" + " </user-service>"
+ " </authentication-provider>"
+ "</authentication-manager>"; + "</authentication-manager>";
@Rule @Rule
public final SpringTestRule spring = new SpringTestRule(); public final SpringTestRule spring = new SpringTestRule();
@ -92,6 +95,23 @@ public class AuthenticationManagerBeanDefinitionParserTests {
assertThat(pm.isEraseCredentialsAfterAuthentication()).isFalse(); assertThat(pm.isEraseCredentialsAfterAuthentication()).isFalse();
} }
@Autowired
MockMvc mockMvc;
@Test
public void passwordEncoderBeanUsed() throws Exception {
this.spring.context("<b:bean id='passwordEncoder' class='org.springframework.security.crypto.password.NoOpPasswordEncoder' factory-method='getInstance'/>"
+ "<user-service>"
+ " <user name='user' password='password' authorities='ROLE_A,ROLE_B' />"
+ "</user-service>"
+ "<http/>")
.mockMvcAfterSpringSecurityOk()
.autowire();
this.mockMvc.perform(get("/").with(httpBasic("user", "password")))
.andExpect(status().isOk());
}
private static class AuthListener implements private static class AuthListener implements
ApplicationListener<AbstractAuthenticationEvent> { ApplicationListener<AbstractAuthenticationEvent> {
List<AbstractAuthenticationEvent> events = new ArrayList<AbstractAuthenticationEvent>(); List<AbstractAuthenticationEvent> events = new ArrayList<AbstractAuthenticationEvent>();

View File

@ -0,0 +1,60 @@
/*
* Copyright 2002-2017 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.authentication;
import org.junit.Rule;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.config.test.SpringTestRule;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* @author Rob Winch
* @since 5.0
*/
public class PasswordEncoderParserTests {
@Rule
public final SpringTestRule spring = new SpringTestRule();
@Autowired
MockMvc mockMvc;
@Test
public void passwordEncoderDefaultsToDelegatingPasswordEncoder() throws Exception {
this.spring.configLocations("classpath:org/springframework/security/config/authentication/PasswordEncoderParserTests-default.xml")
.mockMvcAfterSpringSecurityOk()
.autowire();
this.mockMvc.perform(get("/").with(httpBasic("user", "password")))
.andExpect(status().isOk());
}
@Test
public void passwordEncoderDefaultsToPasswordEncoderBean() throws Exception {
this.spring.configLocations("classpath:org/springframework/security/config/authentication/PasswordEncoderParserTests-bean.xml")
.mockMvcAfterSpringSecurityOk()
.autowire();
this.mockMvc.perform(get("/").with(httpBasic("user", "password")))
.andExpect(status().isOk());
}
}

View File

@ -0,0 +1,34 @@
<!--
~ Copyright 2002-2017 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.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<b:beans xmlns="http://www.springframework.org/schema/security"
xmlns:b="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd">
<b:bean id="passwordEncoder" class="org.springframework.security.crypto.password.NoOpPasswordEncoder" factory-method="getInstance"/>
<http />
<authentication-manager>
<authentication-provider>
<user-service>
<user name="user" password="password" authorities="ROLE_USER" />
</user-service>
</authentication-provider>
</authentication-manager>
</b:beans>

View File

@ -0,0 +1,15 @@
<b:beans xmlns="http://www.springframework.org/schema/security"
xmlns:b="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd">
<http />
<authentication-manager>
<authentication-provider>
<user-service>
<user name="user" password="{noop}password" authorities="ROLE_USER" />
</user-service>
</authentication-provider>
</authentication-manager>
</b:beans>