SEC-1953: Spring Security Java Config support

This is the initial migration of Spring Security Java Config from the
external project at
https://github.com/SpringSource/spring-security-javaconfig
This commit is contained in:
Rob Winch 2013-06-30 17:26:57 -05:00
parent fba4fec84b
commit d0c4e6ca72
140 changed files with 20023 additions and 12 deletions

View File

@ -15,12 +15,22 @@ dependencies {
// NB: Don't add other compile time dependencies to the config module as this breaks tooling
compile project(':spring-security-core'),
project(':spring-security-web'),
project(':spring-security-openid'),
project(':spring-security-ldap'),
"org.aspectj:aspectjweaver:$aspectjVersion",
'aopalliance:aopalliance:1.0',
"org.springframework:spring-aop:$springVersion",
"org.springframework:spring-context:$springVersion",
"org.springframework:spring-web:$springVersion",
"org.springframework:spring-beans:$springVersion"
"org.springframework:spring-beans:$springVersion",
"org.springframework:spring-jdbc:$springVersion",
"org.springframework:spring-tx:$springVersion",
"org.springframework.ldap:spring-ldap-core:$springLdapVersion"
compile('org.openid4java:openid4java-nodeps:0.9.6') {
exclude group: 'com.google.code.guice', module: 'guice'
}
compile 'com.google.inject:guice:2.0'
compile apacheds_libs
provided "org.apache.tomcat:tomcat-servlet-api:$servletApiVersion"
@ -28,6 +38,7 @@ dependencies {
testCompile project(':spring-security-ldap'),
project(':spring-security-openid'),
project(':spring-security-cas'),
project(':spring-security-core').sourceSets.test.output,
'javax.annotation:jsr250-api:1.0',
"org.springframework.ldap:spring-ldap-core:$springLdapVersion",

View File

@ -0,0 +1,177 @@
/*
* Copyright 2002-2013 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.annotation.authentication.ldap
import org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.ldap.core.ContextSource;
import org.springframework.ldap.core.support.BaseLdapPathContextSource;
import org.springframework.security.authentication.AuthenticationManager
import org.springframework.security.authentication.AuthenticationProvider
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
import org.springframework.security.config.annotation.BaseSpringSpec
import org.springframework.security.config.annotation.SecurityBuilder;
import org.springframework.security.config.annotation.authentication.AuthenticationManagerBuilder
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.ldap.DefaultSpringSecurityContextSource;
import org.springframework.security.ldap.authentication.LdapAuthenticationProvider;
import org.springframework.security.ldap.server.ApacheDSContainer;
import org.springframework.test.util.ReflectionTestUtils;
/**
*
* @author Rob Winch
*
*/
class LdapAuthenticationProviderBuilderSecurityBuilderTests extends BaseSpringSpec {
def "default configuration"() {
when:
loadConfig(DefaultLdapConfig)
LdapAuthenticationProvider provider = ldapProvider()
then:
provider.authoritiesPopulator.groupRoleAttribute == "cn"
provider.authoritiesPopulator.groupSearchBase == ""
provider.authoritiesPopulator.groupSearchFilter == "(uniqueMember={0})"
ReflectionTestUtils.getField(provider,"authoritiesMapper").prefix == "ROLE_"
}
@Configuration
static class DefaultLdapConfig extends BaseLdapProviderConfig {
protected void registerAuthentication(
AuthenticationManagerBuilder auth) throws Exception {
auth
.ldapAuthentication()
.contextSource(contextSource())
}
}
def "group roles custom"() {
when:
loadConfig(GroupRolesConfig)
LdapAuthenticationProvider provider = ldapProvider()
then:
provider.authoritiesPopulator.groupRoleAttribute == "group"
}
@Configuration
static class GroupRolesConfig extends BaseLdapProviderConfig {
protected void registerAuthentication(AuthenticationManagerBuilder auth) throws Exception {
auth
.ldapAuthentication()
.contextSource(contextSource())
.groupRoleAttribute("group")
}
}
def "group search custom"() {
when:
loadConfig(GroupSearchConfig)
LdapAuthenticationProvider provider = ldapProvider()
then:
provider.authoritiesPopulator.groupSearchFilter == "ou=groupName"
}
@Configuration
static class GroupSearchConfig extends BaseLdapProviderConfig {
protected void registerAuthentication(
AuthenticationManagerBuilder auth) throws Exception {
auth
.ldapAuthentication()
.contextSource(contextSource())
.groupSearchFilter("ou=groupName");
}
}
def "role prefix custom"() {
when:
loadConfig(RolePrefixConfig)
LdapAuthenticationProvider provider = ldapProvider()
then:
ReflectionTestUtils.getField(provider,"authoritiesMapper").prefix == "role_"
}
@Configuration
static class RolePrefixConfig extends BaseLdapProviderConfig {
protected void registerAuthentication(
AuthenticationManagerBuilder auth) throws Exception {
auth
.ldapAuthentication()
.contextSource(contextSource())
.rolePrefix("role_")
}
}
def "bind authentication"() {
when:
loadConfig(BindAuthenticationConfig)
AuthenticationManager auth = context.getBean(AuthenticationManager)
then:
auth
auth.authenticate(new UsernamePasswordAuthenticationToken("admin","password")).authorities.collect { it.authority }.sort() == ["ROLE_ADMIN","ROLE_USER"]
}
@Configuration
static class BindAuthenticationConfig extends BaseLdapServerConfig {
protected void registerAuthentication(
AuthenticationManagerBuilder auth) throws Exception {
auth
.ldapAuthentication()
.contextSource(contextSource())
.groupSearchBase("ou=groups")
.userDnPatterns("uid={0},ou=people");
}
}
def ldapProvider() {
context.getBean(AuthenticationManager).providers[0]
}
@Configuration
static abstract class BaseLdapServerConfig extends BaseLdapProviderConfig {
@Bean
public ApacheDSContainer ldapServer() throws Exception {
ApacheDSContainer apacheDSContainer = new ApacheDSContainer("dc=springframework,dc=org", "classpath:/users.ldif");
apacheDSContainer.setPort(33389);
return apacheDSContainer;
}
}
@Configuration
static abstract class BaseLdapProviderConfig {
@Bean
public AuthenticationManager authenticationManager() {
AuthenticationManagerBuilder registry = new AuthenticationManagerBuilder();
registerAuthentication(registry);
return registry.build();
}
protected abstract void registerAuthentication(
AuthenticationManagerBuilder auth) throws Exception;
@Bean
public BaseLdapPathContextSource contextSource() throws Exception {
DefaultSpringSecurityContextSource contextSource = new DefaultSpringSecurityContextSource(
"ldap://127.0.0.1:33389/dc=springframework,dc=org")
contextSource.userDn = "uid=admin,ou=system"
contextSource.password = "secret"
contextSource.afterPropertiesSet();
return contextSource;
}
}
}

View File

@ -0,0 +1,68 @@
/*
* Copyright 2002-2013 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.annotation.authentication.ldap
import static org.springframework.security.config.annotation.authentication.ldap.NamespaceLdapAuthenticationProviderTestsConfigs.*
import org.springframework.ldap.core.support.BaseLdapPathContextSource
import org.springframework.security.authentication.AuthenticationManager
import org.springframework.security.authentication.AuthenticationProvider
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
import org.springframework.security.config.annotation.BaseSpringSpec
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.authentication.ldap.NamespaceLdapAuthenticationProviderTestsConfigs.LdapAuthenticationProviderConfig;
import org.springframework.security.ldap.authentication.LdapAuthenticationProvider;
import org.springframework.security.ldap.authentication.PasswordComparisonAuthenticator;
import org.springframework.security.ldap.userdetails.PersonContextMapper;
import org.springframework.test.util.ReflectionTestUtils;
/**
*
* @author Rob Winch
*
*/
class NamespaceLdapAuthenticationProviderTests extends BaseSpringSpec {
def "ldap-authentication-provider"() {
when:
loadConfig(LdapAuthenticationProviderConfig)
then:
authenticationManager.authenticate(new UsernamePasswordAuthenticationToken("user","password")).authorities*.authority.sort() == ['ROLE_USER']
}
def "ldap-authentication-provider custom"() {
when:
loadConfig(CustomLdapAuthenticationProviderConfig)
LdapAuthenticationProvider provider = findAuthenticationProvider(LdapAuthenticationProvider)
then:
provider.authoritiesPopulator.groupRoleAttribute == "cn"
provider.authoritiesPopulator.groupSearchBase == "ou=groups"
provider.authoritiesPopulator.groupSearchFilter == "(member={0})"
ReflectionTestUtils.getField(provider,"authoritiesMapper").prefix == "PREFIX_"
provider.userDetailsContextMapper instanceof PersonContextMapper
provider.authenticator.getUserDns("user") == ["uid=user,ou=people"]
provider.authenticator.userSearch.searchBase == "ou=users"
provider.authenticator.userSearch.searchFilter == "(uid={0})"
}
def "ldap-authentication-provider password compare"() {
when:
loadConfig(PasswordCompareLdapConfig)
LdapAuthenticationProvider provider = findAuthenticationProvider(LdapAuthenticationProvider)
then:
provider.authenticator instanceof PasswordComparisonAuthenticator
authenticationManager.authenticate(new UsernamePasswordAuthenticationToken("user","password")).authorities*.authority.sort() == ['ROLE_USER']
}
}

View File

@ -0,0 +1,84 @@
/*
* Copyright 2002-2013 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.annotation.authentication.ldap;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.encoding.PlaintextPasswordEncoder;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.ldap.userdetails.PersonContextMapper;
/**
* @author Rob Winch
*
*/
public class NamespaceLdapAuthenticationProviderTestsConfigs {
@Configuration
@EnableWebSecurity
static class LdapAuthenticationProviderConfig extends WebSecurityConfigurerAdapter {
protected void registerAuthentication(
AuthenticationManagerBuilder auth) throws Exception {
auth
.ldapAuthentication()
.groupSearchBase("ou=groups")
.userDnPatterns("uid={0},ou=people"); // ldap-server@user-dn-pattern
}
}
@Configuration
@EnableWebSecurity
static class CustomLdapAuthenticationProviderConfig extends WebSecurityConfigurerAdapter {
protected void registerAuthentication(
AuthenticationManagerBuilder auth) throws Exception {
auth
.ldapAuthentication()
.groupRoleAttribute("cn") // ldap-authentication-provider@group-role-attribute
.groupSearchBase("ou=groups") // ldap-authentication-provider@group-search-base
.groupSearchFilter("(member={0})") // ldap-authentication-provider@group-search-filter
.rolePrefix("PREFIX_") // ldap-authentication-provider@group-search-filter
.userDetailsContextMapper(new PersonContextMapper()) // ldap-authentication-provider@user-context-mapper-ref / ldap-authentication-provider@user-details-class
.userDnPatterns("uid={0},ou=people") // ldap-authentication-provider@user-dn-pattern
.userSearchBase("ou=users") // ldap-authentication-provider@user-dn-pattern
.userSearchFilter("(uid={0})") // ldap-authentication-provider@user-search-filter
// .contextSource(contextSource) // ldap-authentication-provider@server-ref
.contextSource()
.ldif("classpath:user.ldif") // ldap-server@ldif
.managerDn("uid=admin,ou=system") // ldap-server@manager-dn
.managerPassword("secret") // ldap-server@manager-password
.port(33399) // ldap-server@port
.root("dc=springframework,dc=org") // ldap-server@root
// .url("ldap://localhost:33389/dc-springframework,dc=org") this overrides root and port and is used for external
;
}
}
@Configuration
@EnableWebSecurity
static class PasswordCompareLdapConfig extends WebSecurityConfigurerAdapter {
protected void registerAuthentication(
AuthenticationManagerBuilder auth) throws Exception {
auth
.ldapAuthentication()
.groupSearchBase("ou=groups")
.userSearchFilter("(uid={0})")
.passwordCompare()
.passwordEncoder(new PlaintextPasswordEncoder()) // ldap-authentication-provider/password-compare/password-encoder@ref
.passwordAttribute("userPassword"); // ldap-authentication-provider/password-compare@password-attribute
}
}
}

View File

@ -0,0 +1,42 @@
dn: ou=groups,dc=springframework,dc=org
objectclass: top
objectclass: organizationalUnit
ou: groups
dn: ou=people,dc=springframework,dc=org
objectclass: top
objectclass: organizationalUnit
ou: people
dn: uid=admin,ou=people,dc=springframework,dc=org
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
cn: Rod Johnson
sn: Johnson
uid: admin
userPassword: password
dn: uid=user,ou=people,dc=springframework,dc=org
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
cn: Dianne Emu
sn: Emu
uid: user
userPassword: password
dn: cn=user,ou=groups,dc=springframework,dc=org
objectclass: top
objectclass: groupOfNames
cn: user
uniqueMember: uid=admin,ou=people,dc=springframework,dc=org
uniqueMember: uid=user,ou=people,dc=springframework,dc=org
dn: cn=admin,ou=groups,dc=springframework,dc=org
objectclass: top
objectclass: groupOfNames
cn: admin
uniqueMember: uid=admin,ou=people,dc=springframework,dc=org

View File

@ -0,0 +1,432 @@
/*
* Copyright 2002-2013 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.annotation;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.util.Assert;
import org.springframework.web.filter.DelegatingFilterProxy;
import com.google.inject.internal.ImmutableList.Builder;
/**
* <p>A base {@link SecurityBuilder} that allows {@link SecurityConfigurer} to be
* applied to it. This makes modifying the {@link SecurityBuilder} a strategy
* that can be customized and broken up into a number of
* {@link SecurityConfigurer} objects that have more specific goals than that
* of the {@link SecurityBuilder}.</p>
*
* <p>For example, a {@link SecurityBuilder} may build an
* {@link DelegatingFilterProxy}, but a {@link SecurityConfigurer} might
* populate the {@link SecurityBuilder} with the filters necessary for session
* management, form based login, authorization, etc.</p>
*
* @see WebSecurity
*
* @author Rob Winch
*
* @param <O>
* The object that this builder returns
* @param <B>
* The type of this builder (that is returned by the base class)
*/
public abstract class AbstractConfiguredSecurityBuilder<O, B extends SecurityBuilder<O>> extends AbstractSecurityBuilder<O> {
private final LinkedHashMap<Class<? extends SecurityConfigurer<O, B>>, List<SecurityConfigurer<O, B>>> configurers =
new LinkedHashMap<Class<? extends SecurityConfigurer<O, B>>, List<SecurityConfigurer<O, B>>>();
private final Map<Class<Object>,Object> sharedObjects = new HashMap<Class<Object>,Object>();
private final boolean allowConfigurersOfSameType;
private BuildState buildState = BuildState.UNBUILT;
private ObjectPostProcessor<Object> objectPostProcessor;
/**
* Creates a new instance without post processing
*/
protected AbstractConfiguredSecurityBuilder() {
this(ObjectPostProcessor.QUIESCENT_POSTPROCESSOR);
}
/***
* Creates a new instance with the provided {@link ObjectPostProcessor}.
* This post processor must support Object since there are many types of
* objects that may be post processed.
*
* @param objectPostProcessor the {@link ObjectPostProcessor} to use
*/
protected AbstractConfiguredSecurityBuilder(ObjectPostProcessor<Object> objectPostProcessor) {
this(objectPostProcessor,false);
}
/***
* Creates a new instance with the provided {@link ObjectPostProcessor}.
* This post processor must support Object since there are many types of
* objects that may be post processed.
*
* @param objectPostProcessor the {@link ObjectPostProcessor} to use
* @param allowConfigurersOfSameType if true, will not override other {@link SecurityConfigurer}'s when performing apply
*/
protected AbstractConfiguredSecurityBuilder(ObjectPostProcessor<Object> objectPostProcessor, boolean allowConfigurersOfSameType) {
Assert.notNull(objectPostProcessor, "objectPostProcessor cannot be null");
this.objectPostProcessor = objectPostProcessor;
this.allowConfigurersOfSameType = allowConfigurersOfSameType;
}
/**
* Applies a {@link SecurityConfigurerAdapter} to this
* {@link SecurityBuilder} and invokes
* {@link SecurityConfigurerAdapter#setBuilder(SecurityBuilder)}.
*
* @param configurer
* @return
* @throws Exception
*/
@SuppressWarnings("unchecked")
public <C extends SecurityConfigurerAdapter<O, B>> C apply(C configurer)
throws Exception {
add(configurer);
configurer.addObjectPostProcessor(objectPostProcessor);
configurer.setBuilder((B) this);
return configurer;
}
/**
* Applies a {@link SecurityConfigurer} to this {@link SecurityBuilder}
* overriding any {@link SecurityConfigurer} of the exact same class. Note
* that object hierarchies are not considered.
*
* @param configurer
* @return
* @throws Exception
*/
public <C extends SecurityConfigurer<O, B>> C apply(C configurer)
throws Exception {
add(configurer);
return configurer;
}
/**
* Sets an object that is shared by multiple {@link SecurityConfigurer}.
*
* @param sharedType the Class to key the shared object by.
* @param object the Object to store
*/
@SuppressWarnings("unchecked")
public <C> void setSharedObject(Class<C> sharedType, C object) {
this.sharedObjects.put((Class<Object>) sharedType, object);
}
/**
* Gets a shared Object. Note that object heirarchies are not considered.
*
* @param sharedType the type of the shared Object
* @return the shared Object or null if it is not found
*/
@SuppressWarnings("unchecked")
public <C> C getSharedObject(Class<C> sharedType) {
return (C) this.sharedObjects.get(sharedType);
}
/**
* Gets the shared objects
* @return
*/
public Map<Class<Object>,Object> getSharedObjects() {
return Collections.unmodifiableMap(this.sharedObjects);
}
/**
* Adds {@link SecurityConfigurer} ensuring that it is allowed and
* invoking {@link SecurityConfigurer#init(SecurityBuilder)} immediately
* if necessary.
*
* @param configurer the {@link SecurityConfigurer} to add
* @throws Exception if an error occurs
*/
@SuppressWarnings("unchecked")
private <C extends SecurityConfigurer<O, B>> void add(C configurer) throws Exception {
Assert.notNull(configurer, "configurer cannot be null");
Class<? extends SecurityConfigurer<O, B>> clazz = (Class<? extends SecurityConfigurer<O, B>>) configurer
.getClass();
synchronized(configurers) {
if(buildState.isConfigured()) {
throw new IllegalStateException("Cannot apply "+configurer+" to already built object");
}
List<SecurityConfigurer<O, B>> configs = allowConfigurersOfSameType ? this.configurers.get(clazz) : null;
if(configs == null) {
configs = new ArrayList<SecurityConfigurer<O,B>>(1);
}
configs.add(configurer);
this.configurers.put(clazz, configs);
if(buildState.isInitializing()) {
configurer.init((B)this);
}
}
}
/**
* Gets all the {@link SecurityConfigurer} instances by its class name or an
* empty List if not found. Note that object hierarchies are not considered.
*
* @param clazz the {@link SecurityConfigurer} class to look for
* @return
*/
@SuppressWarnings("unchecked")
public <C extends SecurityConfigurer<O, B>> List<C> getConfigurers(
Class<C> clazz) {
List<C> configs = (List<C>) this.configurers.get(clazz);
if(configs == null) {
return new ArrayList<C>();
}
return new ArrayList<C>(configs);
}
/**
* Removes all the {@link SecurityConfigurer} instances by its class name or an
* empty List if not found. Note that object hierarchies are not considered.
*
* @param clazz the {@link SecurityConfigurer} class to look for
* @return
*/
@SuppressWarnings("unchecked")
public <C extends SecurityConfigurer<O, B>> List<C> removeConfigurers(
Class<C> clazz) {
List<C> configs = (List<C>) this.configurers.remove(clazz);
if(configs == null) {
return new ArrayList<C>();
}
return new ArrayList<C>(configs);
}
/**
* Gets the {@link SecurityConfigurer} by its class name or
* <code>null</code> if not found. Note that object hierarchies are not
* considered.
*
* @param clazz
* @return
*/
@SuppressWarnings("unchecked")
public <C extends SecurityConfigurer<O, B>> C getConfigurer(
Class<C> clazz) {
List<SecurityConfigurer<O,B>> configs = this.configurers.get(clazz);
if(configs == null) {
return null;
}
if(configs.size() != 1) {
throw new IllegalStateException("Only one configurer expected for type " + clazz + ", but got " + configs);
}
return (C) configs.get(0);
}
/**
* Removes and returns the {@link SecurityConfigurer} by its class name or
* <code>null</code> if not found. Note that object hierarchies are not
* considered.
*
* @param clazz
* @return
*/
@SuppressWarnings("unchecked")
public <C extends SecurityConfigurer<O,B>> C removeConfigurer(Class<C> clazz) {
List<SecurityConfigurer<O,B>> configs = this.configurers.remove(clazz);
if(configs == null) {
return null;
}
if(configs.size() != 1) {
throw new IllegalStateException("Only one configurer expected for type " + clazz + ", but got " + configs);
}
return (C) configs.get(0);
}
/**
* Specifies the {@link ObjectPostProcessor} to use.
* @param objectPostProcessor the {@link ObjectPostProcessor} to use. Cannot be null
* @return the {@link SecurityBuilder} for further customizations
*/
@SuppressWarnings("unchecked")
public O objectPostProcessor(ObjectPostProcessor<Object> objectPostProcessor) {
Assert.notNull(objectPostProcessor,"objectPostProcessor cannot be null");
this.objectPostProcessor = objectPostProcessor;
return (O) this;
}
/**
* Performs post processing of an object. The default is to delegate to the
* {@link ObjectPostProcessor}.
*
* @param object the Object to post process
* @return the possibly modified Object to use
*/
protected <P> P postProcess(P object) {
return (P) this.objectPostProcessor.postProcess(object);
}
/**
* Executes the build using the {@link SecurityConfigurer}'s that have been applied using the following steps:
*
* <ul>
* <li>Invokes {@link #beforeInit()} for any subclass to hook into</li>
* <li>Invokes {@link SecurityConfigurer#init(SecurityBuilder)} for any {@link SecurityConfigurer} that was applied to this builder.</li>
* <li>Invokes {@link #beforeConfigure()} for any subclass to hook into</li>
* <li>Invokes {@link #performBuild()} which actually builds the Object</li>
* </ul>
*/
@Override
protected final O doBuild() throws Exception {
synchronized(configurers) {
buildState = BuildState.INITIALIZING;
beforeInit();
init();
buildState = BuildState.CONFIGURING;
beforeConfigure();
configure();
buildState = BuildState.BUILDING;
O result = performBuild();
buildState = BuildState.BUILT;
return result;
}
}
/**
* Invoked prior to invoking each
* {@link SecurityConfigurer#init(SecurityBuilder)} method. Subclasses may
* override this method to hook into the lifecycle without using a
* {@link SecurityConfigurer}.
*/
protected void beforeInit() throws Exception {
}
/**
* Invoked prior to invoking each
* {@link SecurityConfigurer#configure(SecurityBuilder)} method.
* Subclasses may override this method to hook into the lifecycle without
* using a {@link SecurityConfigurer}.
*/
protected void beforeConfigure() throws Exception {
}
/**
* Subclasses must implement this method to build the object that is being returned.
*
* @return
*/
protected abstract O performBuild() throws Exception;
@SuppressWarnings("unchecked")
private void init() throws Exception {
Collection<SecurityConfigurer<O,B>> configurers = getConfigurers();
for(SecurityConfigurer<O,B> configurer : configurers ) {
configurer.init((B) this);
}
}
@SuppressWarnings("unchecked")
private void configure() throws Exception {
Collection<SecurityConfigurer<O,B>> configurers = getConfigurers();
for(SecurityConfigurer<O,B> configurer : configurers ) {
configurer.configure((B) this);
}
}
private Collection<SecurityConfigurer<O, B>> getConfigurers() {
List<SecurityConfigurer<O,B>> result = new ArrayList<SecurityConfigurer<O,B>>();
for(List<SecurityConfigurer<O,B>> configs : this.configurers.values()) {
result.addAll(configs);
}
return result;
}
/**
* The build state for the application
*
* @author Rob Winch
* @since 3.2
*/
private static enum BuildState {
/**
* This is the state before the {@link Builder#build()} is invoked
*/
UNBUILT(0),
/**
* The state from when {@link Builder#build()} is first invoked until
* all the {@link SecurityConfigurer#init(SecurityBuilder)} methods
* have been invoked.
*/
INITIALIZING(1),
/**
* The state from after all
* {@link SecurityConfigurer#init(SecurityBuilder)} have been invoked
* until after all the
* {@link SecurityConfigurer#configure(SecurityBuilder)} methods have
* been invoked.
*/
CONFIGURING(2),
/**
* From the point after all the
* {@link SecurityConfigurer#configure(SecurityBuilder)} have
* completed to just after
* {@link AbstractConfiguredSecurityBuilder#performBuild()}.
*/
BUILDING(3),
/**
* After the object has been completely built.
*/
BUILT(4);
private final int order;
BuildState(int order) {
this.order = order;
}
public boolean isInitializing() {
return INITIALIZING.order == order;
}
/**
* Determines if the state is CONFIGURING or later
* @return
*/
public boolean isConfigured() {
return order >= CONFIGURING.order;
}
}
}

View File

@ -0,0 +1,67 @@
/*
* Copyright 2002-2013 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.annotation;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* A base {@link SecurityBuilder} that ensures the object being built is only
* built one time.
*
* @param <O> the type of Object that is being built
*
* @author Rob Winch
*
*/
public abstract class AbstractSecurityBuilder<O> implements SecurityBuilder<O> {
private AtomicBoolean building = new AtomicBoolean();
private O object;
/* (non-Javadoc)
* @see org.springframework.security.config.annotation.SecurityBuilder#build()
*/
@Override
public final O build() throws Exception {
if(building.compareAndSet(false, true)) {
object = doBuild();
return object;
}
throw new IllegalStateException("This object has already been built");
}
/**
* Gets the object that was built. If it has not been built yet an Exception
* is thrown.
*
* @return the Object that was built
*/
public final O getObject() {
if(!building.get()) {
throw new IllegalStateException("This object has not been built");
}
return object;
}
/**
* Subclasses should implement this to perform the build.
*
* @return the object that should be returned by {@link #build()}.
*
* @throws Exception if an error occurs
*/
protected abstract O doBuild() throws Exception;
}

View File

@ -0,0 +1,52 @@
/*
* Copyright 2002-2013 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.annotation;
import org.springframework.beans.factory.Aware;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
/**
* Allows initialization of Objects. Typically this is used to call the
* {@link Aware} methods, {@link InitializingBean#afterPropertiesSet()}, and
* ensure that {@link DisposableBean#destroy()} has been invoked.
*
* @param <T> the bound of the types of Objects this {@link ObjectPostProcessor} supports.
*
* @author Rob Winch
* @since 3.2
*/
public interface ObjectPostProcessor<T> {
/**
* Initialize the object possibly returning a modified instance that should
* be used instead.
*
* @param object the object to initialize
* @return the initialized version of the object
*/
<O extends T> O postProcess(O object);
/**
* A do nothing implementation of the {@link ObjectPostProcessor}
*/
ObjectPostProcessor<Object> QUIESCENT_POSTPROCESSOR = new ObjectPostProcessor<Object>() {
@Override
public <T> T postProcess(T object) {
return object;
}
};
}

View File

@ -0,0 +1,35 @@
/*
* Copyright 2002-2013 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.annotation;
/**
* Interface for building an Object
*
* @author Rob Winch
* @since 3.2
*
* @param <O> The type of the Object being built
*/
public interface SecurityBuilder<O> {
/**
* Builds the object and returns it or null.
*
* @return the Object to be built or null if the implementation allows it.
* @throws Exception if an error occured when building the Object
*/
O build() throws Exception;
}

View File

@ -0,0 +1,55 @@
/*
* Copyright 2002-2013 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.annotation;
/**
* Allows for configuring a {@link SecurityBuilder}. All
* {@link SecurityConfigurer} first have their {@link #init(SecurityBuilder)}
* method invoked. After all {@link #init(SecurityBuilder)} methods have been
* invoked, each {@link #configure(SecurityBuilder)} method is invoked.
*
* @see AbstractConfiguredSecurityBuilder
*
* @author Rob Winch
*
* @param <O>
* The object being built by the {@link SecurityBuilder} B
* @param <B>
* The {@link SecurityBuilder} that builds objects of type O. This is
* also the {@link SecurityBuilder} that is being configured.
*/
public interface SecurityConfigurer<O, B extends SecurityBuilder<O>> {
/**
* Initialize the {@link SecurityBuilder}. Here only shared state should be
* created and modified, but not properties on the {@link SecurityBuilder}
* used for building the object. This ensures that the
* {@link #configure(SecurityBuilder)} method uses the correct shared
* objects when building.
*
* @param builder
* @throws Exception
*/
void init(B builder) throws Exception;
/**
* Configure the {@link SecurityBuilder} by setting the necessary properties
* on the {@link SecurityBuilder}.
*
* @param builder
* @throws Exception
*/
void configure(B builder) throws Exception;
}

View File

@ -0,0 +1,135 @@
/*
* Copyright 2002-2013 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.annotation;
import java.util.ArrayList;
import java.util.List;
import org.springframework.core.GenericTypeResolver;
/**
* A base class for {@link SecurityConfigurer} that allows subclasses to only
* implement the methods they are interested in. It also provides a mechanism
* for using the {@link SecurityConfigurer} and when done gaining access to the
* {@link SecurityBuilder} that is being configured.
*
* @author Rob Winch
*
* @param <O>
* The Object being built by B
* @param <B>
* The Builder that is building O and is configured by {@link SecurityConfigurerAdapter}
*/
public abstract class SecurityConfigurerAdapter<O,B extends SecurityBuilder<O>> implements SecurityConfigurer<O,B> {
private B securityBuilder;
private CompositeObjectPostProcessor objectPostProcessor = new CompositeObjectPostProcessor();
@Override
public void init(B builder) throws Exception {}
@Override
public void configure(B builder) throws Exception {}
/**
* Return the {@link SecurityBuilder} when done using the
* {@link SecurityConfigurer}. This is useful for method chaining.
*
* @return
*/
public B and() {
return getBuilder();
}
/**
* Gets the {@link SecurityBuilder}. Cannot be null.
*
* @return the {@link SecurityBuilder}
* @throw {@link IllegalStateException} if {@link SecurityBuilder} is null
*/
protected final B getBuilder() {
if(securityBuilder == null) {
throw new IllegalStateException("securityBuilder cannot be null");
}
return securityBuilder;
}
/**
* Performs post processing of an object. The default is to delegate to the
* {@link ObjectPostProcessor}.
*
* @param object the Object to post process
* @return the possibly modified Object to use
*/
@SuppressWarnings("unchecked")
protected <T> T postProcess(T object) {
return (T) this.objectPostProcessor.postProcess(object);
}
/**
* Adds an {@link ObjectPostProcessor} to be used for this
* {@link SecurityConfigurerAdapter}. The default implementation does
* nothing to the object.
*
* @param objectPostProcessor the {@link ObjectPostProcessor} to use
*/
public void addObjectPostProcessor(ObjectPostProcessor<?> objectPostProcessor) {
this.objectPostProcessor.addObjectPostProcessor(objectPostProcessor);
}
/**
* Sets the {@link SecurityBuilder} to be used. This is automatically set
* when using
* {@link AbstractConfiguredSecurityBuilder#apply(SecurityConfigurerAdapter)}
*
* @param builder the {@link SecurityBuilder} to set
*/
public void setBuilder(B builder) {
this.securityBuilder = builder;
}
/**
* An {@link ObjectPostProcessor} that delegates work to numerous
* {@link ObjectPostProcessor} implementations.
*
* @author Rob Winch
*/
private static final class CompositeObjectPostProcessor implements ObjectPostProcessor<Object> {
private List<ObjectPostProcessor<? extends Object>> postProcessors = new ArrayList<ObjectPostProcessor<?>>();
@Override
@SuppressWarnings({ "rawtypes", "unchecked" })
public Object postProcess(Object object) {
for(ObjectPostProcessor opp : postProcessors) {
Class<?> oppClass = opp.getClass();
Class<?> oppType = GenericTypeResolver.resolveTypeArgument(oppClass,ObjectPostProcessor.class);
if(oppType == null || oppType.isAssignableFrom(object.getClass())) {
object = opp.postProcess(object);
}
}
return object;
}
/**
* Adds an {@link ObjectPostProcessor} to use
* @param objectPostProcessor the {@link ObjectPostProcessor} to add
* @return true if the {@link ObjectPostProcessor} was added, else false
*/
private boolean addObjectPostProcessor(ObjectPostProcessor<?extends Object> objectPostProcessor) {
return this.postProcessors.add(objectPostProcessor);
}
}
}

View File

@ -0,0 +1,44 @@
/*
* Copyright 2002-2013 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.annotation.authentication;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.config.annotation.SecurityBuilder;
/**
* Interface for operating on a SecurityBuilder that creates a {@link ProviderManager}
*
* @author Rob Winch
*
* @param <B> the type of the {@link SecurityBuilder}
*/
public interface ProviderManagerBuilder<B extends ProviderManagerBuilder<B>> extends SecurityBuilder<AuthenticationManager> {
/**
* Add authentication based upon the custom {@link AuthenticationProvider}
* that is passed in. Since the {@link AuthenticationProvider}
* implementation is unknown, all customizations must be done externally and
* the {@link ProviderManagerBuilder} is returned immediately.
*
* @return a {@link ProviderManagerBuilder} to allow further authentication
* to be provided to the {@link ProviderManagerBuilder}
* @throws Exception
* if an error occurs when adding the {@link AuthenticationProvider}
*/
B authenticationProvider(AuthenticationProvider authenticationProvider);
}

View File

@ -0,0 +1,254 @@
/*
* Copyright 2002-2013 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.annotation.authentication.builders;
import java.util.ArrayList;
import java.util.List;
import org.springframework.security.authentication.AuthenticationEventPublisher;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.SecurityBuilder;
import org.springframework.security.config.annotation.authentication.ProviderManagerBuilder;
import org.springframework.security.config.annotation.authentication.configurers.ldap.LdapAuthenticationProviderConfigurer;
import org.springframework.security.config.annotation.authentication.configurers.provisioning.InMemoryUserDetailsManagerConfigurer;
import org.springframework.security.config.annotation.authentication.configurers.provisioning.JdbcUserDetailsManagerConfigurer;
import org.springframework.security.config.annotation.authentication.configurers.userdetails.DaoAuthenticationConfigurer;
import org.springframework.security.config.annotation.authentication.configurers.userdetails.UserDetailsAwareConfigurer;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.util.Assert;
/**
* {@link SecurityBuilder} used to create an {@link AuthenticationManager}.
* Allows for easily building in memory authentication, LDAP authentication,
* JDBC based authentication, adding {@link UserDetailsService}, and adding
* {@link AuthenticationProvider}'s.
*
* @author Rob Winch
* @since 3.2
*/
public class AuthenticationManagerBuilder extends AbstractConfiguredSecurityBuilder<AuthenticationManager, AuthenticationManagerBuilder> implements ProviderManagerBuilder<AuthenticationManagerBuilder> {
private AuthenticationManager parentAuthenticationManager;
private List<AuthenticationProvider> authenticationProviders = new ArrayList<AuthenticationProvider>();
private UserDetailsService defaultUserDetailsService;
private Boolean eraseCredentials;
private AuthenticationEventPublisher eventPublisher;
/**
* Creates a new instance
*/
public AuthenticationManagerBuilder() {
super(ObjectPostProcessor.QUIESCENT_POSTPROCESSOR,true);
}
/**
* Allows providing a parent {@link AuthenticationManager} that will be
* tried if this {@link AuthenticationManager} was unable to attempt to
* authenticate the provided {@link Authentication}.
*
* @param authenticationManager
* the {@link AuthenticationManager} that should be used if the
* current {@link AuthenticationManager} was unable to attempt to
* authenticate the provided {@link Authentication}.
* @return the {@link AuthenticationManagerBuilder} for further adding types
* of authentication
*/
public AuthenticationManagerBuilder parentAuthenticationManager(
AuthenticationManager authenticationManager) {
this.parentAuthenticationManager = authenticationManager;
return this;
}
/**
* Sets the {@link AuthenticationEventPublisher}
*
* @param eventPublisher
* the {@link AuthenticationEventPublisher} to use
* @return the {@link AuthenticationManagerBuilder} for further
* customizations
*/
public AuthenticationManagerBuilder authenticationEventPublisher(AuthenticationEventPublisher eventPublisher) {
Assert.notNull(eventPublisher, "AuthenticationEventPublisher cannot be null");
this.eventPublisher = eventPublisher;
return this;
}
/**
*
*
* @param eraseCredentials
* true if {@link AuthenticationManager} should clear the
* credentials from the {@link Authentication} object after
* authenticating
* @return the {@link AuthenticationManagerBuilder} for further customizations
*/
public AuthenticationManagerBuilder eraseCredentials(boolean eraseCredentials) {
this.eraseCredentials = eraseCredentials;
return this;
}
/**
* Add in memory authentication to the {@link AuthenticationManagerBuilder}
* and return a {@link InMemoryUserDetailsManagerConfigurer} to
* allow customization of the in memory authentication.
*
* <p>
* This method also ensure that a {@link UserDetailsService} is available
* for the {@link #getDefaultUserDetailsService()} method. Note that
* additional {@link UserDetailsService}'s may override this
* {@link UserDetailsService} as the default.
* </p>
*
* @return a {@link InMemoryUserDetailsManagerConfigurer} to allow
* customization of the in memory authentication
* @throws Exception
* if an error occurs when adding the in memory authentication
*/
public InMemoryUserDetailsManagerConfigurer<AuthenticationManagerBuilder> inMemoryAuthentication()
throws Exception {
return apply(new InMemoryUserDetailsManagerConfigurer<AuthenticationManagerBuilder>());
}
/**
* Add JDBC authentication to the {@link AuthenticationManagerBuilder} and
* return a {@link JdbcUserDetailsManagerConfigurer} to allow customization of the
* JDBC authentication.
*
* <p>
* This method also ensure that a {@link UserDetailsService} is available
* for the {@link #getDefaultUserDetailsService()} method. Note that
* additional {@link UserDetailsService}'s may override this
* {@link UserDetailsService} as the default.
* </p>
*
* @return a {@link JdbcUserDetailsManagerConfigurer} to allow customization of the
* JDBC authentication
* @throws Exception if an error occurs when adding the JDBC authentication
*/
public JdbcUserDetailsManagerConfigurer<AuthenticationManagerBuilder> jdbcAuthentication()
throws Exception {
return apply(new JdbcUserDetailsManagerConfigurer<AuthenticationManagerBuilder>());
}
/**
* Add authentication based upon the custom {@link UserDetailsService} that
* is passed in. It then returns a {@link DaoAuthenticationConfigurer} to
* allow customization of the authentication.
*
* <p>
* This method also ensure that the {@link UserDetailsService} is available
* for the {@link #getDefaultUserDetailsService()} method. Note that
* additional {@link UserDetailsService}'s may override this
* {@link UserDetailsService} as the default.
* </p>
*
* @return a {@link DaoAuthenticationConfigurer} to allow customization
* of the DAO authentication
* @throws Exception
* if an error occurs when adding the {@link UserDetailsService}
* based authentication
*/
public <T extends UserDetailsService> DaoAuthenticationConfigurer<AuthenticationManagerBuilder,T> userDetailsService(
T userDetailsService) throws Exception {
this.defaultUserDetailsService = userDetailsService;
return apply(new DaoAuthenticationConfigurer<AuthenticationManagerBuilder,T>(userDetailsService));
}
/**
* Add LDAP authentication to the {@link AuthenticationManagerBuilder} and
* return a {@link LdapAuthenticationProviderConfigurer} to allow
* customization of the LDAP authentication.
*
* <p>
* This method <b>does NOT</b> ensure that a {@link UserDetailsService} is
* available for the {@link #getDefaultUserDetailsService()} method.
* </p>
*
* @return a {@link LdapAuthenticationProviderConfigurer} to allow
* customization of the LDAP authentication
* @throws Exception
* if an error occurs when adding the LDAP authentication
*/
public LdapAuthenticationProviderConfigurer<AuthenticationManagerBuilder> ldapAuthentication()
throws Exception {
return apply(new LdapAuthenticationProviderConfigurer<AuthenticationManagerBuilder>());
}
/**
* Add authentication based upon the custom {@link AuthenticationProvider}
* that is passed in. Since the {@link AuthenticationProvider}
* implementation is unknown, all customizations must be done externally and
* the {@link AuthenticationManagerBuilder} is returned immediately.
*
* <p>
* This method <b>does NOT</b> ensure that the {@link UserDetailsService} is
* available for the {@link #getDefaultUserDetailsService()} method.
* </p>
*
* @return a {@link AuthenticationManagerBuilder} to allow further authentication
* to be provided to the {@link AuthenticationManagerBuilder}
* @throws Exception
* if an error occurs when adding the {@link AuthenticationProvider}
*/
public AuthenticationManagerBuilder authenticationProvider(
AuthenticationProvider authenticationProvider) {
this.authenticationProviders.add(authenticationProvider);
return this;
}
@Override
protected ProviderManager performBuild() throws Exception {
ProviderManager providerManager = new ProviderManager(authenticationProviders, parentAuthenticationManager);
if(eraseCredentials != null) {
providerManager.setEraseCredentialsAfterAuthentication(eraseCredentials);
}
if(eventPublisher != null) {
providerManager.setAuthenticationEventPublisher(eventPublisher);
}
providerManager = postProcess(providerManager);
return providerManager;
}
/**
* Gets the default {@link UserDetailsService} for the
* {@link AuthenticationManagerBuilder}. The result may be null in some
* circumstances.
*
* @return the default {@link UserDetailsService} for the
* {@link AuthenticationManagerBuilder}
*/
public UserDetailsService getDefaultUserDetailsService() {
return this.defaultUserDetailsService;
}
/**
* Captures the {@link UserDetailsService} from any {@link UserDetailsAwareConfigurer}.
*
* @param configurer the {@link UserDetailsAwareConfigurer} to capture the {@link UserDetailsService} from.
* @return the {@link UserDetailsAwareConfigurer} for further customizations
* @throws Exception if an error occurs
*/
private <C extends UserDetailsAwareConfigurer<AuthenticationManagerBuilder,? extends UserDetailsService>> C apply(C configurer) throws Exception {
this.defaultUserDetailsService = configurer.getUserDetailsService();
return (C) super.apply(configurer);
}
}

View File

@ -0,0 +1,469 @@
/*
* Copyright 2002-2013 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.annotation.authentication.configurers.ldap;
import org.springframework.ldap.core.support.BaseLdapPathContextSource;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.encoding.PasswordEncoder;
import org.springframework.security.authentication.encoding.PlaintextPasswordEncoder;
import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
import org.springframework.security.config.annotation.authentication.ProviderManagerBuilder;
import org.springframework.security.core.authority.mapping.SimpleAuthorityMapper;
import org.springframework.security.ldap.DefaultSpringSecurityContextSource;
import org.springframework.security.ldap.authentication.AbstractLdapAuthenticator;
import org.springframework.security.ldap.authentication.BindAuthenticator;
import org.springframework.security.ldap.authentication.LdapAuthenticationProvider;
import org.springframework.security.ldap.authentication.LdapAuthenticator;
import org.springframework.security.ldap.authentication.PasswordComparisonAuthenticator;
import org.springframework.security.ldap.search.FilterBasedLdapUserSearch;
import org.springframework.security.ldap.search.LdapUserSearch;
import org.springframework.security.ldap.server.ApacheDSContainer;
import org.springframework.security.ldap.userdetails.DefaultLdapAuthoritiesPopulator;
import org.springframework.security.ldap.userdetails.InetOrgPersonContextMapper;
import org.springframework.security.ldap.userdetails.LdapUserDetailsMapper;
import org.springframework.security.ldap.userdetails.PersonContextMapper;
import org.springframework.security.ldap.userdetails.UserDetailsContextMapper;
/**
* Configures LDAP {@link AuthenticationProvider} in the {@link ProviderManagerBuilder}.
*
* @param <B> the {@link ProviderManagerBuilder} type that this is configuring.
*
* @author Rob Winch
* @since 3.2
*/
public class LdapAuthenticationProviderConfigurer<B extends ProviderManagerBuilder<B>> extends SecurityConfigurerAdapter<AuthenticationManager,B> {
private String groupRoleAttribute = "cn";
private String groupSearchBase = "";
private String groupSearchFilter = "(uniqueMember={0})";
private String rolePrefix = "ROLE_";
private String userSearchBase = ""; // only for search
private String userSearchFilter = null;//"uid={0}"; // only for search
private String[] userDnPatterns;
private BaseLdapPathContextSource contextSource;
private ContextSourceBuilder contextSourceBuilder = new ContextSourceBuilder();
private UserDetailsContextMapper userDetailsContextMapper;
private PasswordEncoder passwordEncoder;
private String passwordAttribute;
private LdapAuthenticationProvider build() throws Exception {
BaseLdapPathContextSource contextSource = getContextSource();
LdapAuthenticator ldapAuthenticator = createLdapAuthenticator(contextSource);
DefaultLdapAuthoritiesPopulator authoritiesPopulator = new DefaultLdapAuthoritiesPopulator(
contextSource, groupSearchBase);
authoritiesPopulator.setGroupRoleAttribute(groupRoleAttribute);
authoritiesPopulator.setGroupSearchFilter(groupSearchFilter);
LdapAuthenticationProvider ldapAuthenticationProvider = new LdapAuthenticationProvider(
ldapAuthenticator, authoritiesPopulator);
SimpleAuthorityMapper simpleAuthorityMapper = new SimpleAuthorityMapper();
simpleAuthorityMapper.setPrefix(rolePrefix);
simpleAuthorityMapper.afterPropertiesSet();
ldapAuthenticationProvider.setAuthoritiesMapper(simpleAuthorityMapper);
if(userDetailsContextMapper != null) {
ldapAuthenticationProvider.setUserDetailsContextMapper(userDetailsContextMapper);
}
return ldapAuthenticationProvider;
}
/**
* Creates the {@link LdapAuthenticator} to use
*
* @param contextSource the {@link BaseLdapPathContextSource} to use
* @return the {@link LdapAuthenticator} to use
*/
private LdapAuthenticator createLdapAuthenticator(BaseLdapPathContextSource contextSource) {
AbstractLdapAuthenticator ldapAuthenticator = passwordEncoder == null ? createBindAuthenticator(contextSource) : createPasswordCompareAuthenticator(contextSource);
LdapUserSearch userSearch = createUserSearch();
if(userSearch != null) {
ldapAuthenticator.setUserSearch(userSearch);
}
if(userDnPatterns != null && userDnPatterns.length > 0) {
ldapAuthenticator.setUserDnPatterns(userDnPatterns);
}
return postProcess(ldapAuthenticator);
}
/**
* Creates {@link PasswordComparisonAuthenticator}
*
* @param contextSource the {@link BaseLdapPathContextSource} to use
* @return
*/
private PasswordComparisonAuthenticator createPasswordCompareAuthenticator(BaseLdapPathContextSource contextSource) {
PasswordComparisonAuthenticator ldapAuthenticator = new PasswordComparisonAuthenticator(contextSource);
ldapAuthenticator.setPasswordAttributeName(passwordAttribute);
ldapAuthenticator.setPasswordEncoder(passwordEncoder);
return ldapAuthenticator;
}
/**
* Creates a {@link BindAuthenticator}
*
* @param contextSource the {@link BaseLdapPathContextSource} to use
* @return the {@link BindAuthenticator} to use
*/
private BindAuthenticator createBindAuthenticator(
BaseLdapPathContextSource contextSource) {
return new BindAuthenticator(contextSource);
}
private LdapUserSearch createUserSearch() {
if(userSearchFilter == null) {
return null;
}
return new FilterBasedLdapUserSearch(userSearchBase, userSearchFilter, contextSource);
}
/**
* Specifies the {@link BaseLdapPathContextSource} to be used. If not
* specified, an embedded LDAP server will be created using
* {@link #contextSource()}.
*
* @param contextSource
* the {@link BaseLdapPathContextSource} to use
* @return the {@link LdapAuthenticationProviderConfigurer} for further
* customizations
* @see #contextSource()
*/
public LdapAuthenticationProviderConfigurer<B> contextSource(BaseLdapPathContextSource contextSource) {
this.contextSource = contextSource;
return this;
}
/**
* Allows easily configuring of a {@link BaseLdapPathContextSource} with
* defaults pointing to an embedded LDAP server that is created.
*
* @return the {@link ContextSourceBuilder} for further customizations
*/
public ContextSourceBuilder contextSource() {
return contextSourceBuilder;
}
/**
* Specifies the {@link PasswordEncoder} to be used when authenticating with
* password comparison.
*
* @param passwordEncoder the {@link PasswordEncoder} to use
* @return the {@link LdapAuthenticationProviderConfigurer} for further customization
*/
public LdapAuthenticationProviderConfigurer<B> passwordEncoder(PasswordEncoder passwordEncoder) {
this.passwordEncoder = passwordEncoder;
return this;
}
/**
* If your users are at a fixed location in the directory (i.e. you can work
* out the DN directly from the username without doing a directory search),
* you can use this attribute to map directly to the DN. It maps directly to
* the userDnPatterns property of AbstractLdapAuthenticator. The value is a
* specific pattern used to build the user's DN, for example
* "uid={0},ou=people". The key "{0}" must be present and will be
* substituted with the username.
*
* @param userDnPatterns the LDAP patterns for finding the usernames
* @return the {@link LdapAuthenticationProviderConfigurer} for further customizations
*/
public LdapAuthenticationProviderConfigurer<B> userDnPatterns(String...userDnPatterns) {
this.userDnPatterns = userDnPatterns;
return this;
}
/**
* Allows explicit customization of the loaded user object by specifying a
* UserDetailsContextMapper bean which will be called with the context
* information from the user's directory entry.
*
* @param userDetailsContextMapper the {@link UserDetailsContextMapper} to use
* @return the {@link LdapAuthenticationProviderConfigurer} for further
* customizations
*
* @see PersonContextMapper
* @see InetOrgPersonContextMapper
* @see LdapUserDetailsMapper
*/
public LdapAuthenticationProviderConfigurer<B> userDetailsContextMapper(UserDetailsContextMapper userDetailsContextMapper) {
this.userDetailsContextMapper = userDetailsContextMapper;
return this;
}
/**
* Specifies the attribute name which contains the role name. Default is "cn".
* @param groupRoleAttribute the attribute name that maps a group to a role.
* @return
*/
public LdapAuthenticationProviderConfigurer<B> groupRoleAttribute(String groupRoleAttribute) {
this.groupRoleAttribute = groupRoleAttribute;
return this;
}
/**
* The search base for group membership searches. Defaults to "".
* @param groupSearchBase
* @return the {@link LdapAuthenticationProviderConfigurer} for further customizations
*/
public LdapAuthenticationProviderConfigurer<B> groupSearchBase(String groupSearchBase) {
this.groupSearchBase = groupSearchBase;
return this;
}
/**
* The LDAP filter to search for groups. Defaults to "(uniqueMember={0})".
* The substituted parameter is the DN of the user.
*
* @param groupSearchFilter the LDAP filter to search for groups
* @return the {@link LdapAuthenticationProviderConfigurer} for further customizations
*/
public LdapAuthenticationProviderConfigurer<B> groupSearchFilter(String groupSearchFilter) {
this.groupSearchFilter = groupSearchFilter;
return this;
}
/**
* A non-empty string prefix that will be added as a prefix to the existing
* roles. The default is "ROLE_".
*
* @param rolePrefix the prefix to be added to the roles that are loaded.
* @return the {@link LdapAuthenticationProviderConfigurer} for further customizations
* @see SimpleAuthorityMapper#setPrefix(String)
*/
public LdapAuthenticationProviderConfigurer<B> rolePrefix(String rolePrefix) {
this.rolePrefix = rolePrefix;
return this;
}
/**
* Search base for user searches. Defaults to "". Only used with {@link #userSearchFilter(String)}.
*
* @param userSearchBase search base for user searches
* @return the {@link LdapAuthenticationProviderConfigurer} for further customizations
*/
public LdapAuthenticationProviderConfigurer<B> userSearchBase(String userSearchBase) {
this.userSearchBase = userSearchBase;
return this;
}
/**
* The LDAP filter used to search for users (optional). For example
* "(uid={0})". The substituted parameter is the user's login name.
*
* @param userSearchFilter
* the LDAP filter used to search for users
* @return the {@link LdapAuthenticationProviderConfigurer} for further
* customizations
*/
public LdapAuthenticationProviderConfigurer<B> userSearchFilter(String userSearchFilter) {
this.userSearchFilter = userSearchFilter;
return this;
}
@Override
public void configure(B builder) throws Exception {
LdapAuthenticationProvider provider = postProcess(build());
builder.authenticationProvider(provider);
}
/**
* Sets up Password based comparison
*
* @author Rob Winch
*/
public final class PasswordCompareConfigurer {
/**
* Allows specifying the {@link PasswordEncoder} to use. The default is {@link PlaintextPasswordEncoder}.
* @param passwordEncoder the {@link PasswordEncoder} to use
* @return the {@link PasswordEncoder} to use
*/
public PasswordCompareConfigurer passwordEncoder(PasswordEncoder passwordEncoder) {
LdapAuthenticationProviderConfigurer.this.passwordEncoder = passwordEncoder;
return this;
}
/**
* The attribute in the directory which contains the user password. Defaults to "userPassword".
*
* @param passwordAttribute the attribute in the directory which contains the user password
* @return the {@link PasswordCompareConfigurer} for further customizations
*/
public PasswordCompareConfigurer passwordAttribute(String passwordAttribute) {
LdapAuthenticationProviderConfigurer.this.passwordAttribute = passwordAttribute;
return this;
}
/**
* Allows obtaining a reference to the
* {@link LdapAuthenticationProviderConfigurer} for further
* customizations
*
* @return attribute in the directory which contains the user password
*/
public LdapAuthenticationProviderConfigurer<B> and() {
return LdapAuthenticationProviderConfigurer.this;
}
private PasswordCompareConfigurer() {}
}
/**
* Allows building a {@link BaseLdapPathContextSource} and optionally
* creating an embedded LDAP instance.
*
* @author Rob Winch
* @since 3.2
*/
public final class ContextSourceBuilder {
private String ldif = "classpath*:*.ldif";
private String managerPassword;
private String managerDn;
private int port = 33389;
private String root = "dc=springframework,dc=org";
private String url;
/**
* Specifies an ldif to load at startup for an embedded LDAP server.
* This only loads if using an embedded instance. The default is
* "classpath*:*.ldif".
*
* @param ldif
* the ldif to load at startup for an embedded LDAP server.
* @return the {@link ContextSourceBuilder} for further customization
*/
public ContextSourceBuilder ldif(String ldif) {
this.ldif = ldif;
return this;
}
/**
* Username (DN) of the "manager" user identity (i.e.
* "uid=admin,ou=system") which will be used to authenticate to a
* (non-embedded) LDAP server. If omitted, anonymous access will be
* used.
*
* @param managerDn
* the username (DN) of the "manager" user identity used to
* authenticate to a LDAP server.
* @return the {@link ContextSourceBuilder} for further customization
*/
public ContextSourceBuilder managerDn(String managerDn) {
this.managerDn = managerDn;
return this;
}
/**
* The password for the manager DN. This is required if the manager-dn is specified.
* @param managerPassword password for the manager DN
* @return the {@link ContextSourceBuilder} for further customization
*/
public ContextSourceBuilder managerPassword(String managerPassword) {
this.managerPassword = managerPassword;
return this;
}
/**
* The port to connect to LDAP to (the default is 33389).
* @param port the port to connect to
* @return the {@link ContextSourceBuilder} for further customization
*/
public ContextSourceBuilder port(int port) {
this.port = port;
return this;
}
/**
* Optional root suffix for the embedded LDAP server. Default is
* "dc=springframework,dc=org"
*
* @param root
* root suffix for the embedded LDAP server
* @return the {@link ContextSourceBuilder} for further customization
*/
public ContextSourceBuilder root(String root) {
this.root = root;
return this;
}
/**
* Specifies the ldap server URL when not using the embedded LDAP
* server. For example, "ldaps://ldap.example.com:33389/dc=myco,dc=org".
*
* @param url
* the ldap server URL
* @return the {@link ContextSourceBuilder} for further customization
*/
public ContextSourceBuilder url(String url) {
this.url = url;
return this;
}
/**
* Gets the {@link LdapAuthenticationProviderConfigurer} for further
* customizations
*
* @return the {@link LdapAuthenticationProviderConfigurer} for further
* customizations
*/
public LdapAuthenticationProviderConfigurer<B> and() {
return LdapAuthenticationProviderConfigurer.this;
}
private DefaultSpringSecurityContextSource build() throws Exception {
DefaultSpringSecurityContextSource contextSource = new DefaultSpringSecurityContextSource(getProviderUrl());
if(managerDn != null) {
contextSource.setUserDn(managerDn);
if(managerPassword == null) {
throw new IllegalStateException("managerPassword is required if managerDn is supplied");
}
contextSource.setPassword(managerPassword);
}
contextSource = postProcess(contextSource);
if(url != null) {
return contextSource;
}
ApacheDSContainer apacheDsContainer = new ApacheDSContainer(root, ldif);
apacheDsContainer.setPort(port);
postProcess(apacheDsContainer);
return contextSource;
}
private String getProviderUrl() {
if(url == null) {
return "ldap://127.0.0.1:" + port + "/" + root;
}
return url;
}
private ContextSourceBuilder() {}
}
private BaseLdapPathContextSource getContextSource() throws Exception {
if(contextSource == null) {
contextSource = contextSourceBuilder.build();
}
return contextSource;
}
/**
* @return
*/
public PasswordCompareConfigurer passwordCompare() {
return new PasswordCompareConfigurer()
.passwordAttribute("password")
.passwordEncoder(new PlaintextPasswordEncoder());
}
}

View File

@ -0,0 +1,42 @@
/*
* Copyright 2002-2013 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.annotation.authentication.configurers.provisioning;
import java.util.ArrayList;
import org.springframework.security.config.annotation.authentication.ProviderManagerBuilder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
/**
* Configures an {@link org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder} to
* have in memory authentication. It also allows easily adding users to the in memory authentication.
*
* @param <B> the type of the {@link SecurityBuilder} that is being configured
*
* @author Rob Winch
* @since 3.2
*/
public class InMemoryUserDetailsManagerConfigurer<B extends ProviderManagerBuilder<B>> extends
UserDetailsManagerConfigurer<B,InMemoryUserDetailsManagerConfigurer<B>> {
/**
* Creates a new instance
*/
public InMemoryUserDetailsManagerConfigurer() {
super(new InMemoryUserDetailsManager(new ArrayList<UserDetails>()));
}
}

View File

@ -0,0 +1,192 @@
/*
* Copyright 2002-2013 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.annotation.authentication.configurers.provisioning;
import java.util.ArrayList;
import java.util.List;
import javax.sql.DataSource;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.jdbc.datasource.init.DataSourceInitializer;
import org.springframework.jdbc.datasource.init.DatabasePopulator;
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
import org.springframework.security.config.annotation.authentication.ProviderManagerBuilder;
import org.springframework.security.core.userdetails.UserCache;
import org.springframework.security.provisioning.JdbcUserDetailsManager;
/**
* Configures an {@link org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder} to
* have JDBC authentication. It also allows easily adding users to the database used for authentication and setting up
* the schema.
*
* <p>
* The only required method is the {@link #dataSource(javax.sql.DataSource)} all other methods have reasonable defaults.
* </p>
*
* @param <B> the type of the {@link ProviderManagerBuilder} that is being configured
*
* @author Rob Winch
* @since 3.2
*/
public class JdbcUserDetailsManagerConfigurer<B extends ProviderManagerBuilder<B>> extends
UserDetailsManagerConfigurer<B,JdbcUserDetailsManagerConfigurer<B>> {
private DataSource dataSource;
private List<Resource> initScripts = new ArrayList<Resource>();
public JdbcUserDetailsManagerConfigurer(JdbcUserDetailsManager manager) {
super(manager);
}
public JdbcUserDetailsManagerConfigurer() {
this(new JdbcUserDetailsManager());
}
/**
* Populates the {@link DataSource} to be used. This is the only required attribute.
*
* @param dataSource the {@link DataSource} to be used. Cannot be null.
* @return
* @throws Exception
*/
public JdbcUserDetailsManagerConfigurer<B> dataSource(DataSource dataSource) throws Exception {
this.dataSource = dataSource;
getUserDetailsService().setDataSource(dataSource);
return this;
}
/**
* Sets the query to be used for finding a user by their username. For example:
*
* <code>
* select username,password,enabled from users where username = ?
* </code>
* @param query The query to use for selecting the username, password, and if the user is enabled by username.
* Must contain a single parameter for the username.
* @return The {@link JdbcUserDetailsManagerRegistry} used for additional customizations
* @throws Exception
*/
public JdbcUserDetailsManagerConfigurer<B> usersByUsernameQuery(String query) throws Exception {
getUserDetailsService().setUsersByUsernameQuery(query);
return this;
}
/**
* Sets the query to be used for finding a user's authorities by their username. For example:
*
* <code>
* select username,authority from authorities where username = ?
* </code>
*
* @param query The query to use for selecting the username, authority by username.
* Must contain a single parameter for the username.
* @return The {@link JdbcUserDetailsManagerRegistry} used for additional customizations
* @throws Exception
*/
public JdbcUserDetailsManagerConfigurer<B> authoritiesByUsernameQuery(String query) throws Exception {
getUserDetailsService().setAuthoritiesByUsernameQuery(query);
return this;
}
/**
* An SQL statement to query user's group authorities given a username. For example:
*
* <code>
* select
* g.id, g.group_name, ga.authority
* from
* groups g, group_members gm, group_authorities ga
* where
* gm.username = ? and g.id = ga.group_id and g.id = gm.group_id
* </code>
*
* @param query The query to use for selecting the authorities by group.
* Must contain a single parameter for the username.
* @return The {@link JdbcUserDetailsManagerRegistry} used for additional customizations
* @throws Exception
*/
public JdbcUserDetailsManagerConfigurer<B> groupAuthoritiesByUsername(String query) throws Exception {
JdbcUserDetailsManager userDetailsService = getUserDetailsService();
userDetailsService.setEnableGroups(true);
userDetailsService.setGroupAuthoritiesByUsernameQuery(query);
return this;
}
/**
* A non-empty string prefix that will be added to role strings loaded from persistent storage (default is "").
*
* @param rolePrefix
* @return
* @throws Exception
*/
public JdbcUserDetailsManagerConfigurer<B> rolePrefix(String rolePrefix) throws Exception {
getUserDetailsService().setRolePrefix(rolePrefix);
return this;
}
/**
* Defines the {@link UserCache} to use
*
* @param userCache the {@link UserCache} to use
* @return the {@link JdbcUserDetailsManagerConfigurer} for further customizations
* @throws Exception
*/
public JdbcUserDetailsManagerConfigurer<B> userCache(UserCache userCache) throws Exception {
getUserDetailsService().setUserCache(userCache);
return this;
}
@Override
protected void initUserDetailsService() throws Exception {
if(!initScripts.isEmpty()) {
getDataSourceInit().afterPropertiesSet();
}
super.initUserDetailsService();
}
@Override
public JdbcUserDetailsManager getUserDetailsService() {
return (JdbcUserDetailsManager) super.getUserDetailsService();
}
/**
* Populates the default schema that allows users and authorities to be stored.
*
* @return The {@link JdbcUserDetailsManagerRegistry} used for additional customizations
*/
public JdbcUserDetailsManagerConfigurer<B> withDefaultSchema() {
this.initScripts.add(new ClassPathResource("org/springframework/security/core/userdetails/jdbc/users.ddl"));
return this;
}
protected DatabasePopulator getDatabasePopulator() {
ResourceDatabasePopulator dbp = new ResourceDatabasePopulator();
dbp.setScripts(initScripts.toArray(new Resource[initScripts.size()]));
return dbp;
}
private DataSourceInitializer getDataSourceInit() {
DataSourceInitializer dsi = new DataSourceInitializer();
dsi.setDatabasePopulator(getDatabasePopulator());
dsi.setDataSource(dataSource);
return dsi;
}
}

View File

@ -0,0 +1,263 @@
/*
* Copyright 2002-2013 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.annotation.authentication.configurers.provisioning;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.springframework.security.config.annotation.SecurityBuilder;
import org.springframework.security.config.annotation.authentication.ProviderManagerBuilder;
import org.springframework.security.config.annotation.authentication.configurers.userdetails.UserDetailsServiceConfigurer;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.provisioning.UserDetailsManager;
import org.springframework.util.Assert;
/**
* Base class for populating an
* {@link org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder} with a
* {@link UserDetailsManager}.
*
* @param <B> the type of the {@link SecurityBuilder} that is being configured
* @param <C> the type of {@link UserDetailsManagerConfigurer}
*
* @author Rob Winch
* @since 3.2
*/
public class UserDetailsManagerConfigurer<B extends ProviderManagerBuilder<B>, C extends UserDetailsManagerConfigurer<B,C>> extends
UserDetailsServiceConfigurer<B,C,UserDetailsManager> {
private final List<UserDetailsBuilder> userBuilders = new ArrayList<UserDetailsBuilder>();
protected UserDetailsManagerConfigurer(UserDetailsManager userDetailsManager) {
super(userDetailsManager);
}
/**
* Populates the users that have been added.
*
* @throws Exception
*/
@Override
protected void initUserDetailsService() throws Exception {
for(UserDetailsBuilder userBuilder : userBuilders) {
getUserDetailsService().createUser(userBuilder.build());
}
}
/**
* Allows adding a user to the {@link UserDetailsManager} that is being created. This method can be invoked
* multiple times to add multiple users.
*
* @param username the username for the user being added. Cannot be null.
* @return
*/
@SuppressWarnings("unchecked")
public final UserDetailsBuilder withUser(String username) {
UserDetailsBuilder userBuilder = new UserDetailsBuilder((C)this);
userBuilder.username(username);
this.userBuilders.add(userBuilder);
return userBuilder;
}
/**
* Builds the user to be added. At minimum the username, password, and authorities should provided. The remaining
* attributes have reasonable defaults.
*
* @param <T> the type of {@link UserDetailsManagerConfigurer} to return for chaining methods.
*/
public class UserDetailsBuilder {
private String username;
private String password;
private List<GrantedAuthority> authorities;
private boolean accountExpired;
private boolean accountLocked;
private boolean credentialsExpired;
private boolean disabled;
private final C builder;
/**
* Creates a new instance
* @param builder the builder to return
*/
private UserDetailsBuilder(C builder) {
this.builder = builder;
}
/**
* Returns the {@link UserDetailsManagerRegistry} for method chaining (i.e. to add another user)
*
* @return the {@link UserDetailsManagerRegistry} for method chaining (i.e. to add another user)
*/
public C and() {
return builder;
}
/**
* Populates the username. This attribute is required.
*
* @param username the username. Cannot be null.
* @return the {@link UserDetailsBuilder} for method chaining (i.e. to populate additional attributes for this
* user)
*/
private UserDetailsBuilder username(String username) {
Assert.notNull(username, "username cannot be null");
this.username = username;
return this;
}
/**
* Populates the password. This attribute is required.
*
* @param password the password. Cannot be null.
* @return the {@link UserDetailsBuilder} for method chaining (i.e. to populate additional attributes for this
* user)
*/
public UserDetailsBuilder password(String password) {
Assert.notNull(password, "password cannot be null");
this.password = password;
return this;
}
/**
* Populates the roles. This method is a shortcut for calling {@link #authorities(String...)}, but automatically
* prefixes each entry with "ROLE_". This means the following:
*
* <code>
* builder.roles("USER","ADMIN");
* </code>
*
* is equivalent to
*
* <code>
* builder.authorities("ROLE_USER","ROLE_ADMIN");
* </code>
*
* <p>This attribute is required, but can also be populated with {@link #authorities(String...)}.</p>
*
* @param roles the roles for this user (i.e. USER, ADMIN, etc). Cannot be null, contain null values or start
* with "ROLE_"
* @return the {@link UserDetailsBuilder} for method chaining (i.e. to populate additional attributes for this
* user)
*/
public UserDetailsBuilder roles(String... roles) {
List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>(roles.length);
for(String role : roles) {
Assert.isTrue(!role.startsWith("ROLE_"), role + " cannot start with ROLE_ (it is automatically added)");
authorities.add(new SimpleGrantedAuthority("ROLE_"+role));
}
return authorities(authorities);
}
/**
* Populates the authorities. This attribute is required.
*
* @param authorities the authorities for this user. Cannot be null, or contain null
* values
* @return the {@link UserDetailsBuilder} for method chaining (i.e. to populate additional attributes for this
* user)
* @see #roles(String...)
*/
public UserDetailsBuilder authorities(GrantedAuthority...authorities) {
return authorities(Arrays.asList(authorities));
}
/**
* Populates the authorities. This attribute is required.
*
* @param authorities the authorities for this user. Cannot be null, or contain null
* values
* @return the {@link UserDetailsBuilder} for method chaining (i.e. to populate additional attributes for this
* user)
* @see #roles(String...)
*/
public UserDetailsBuilder authorities(List<? extends GrantedAuthority> authorities) {
this.authorities = new ArrayList<GrantedAuthority>(authorities);
return this;
}
/**
* Populates the authorities. This attribute is required.
*
* @param authorities the authorities for this user (i.e. ROLE_USER, ROLE_ADMIN, etc). Cannot be null, or contain null
* values
* @return the {@link UserDetailsBuilder} for method chaining (i.e. to populate additional attributes for this
* user)
* @see #roles(String...)
*/
public UserDetailsBuilder authorities(String... authorities) {
return authorities(AuthorityUtils.createAuthorityList(authorities));
}
/**
* Defines if the account is expired or not. Default is false.
*
* @param accountExpired true if the account is expired, false otherwise
* @return the {@link UserDetailsBuilder} for method chaining (i.e. to populate additional attributes for this
* user)
*/
public UserDetailsBuilder accountExpired(boolean accountExpired) {
this.accountExpired = accountExpired;
return this;
}
/**
* Defines if the account is locked or not. Default is false.
*
* @param accountLocked true if the account is locked, false otherwise
* @return the {@link UserDetailsBuilder} for method chaining (i.e. to populate additional attributes for this
* user)
*/
public UserDetailsBuilder accountLocked(boolean accountLocked) {
this.accountLocked = accountLocked;
return this;
}
/**
* Defines if the credentials are expired or not. Default is false.
*
* @param credentialsExpired true if the credentials are expired, false otherwise
* @return the {@link UserDetailsBuilder} for method chaining (i.e. to populate additional attributes for this
* user)
*/
public UserDetailsBuilder credentialsExpired(boolean credentialsExpired) {
this.credentialsExpired = credentialsExpired;
return this;
}
/**
* Defines if the account is disabled or not. Default is false.
*
* @param disabled true if the account is disabled, false otherwise
* @return the {@link UserDetailsBuilder} for method chaining (i.e. to populate additional attributes for this
* user)
*/
public UserDetailsBuilder disabled(boolean disabled) {
this.disabled = disabled;
return this;
}
private UserDetails build() {
return new User(username, password, !disabled, !accountExpired,
!credentialsExpired, !accountLocked, authorities);
}
}
}

View File

@ -0,0 +1,95 @@
/*
* Copyright 2002-2013 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.annotation.authentication.configurers.userdetails;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.SecurityBuilder;
import org.springframework.security.config.annotation.SecurityConfigurer;
import org.springframework.security.config.annotation.authentication.ProviderManagerBuilder;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
/**
* Allows configuring a {@link DaoAuthenticationProvider}
*
* @author Rob Winch
* @since 3.2
*
* @param <B> the type of the {@link SecurityBuilder}
* @param <C> the type of {@link AbstractDaoAuthenticationConfigurer} this is
* @param <U> The type of {@link UserDetailsService} that is being used
*
*/
abstract class AbstractDaoAuthenticationConfigurer<B extends ProviderManagerBuilder<B>, C extends AbstractDaoAuthenticationConfigurer<B,C,U>,U extends UserDetailsService> extends UserDetailsAwareConfigurer<B,U> {
private DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
private final U userDetailsService;
/**
* Creates a new instance
*
* @param userDetailsService
*/
protected AbstractDaoAuthenticationConfigurer(U userDetailsService) {
this.userDetailsService = userDetailsService;
provider.setUserDetailsService(userDetailsService);
}
/**
* Allows specifying the {@link PasswordEncoder} to use with the {@link DaoAuthenticationProvider}. The default is
* is to use plain text.
*
* @param passwordEncoder The {@link PasswordEncoder} to use.
* @return
*/
@SuppressWarnings("unchecked")
public C passwordEncoder(PasswordEncoder passwordEncoder) {
provider.setPasswordEncoder(passwordEncoder);
return (C) this;
}
/**
* Allows specifying the
* {@link org.springframework.security.authentication.encoding.PasswordEncoder}
* to use with the {@link DaoAuthenticationProvider}. The default is is to
* use plain text.
*
* @param passwordEncoder
* The
* {@link org.springframework.security.authentication.encoding.PasswordEncoder}
* to use.
* @return the {@link SecurityConfigurer} for further customizations
*/
@SuppressWarnings("unchecked")
public C passwordEncoder(org.springframework.security.authentication.encoding.PasswordEncoder passwordEncoder) {
provider.setPasswordEncoder(passwordEncoder);
return (C) this;
}
@Override
public void configure(B builder) throws Exception {
provider = postProcess(provider);
builder.authenticationProvider(provider);
}
/**
* Gets the {@link UserDetailsService} that is used with the {@link DaoAuthenticationProvider}
*
* @return the {@link UserDetailsService} that is used with the {@link DaoAuthenticationProvider}
*/
public U getUserDetailsService() {
return userDetailsService;
}
}

View File

@ -0,0 +1,41 @@
/*
* Copyright 2002-2013 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.annotation.authentication.configurers.userdetails;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.ProviderManagerBuilder;
import org.springframework.security.core.userdetails.UserDetailsService;
/**
* Allows configuring a {@link DaoAuthenticationProvider}
*
* @author Rob Winch
* @since 3.2
*
* @param <B> The type of {@link ProviderManagerBuilder} this is
* @param <U> The type of {@link UserDetailsService} that is being used
*
*/
public class DaoAuthenticationConfigurer<B extends ProviderManagerBuilder<B>, U extends UserDetailsService> extends AbstractDaoAuthenticationConfigurer<B,DaoAuthenticationConfigurer<B,U>, U>{
/**
* Creates a new instance
* @param userDetailsService
*/
public DaoAuthenticationConfigurer(U userDetailsService) {
super(userDetailsService);
}
}

View File

@ -0,0 +1,39 @@
/*
* Copyright 2002-2013 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.annotation.authentication.configurers.userdetails;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
import org.springframework.security.config.annotation.authentication.ProviderManagerBuilder;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.core.userdetails.UserDetailsService;
/**
* Base class that allows access to the {@link UserDetailsService} for using as a default value with {@link AuthenticationManagerBuilder}.
*
* @author Rob Winch
*
* @param <B> the type of the {@link ProviderManagerBuilder}
* @param <U> the type of {@link UserDetailsService}
*/
public abstract class UserDetailsAwareConfigurer<B extends ProviderManagerBuilder<B>, U extends UserDetailsService> extends SecurityConfigurerAdapter<AuthenticationManager,B> {
/**
* Gets the {@link UserDetailsService} or null if it is not available
* @return the {@link UserDetailsService} or null if it is not available
*/
public abstract U getUserDetailsService();
}

View File

@ -0,0 +1,58 @@
/*
* Copyright 2002-2013 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.annotation.authentication.configurers.userdetails;
import org.springframework.security.config.annotation.SecurityConfigurer;
import org.springframework.security.config.annotation.authentication.ProviderManagerBuilder;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.core.userdetails.UserDetailsService;
/**
* Allows configuring a {@link UserDetailsService} within a {@link AuthenticationManagerBuilder}.
*
* @author Rob Winch
* @since 3.2
*
* @param <B> the type of the {@link SecurityBuilder}
* @param <C> the {@link SecurityConfigurer} (or this)
* @param <U> the type of UserDetailsService being used to allow for returning the concrete UserDetailsService.
*/
public class UserDetailsServiceConfigurer<B extends ProviderManagerBuilder<B>,
C extends UserDetailsServiceConfigurer<B, C, U>,
U extends UserDetailsService>
extends AbstractDaoAuthenticationConfigurer<B, C, U> {
/**
* Creates a new instance
* @param userDetailsService the {@link UserDetailsService} that should be used
*/
public UserDetailsServiceConfigurer(U userDetailsService) {
super(userDetailsService);
}
@Override
public void configure(B builder) throws Exception {
initUserDetailsService();
super.configure(builder);
}
/**
* Allows subclasses to initialize the {@link UserDetailsService}. For example, it might add users, initialize
* schema, etc.
*/
protected void initUserDetailsService() throws Exception {}
}

View File

@ -0,0 +1,77 @@
/*
* Copyright 2002-2013 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.annotation.configuration;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.Aware;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.util.Assert;
/**
* Allows registering Objects to participate with an
* {@link AutowireCapableBeanFactory}'s post processing of {@link Aware}
* methods, {@link InitializingBean#afterPropertiesSet()}, and
* {@link DisposableBean#destroy()}.
*
* @author Rob Winch
* @since 3.2
*/
final class AutowireBeanFactoryObjectPostProcessor implements ObjectPostProcessor<Object>, DisposableBean {
private final Log logger = LogFactory.getLog(getClass());
private final AutowireCapableBeanFactory autowireBeanFactory;
private final List<DisposableBean> disposableBeans = new ArrayList<DisposableBean>();
public AutowireBeanFactoryObjectPostProcessor(
AutowireCapableBeanFactory autowireBeanFactory) {
Assert.notNull(autowireBeanFactory, "autowireBeanFactory cannot be null");
this.autowireBeanFactory = autowireBeanFactory;
}
/* (non-Javadoc)
* @see org.springframework.security.config.annotation.web.Initializer#initialize(java.lang.Object)
*/
@SuppressWarnings("unchecked")
@Override
public <T> T postProcess(T object) {
T result = (T) autowireBeanFactory.initializeBean(object, null);
if(result instanceof DisposableBean) {
disposableBeans.add((DisposableBean) result);
}
return result;
}
/* (non-Javadoc)
* @see org.springframework.beans.factory.DisposableBean#destroy()
*/
@Override
public void destroy() throws Exception {
for(DisposableBean disposable : disposableBeans) {
try {
disposable.destroy();
} catch(Exception error) {
logger.error(error);
}
}
}
}

View File

@ -0,0 +1,44 @@
/*
* Copyright 2002-2013 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.annotation.configuration;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
/**
* Spring {@link Configuration} that exports the default
* {@link ObjectPostProcessor}. This class is not intended to be imported
* manually rather it is imported automatically when using
* {@link EnableWebSecurity} or {@link EnableGlobalMethodSecurity}.
*
* @see EnableWebSecurity
* @see EnableGlobalMethodSecurity
*
* @author Rob Winch
* @since 3.2
*/
@Configuration
public class ObjectPostProcessorConfiguration {
@Bean
public ObjectPostProcessor<Object> objectPostProcessor(AutowireCapableBeanFactory beanFactory) {
return new AutowireBeanFactoryObjectPostProcessor(beanFactory);
}
}

View File

@ -0,0 +1,101 @@
/*
* Copyright 2002-2013 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.annotation.method.configuration;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import org.springframework.context.annotation.AdviceMode;
import org.springframework.context.annotation.Import;
import org.springframework.core.Ordered;
import org.springframework.security.access.annotation.Secured;
import org.springframework.security.config.annotation.configuration.ObjectPostProcessorConfiguration;
/**
* <p>Enables Spring Security global method security similar to the
* <global-method-security> xml support.</p>
*
* <p>
* More advanced configurations may wish to extend
* {@link GlobalMethodSecurityConfiguration} and override the protected methods
* to provide custom implementations. Note that
* {@link EnableGlobalMethodSecurity} still must be included on the class
* extending {@link GlobalMethodSecurityConfiguration} to determine the
* settings.
*
* @author Rob Winch
* @since 3.2
*/
@Retention(value=java.lang.annotation.RetentionPolicy.RUNTIME)
@Target(value={java.lang.annotation.ElementType.TYPE})
@Documented
@Import({GlobalMethodSecuritySelector.class,ObjectPostProcessorConfiguration.class})
public @interface EnableGlobalMethodSecurity {
/**
* Determines if Spring Security's pre post annotations should be enabled. Default is false.
* @return true if pre post annotations should be enabled false otherwise.
*/
boolean prePostEnabled() default false;
/**
* Determines if Spring Security's {@link Secured} annotations should be enabled.
* @return true if {@link Secured} annotations should be enabled false otherwise. Default is false.
*/
boolean securedEnabled() default false;
/**
* Determines if JSR-250 annotations should be enabled. Default is false.
* @return true if JSR-250 should be enabled false otherwise.
*/
boolean jsr250Enabled() default false;
/**
* Indicate whether subclass-based (CGLIB) proxies are to be created ({@code true}) as
* opposed to standard Java interface-based proxies ({@code false}). The default is
* {@code false}. <strong>Applicable only if {@link #mode()} is set to
* {@link AdviceMode#PROXY}</strong>.
*
* <p>Note that setting this attribute to {@code true} will affect <em>all</em>
* Spring-managed beans requiring proxying, not just those marked with
* the Security annotations. For example, other beans marked with Spring's
* {@code @Transactional} annotation will be upgraded to subclass proxying at the same
* time. This approach has no negative impact in practice unless one is explicitly
* expecting one type of proxy vs another, e.g. in tests.
*
* @return true if CGILIB proxies should be created instead of interface based proxies, else false
*/
boolean proxyTargetClass() default false;
/**
* Indicate how security advice should be applied. The default is
* {@link AdviceMode#PROXY}.
* @see AdviceMode
*
* @return the {@link AdviceMode} to use
*/
AdviceMode mode() default AdviceMode.PROXY;
/**
* Indicate the ordering of the execution of the security advisor
* when multiple advices are applied at a specific joinpoint.
* The default is {@link Ordered#LOWEST_PRECEDENCE}.
*
* @return the order the security advisor should be applied
*/
int order() default Ordered.LOWEST_PRECEDENCE;
}

View File

@ -0,0 +1,68 @@
/*
* Copyright 2002-2013 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.annotation.method.configuration;
import java.util.Map;
import org.springframework.aop.config.AopConfigUtils;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.type.AnnotationMetadata;
/**
* Registers an
* {@link org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator
* AnnotationAwareAspectJAutoProxyCreator} against the current
* {@link BeanDefinitionRegistry} as appropriate based on a given @
* {@link EnableGlobalMethodSecurity} annotation.
*
* <p>
* Note: This class is necessary because AspectJAutoProxyRegistrar only supports
* EnableAspectJAutoProxy.
* </p>
*
* @author Rob Winch
* @since 3.2
*/
class GlobalMethodSecurityAspectJAutoProxyRegistrar implements
ImportBeanDefinitionRegistrar {
/**
* Register, escalate, and configure the AspectJ auto proxy creator based on
* the value of the @{@link EnableGlobalMethodSecurity#proxyTargetClass()}
* attribute on the importing {@code @Configuration} class.
*/
@Override
public void registerBeanDefinitions(
AnnotationMetadata importingClassMetadata,
BeanDefinitionRegistry registry) {
AopConfigUtils
.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);
Map<String, Object> annotationAttributes = importingClassMetadata
.getAnnotationAttributes(EnableGlobalMethodSecurity.class
.getName());
AnnotationAttributes enableAJAutoProxy = AnnotationAttributes
.fromMap(annotationAttributes);
if (enableAJAutoProxy.getBoolean("proxyTargetClass")) {
AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
}
}
}

View File

@ -0,0 +1,385 @@
/*
* Copyright 2002-2013 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.annotation.method.configuration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import org.aopalliance.intercept.MethodInterceptor;
import org.springframework.aop.framework.ProxyFactoryBean;
import org.springframework.aop.target.LazyInitTargetSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportAware;
import org.springframework.context.annotation.Role;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDecisionVoter;
import org.springframework.security.access.AfterInvocationProvider;
import org.springframework.security.access.annotation.Jsr250MethodSecurityMetadataSource;
import org.springframework.security.access.annotation.SecuredAnnotationSecurityMetadataSource;
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
import org.springframework.security.access.expression.method.ExpressionBasedAnnotationAttributeFactory;
import org.springframework.security.access.expression.method.ExpressionBasedPostInvocationAdvice;
import org.springframework.security.access.expression.method.ExpressionBasedPreInvocationAdvice;
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
import org.springframework.security.access.intercept.AfterInvocationManager;
import org.springframework.security.access.intercept.AfterInvocationProviderManager;
import org.springframework.security.access.intercept.RunAsManager;
import org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor;
import org.springframework.security.access.intercept.aopalliance.MethodSecurityMetadataSourceAdvisor;
import org.springframework.security.access.method.DelegatingMethodSecurityMetadataSource;
import org.springframework.security.access.method.MethodSecurityMetadataSource;
import org.springframework.security.access.prepost.PostInvocationAdviceProvider;
import org.springframework.security.access.prepost.PreInvocationAuthorizationAdvice;
import org.springframework.security.access.prepost.PreInvocationAuthorizationAdviceVoter;
import org.springframework.security.access.prepost.PrePostAnnotationSecurityMetadataSource;
import org.springframework.security.access.vote.AffirmativeBased;
import org.springframework.security.access.vote.AuthenticatedVoter;
import org.springframework.security.access.vote.RoleVoter;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.DefaultAuthenticationEventPublisher;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.util.Assert;
/**
* Base {@link Configuration} for enabling global method security. Classes may
* extend this class to customize the defaults, but must be sure to specify the
* {@link EnableGlobalMethodSecurity} annotation on the subclass.
*
* @author Rob Winch
* @since 3.2
* @see EnableGlobalMethodSecurity
*/
@Configuration
public class GlobalMethodSecurityConfiguration implements ImportAware {
@Autowired
private ApplicationContext context;
@Autowired(required=false)
private ObjectPostProcessor<Object> objectPostProcessor = new ObjectPostProcessor<Object>() {
@Override
public <T> T postProcess(T object) {
throw new IllegalStateException(ObjectPostProcessor.class.getName()+ " is a required bean. Ensure you have used @"+EnableGlobalMethodSecurity.class.getName());
}
};
private AuthenticationManager authenticationManager;
private AuthenticationManagerBuilder auth = new AuthenticationManagerBuilder();
private boolean disableAuthenticationRegistry;
private AnnotationAttributes enableMethodSecurity;
private MethodSecurityExpressionHandler expressionHandler;
/**
* Creates the default MethodInterceptor which is a MethodSecurityInterceptor using the following methods to
* construct it.
* <ul>
* <li>{@link #accessDecisionManager()}</li>
* <li>{@link #afterInvocationManager()}</li>
* <li>{@link #authenticationManager()}</li>
* <li>{@link #methodSecurityMetadataSource()}</li>
* <li>{@link #runAsManager()}</li>
*
* </ul>
*
* <p>
* Subclasses can override this method to provide a different {@link MethodInterceptor}.
* </p>
*
* @return
* @throws Exception
*/
@Bean
public MethodInterceptor methodSecurityInterceptor() throws Exception {
MethodSecurityInterceptor methodSecurityInterceptor = new MethodSecurityInterceptor();
methodSecurityInterceptor
.setAccessDecisionManager(accessDecisionManager());
methodSecurityInterceptor
.setAfterInvocationManager(afterInvocationManager());
methodSecurityInterceptor
.setAuthenticationManager(authenticationManager());
methodSecurityInterceptor
.setSecurityMetadataSource(methodSecurityMetadataSource());
RunAsManager runAsManager = runAsManager();
if (runAsManager != null) {
methodSecurityInterceptor.setRunAsManager(runAsManager);
}
return methodSecurityInterceptor;
}
/**
* Provide a custom {@link AfterInvocationManager} for the default
* implementation of {@link #methodSecurityInterceptor()}. The default is
* null if pre post is not enabled. Otherwise, it returns a {@link AfterInvocationProviderManager}.
*
* <p>
* Subclasses should override this method to provide a custom {@link AfterInvocationManager}
* </p>
*
* @return
*/
protected AfterInvocationManager afterInvocationManager() {
if(prePostEnabled()) {
AfterInvocationProviderManager invocationProviderManager = new AfterInvocationProviderManager();
ExpressionBasedPostInvocationAdvice postAdvice = new ExpressionBasedPostInvocationAdvice(getExpressionHandler());
PostInvocationAdviceProvider postInvocationAdviceProvider = new PostInvocationAdviceProvider(postAdvice);
List<AfterInvocationProvider> afterInvocationProviders = new ArrayList<AfterInvocationProvider>();
afterInvocationProviders.add(postInvocationAdviceProvider);
invocationProviderManager.setProviders(afterInvocationProviders);
return invocationProviderManager;
}
return null;
}
/**
* Provide a custom {@link RunAsManager} for the default implementation of
* {@link #methodSecurityInterceptor()}. The default is null.
*
* @return
*/
protected RunAsManager runAsManager() {
return null;
}
/**
* Allows subclasses to provide a custom {@link AccessDecisionManager}. The default is a {@link AffirmativeBased}
* with the following voters:
*
* <ul>
* <li>{@link PreInvocationAuthorizationAdviceVoter}</li>
* <li>{@link RoleVoter} </li>
* <li>{@link AuthenticatedVoter} </li>
* </ul>
*
* @return
*/
@SuppressWarnings("rawtypes")
protected AccessDecisionManager accessDecisionManager() {
List<AccessDecisionVoter> decisionVoters = new ArrayList<AccessDecisionVoter>();
ExpressionBasedPreInvocationAdvice expressionAdvice = new ExpressionBasedPreInvocationAdvice();
expressionAdvice.setExpressionHandler(getExpressionHandler());
decisionVoters.add(new PreInvocationAuthorizationAdviceVoter(
expressionAdvice));
decisionVoters.add(new RoleVoter());
decisionVoters.add(new AuthenticatedVoter());
return new AffirmativeBased(decisionVoters);
}
/**
* Provide a {@link MethodSecurityExpressionHandler} that is
* registered with the {@link ExpressionBasedPreInvocationAdvice}. The default is
* {@link DefaultMethodSecurityExpressionHandler}
*
* <p>Subclasses may override this method to provide a custom {@link MethodSecurityExpressionHandler}</p>
*
* @return
*/
protected MethodSecurityExpressionHandler expressionHandler() {
return new DefaultMethodSecurityExpressionHandler();
}
/**
* Gets the {@link MethodSecurityExpressionHandler} or creates it using {@link #expressionHandler}.
*
* @return a non {@code null} {@link MethodSecurityExpressionHandler}
*/
protected final MethodSecurityExpressionHandler getExpressionHandler() {
if(expressionHandler == null) {
expressionHandler = expressionHandler();
}
return expressionHandler;
}
/**
* Provides a custom {@link MethodSecurityMetadataSource} that is registered
* with the {@link #methodSecurityMetadataSource()}. Default is null.
*
* @return a custom {@link MethodSecurityMetadataSource} that is registered
* with the {@link #methodSecurityMetadataSource()}
*/
protected MethodSecurityMetadataSource customMethodSecurityMetadataSource() {
return null;
}
/**
* Allows providing a custom {@link AuthenticationManager}. The default is
* to use any authentication mechanisms registered by {@link #registerAuthentication(AuthenticationManagerBuilder)}. If
* {@link #registerAuthentication(AuthenticationManagerBuilder)} was not overriden, then an {@link AuthenticationManager}
* is attempted to be autowired by type.
*
* @return
*/
protected AuthenticationManager authenticationManager() throws Exception {
if(authenticationManager == null) {
DefaultAuthenticationEventPublisher eventPublisher = objectPostProcessor.postProcess(new DefaultAuthenticationEventPublisher());
auth.authenticationEventPublisher(eventPublisher);
auth.objectPostProcessor(objectPostProcessor);
registerAuthentication(auth);
if(!disableAuthenticationRegistry) {
authenticationManager = auth.build();
}
if(authenticationManager == null) {
authenticationManager = lazyBean(AuthenticationManager.class);
}
}
return authenticationManager;
}
/**
* Sub classes can override this method to register different types of authentication. If not overridden,
* {@link #registerAuthentication(AuthenticationManagerBuilder)} will attempt to autowire by type.
*
* @param auth the {@link AuthenticationManagerBuilder} used to register different authentication mechanisms for the
* global method security.
* @throws Exception
*/
protected void registerAuthentication(AuthenticationManagerBuilder auth) throws Exception {
this.disableAuthenticationRegistry = true;
}
/**
* Provides the default {@link MethodSecurityMetadataSource} that will be
* used. It creates a {@link DelegatingMethodSecurityMetadataSource} based
* upon {@link #customMethodSecurityMetadataSource()} and the attributes on
* {@link EnableGlobalMethodSecurity}.
*
* @return
*/
@Bean
public MethodSecurityMetadataSource methodSecurityMetadataSource() {
List<MethodSecurityMetadataSource> sources = new ArrayList<MethodSecurityMetadataSource>();
ExpressionBasedAnnotationAttributeFactory attributeFactory = new ExpressionBasedAnnotationAttributeFactory(
methodExpressionHandler());
MethodSecurityMetadataSource customMethodSecurityMetadataSource = customMethodSecurityMetadataSource();
if (customMethodSecurityMetadataSource != null) {
sources.add(customMethodSecurityMetadataSource);
}
if (prePostEnabled()) {
sources.add(new PrePostAnnotationSecurityMetadataSource(
attributeFactory));
}
if (securedEnabled()) {
sources.add(new SecuredAnnotationSecurityMetadataSource());
}
if (jsr250Enabled()) {
sources.add(new Jsr250MethodSecurityMetadataSource());
}
return new DelegatingMethodSecurityMetadataSource(sources);
}
/**
* Creates the {@link MethodSecurityExpressionHandler} to be used.
*
* @return
*/
@Bean
public MethodSecurityExpressionHandler methodExpressionHandler() {
return new DefaultMethodSecurityExpressionHandler();
}
/**
* Creates the {@link PreInvocationAuthorizationAdvice} to be used. The
* default is {@link ExpressionBasedPreInvocationAdvice}.
*
* @return
*/
@Bean
public PreInvocationAuthorizationAdvice preInvocationAuthorizationAdvice() {
ExpressionBasedPreInvocationAdvice preInvocationAdvice = new ExpressionBasedPreInvocationAdvice();
preInvocationAdvice.setExpressionHandler(methodExpressionHandler());
return preInvocationAdvice;
}
/**
* Obtains the {@link MethodSecurityMetadataSourceAdvisor} to be used.
*
* @return
*/
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
@Bean
public MethodSecurityMetadataSourceAdvisor metaDataSourceAdvisor() {
MethodSecurityMetadataSourceAdvisor methodAdvisor = new MethodSecurityMetadataSourceAdvisor(
"methodSecurityInterceptor", methodSecurityMetadataSource(),
"methodSecurityMetadataSource");
methodAdvisor.setOrder(order());
return methodAdvisor;
}
/**
* Obtains the attributes from {@link EnableGlobalMethodSecurity} if this class was imported using the {@link EnableGlobalMethodSecurity} annotation.
*/
@Override
public final void setImportMetadata(AnnotationMetadata importMetadata) {
Map<String, Object> annotationAttributes = importMetadata
.getAnnotationAttributes(EnableGlobalMethodSecurity.class
.getName());
enableMethodSecurity = AnnotationAttributes
.fromMap(annotationAttributes);
}
@SuppressWarnings("unchecked")
private <T> T lazyBean(Class<T> interfaceName) {
LazyInitTargetSource lazyTargetSource = new LazyInitTargetSource();
String[] beanNamesForType = context.getBeanNamesForType(interfaceName);
Assert.isTrue(beanNamesForType.length == 1 , "Expecting to only find a single bean for type " + interfaceName + ", but found " + Arrays.asList(beanNamesForType));
lazyTargetSource.setTargetBeanName(beanNamesForType[0]);
lazyTargetSource.setBeanFactory(context);
ProxyFactoryBean proxyFactory = new ProxyFactoryBean();
proxyFactory.setTargetSource(lazyTargetSource);
proxyFactory.setInterfaces(new Class[] { interfaceName });
return (T) proxyFactory.getObject();
}
private boolean prePostEnabled() {
return enableMethodSecurity().getBoolean("prePostEnabled");
}
private boolean securedEnabled() {
return enableMethodSecurity().getBoolean("securedEnabled");
}
private boolean jsr250Enabled() {
return enableMethodSecurity().getBoolean("jsr250Enabled");
}
private int order() {
return (Integer) enableMethodSecurity().get("order");
}
private AnnotationAttributes enableMethodSecurity() {
if (enableMethodSecurity == null) {
// if it is null look at this instance (i.e. a subclass was used)
EnableGlobalMethodSecurity methodSecurityAnnotation = AnnotationUtils
.findAnnotation(getClass(),
EnableGlobalMethodSecurity.class);
Assert.notNull(methodSecurityAnnotation,
EnableGlobalMethodSecurity.class.getName() + " is required");
Map<String, Object> methodSecurityAttrs = AnnotationUtils
.getAnnotationAttributes(methodSecurityAnnotation);
this.enableMethodSecurity = AnnotationAttributes
.fromMap(methodSecurityAttrs);
}
return this.enableMethodSecurity;
}
}

View File

@ -0,0 +1,59 @@
/*
* Copyright 2002-2013 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.annotation.method.configuration;
import java.util.Map;
import org.springframework.context.annotation.AdviceMode;
import org.springframework.context.annotation.AutoProxyRegistrar;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
/**
* Dynamically determines which imports to include using the
* {@link EnableGlobalMethodSecurity} annotation.
*
* @author Rob Winch
* @since 3.2
*/
final class GlobalMethodSecuritySelector implements ImportSelector {
@Override
public final String[] selectImports(AnnotationMetadata importingClassMetadata) {
Class<EnableGlobalMethodSecurity> annoType = EnableGlobalMethodSecurity.class;
Map<String, Object> annotationAttributes = importingClassMetadata.getAnnotationAttributes(annoType.getName(), false);
AnnotationAttributes attributes = AnnotationAttributes.fromMap(annotationAttributes);
Assert.notNull(attributes, String.format(
"@%s is not present on importing class '%s' as expected",
annoType.getSimpleName(), importingClassMetadata.getClassName()));
// TODO would be nice if could use BeanClassLoaderAware (does not work)
Class<?> importingClass = ClassUtils.resolveClassName(importingClassMetadata.getClassName(), ClassUtils.getDefaultClassLoader());
boolean skipMethodSecurityConfiguration = GlobalMethodSecurityConfiguration.class.isAssignableFrom(importingClass);
AdviceMode mode = attributes.getEnum("mode");
String autoProxyClassName = AdviceMode.PROXY == mode ? AutoProxyRegistrar.class.getName()
: GlobalMethodSecurityAspectJAutoProxyRegistrar.class.getName();
if(skipMethodSecurityConfiguration) {
return new String[] { autoProxyClassName };
}
return new String[] { autoProxyClassName,
GlobalMethodSecurityConfiguration.class.getName()};
}
}

View File

@ -0,0 +1,198 @@
/*
* Copyright 2002-2013 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.annotation.web;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.SecurityBuilder;
import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configurers.AbstractRequestMatcherMappingConfigurer;
import org.springframework.security.web.util.AntPathRequestMatcher;
import org.springframework.security.web.util.AnyRequestMatcher;
import org.springframework.security.web.util.RegexRequestMatcher;
import org.springframework.security.web.util.RequestMatcher;
/**
* A base class for registering {@link RequestMatcher}'s. For example, it might allow for specifying which
* {@link RequestMatcher} require a certain level of authorization.
*
*
* @param <B> The Builder that is building Object O and is configured by this {@link AbstractRequestMatcherMappingConfigurer}
* @param <C> The object that is returned or Chained after creating the RequestMatcher
* @param <O> The Object being built by Builder B
*
* @author Rob Winch
* @since 3.2
*/
public abstract class AbstractRequestMatcherConfigurer<B extends SecurityBuilder<O>,C,O> extends SecurityConfigurerAdapter<O,B> {
private static final RequestMatcher ANY_REQUEST = new AnyRequestMatcher();
/**
* Maps any request.
*
* @param method the {@link HttpMethod} to use or {@code null} for any {@link HttpMethod}.
* @param antPatterns the ant patterns to create {@link org.springframework.security.web.util.AntPathRequestMatcher}
* from
*
* @return the object that is chained after creating the {@link RequestMatcher}
*/
public C anyRequest() {
return requestMatchers(ANY_REQUEST);
}
/**
* Maps a {@link List} of {@link org.springframework.security.web.util.AntPathRequestMatcher} instances.
*
* @param method the {@link HttpMethod} to use or {@code null} for any {@link HttpMethod}.
* @param antPatterns the ant patterns to create {@link org.springframework.security.web.util.AntPathRequestMatcher}
* from
*
* @return the object that is chained after creating the {@link RequestMatcher}
*/
public C antMatchers(HttpMethod method, String... antPatterns) {
return chainRequestMatchers(RequestMatchers.antMatchers(method, antPatterns));
}
/**
* Maps a {@link List} of {@link org.springframework.security.web.util.AntPathRequestMatcher} instances that do
* not care which {@link HttpMethod} is used.
*
* @param antPatterns the ant patterns to create {@link org.springframework.security.web.util.AntPathRequestMatcher}
* from
*
* @return the object that is chained after creating the {@link RequestMatcher}
*/
public C antMatchers(String... antPatterns) {
return chainRequestMatchers(RequestMatchers.antMatchers(antPatterns));
}
/**
* Maps a {@link List} of {@link org.springframework.security.web.util.RegexRequestMatcher} instances.
*
* @param method the {@link HttpMethod} to use or {@code null} for any {@link HttpMethod}.
* @param regexPatterns the regular expressions to create
* {@link org.springframework.security.web.util.RegexRequestMatcher} from
*
* @return the object that is chained after creating the {@link RequestMatcher}
*/
public C regexMatchers(HttpMethod method, String... regexPatterns) {
return chainRequestMatchers(RequestMatchers.regexMatchers(method,
regexPatterns));
}
/**
* Create a {@link List} of {@link org.springframework.security.web.util.RegexRequestMatcher} instances that do not
* specify an {@link HttpMethod}.
*
* @param regexPatterns the regular expressions to create
* {@link org.springframework.security.web.util.RegexRequestMatcher} from
*
* @return the object that is chained after creating the {@link RequestMatcher}
*/
public C regexMatchers(String... regexPatterns) {
return chainRequestMatchers(RequestMatchers.regexMatchers(regexPatterns));
}
/**
* Associates a list of {@link RequestMatcher} instances with the {@link AbstractRequestMatcherMappingConfigurer}
*
* @param requestMatchers the {@link RequestMatcher} instances
*
* @return the object that is chained after creating the {@link RequestMatcher}
*/
public C requestMatchers(RequestMatcher... requestMatchers) {
return chainRequestMatchers(Arrays.asList(requestMatchers));
}
/**
* Subclasses should implement this method for returning the object that is chained to the creation of the
* {@link RequestMatcher} instances.
*
* @param requestMatchers the {@link RequestMatcher} instances that were created
* @return the chained Object for the subclass which allows association of something else to the
* {@link RequestMatcher}
*/
protected abstract C chainRequestMatchers(List<RequestMatcher> requestMatchers);
/**
* Utilities for creating {@link RequestMatcher} instances.
*
* @author Rob Winch
* @since 3.2
*/
private static final class RequestMatchers {
/**
* Create a {@link List} of {@link AntPathRequestMatcher} instances.
*
* @param httpMethod the {@link HttpMethod} to use or {@code null} for any {@link HttpMethod}.
* @param antPatterns the ant patterns to create {@link AntPathRequestMatcher} from
*
* @return a {@link List} of {@link AntPathRequestMatcher} instances
*/
public static List<RequestMatcher> antMatchers(HttpMethod httpMethod, String...antPatterns) {
String method = httpMethod == null ? null : httpMethod.toString();
List<RequestMatcher> matchers = new ArrayList<RequestMatcher>();
for(String pattern : antPatterns) {
matchers.add(new AntPathRequestMatcher(pattern, method));
}
return matchers;
}
/**
* Create a {@link List} of {@link AntPathRequestMatcher} instances that do not specify an {@link HttpMethod}.
*
* @param antPatterns the ant patterns to create {@link AntPathRequestMatcher} from
*
* @return a {@link List} of {@link AntPathRequestMatcher} instances
*/
public static List<RequestMatcher> antMatchers(String...antPatterns) {
return antMatchers(null, antPatterns);
}
/**
* Create a {@link List} of {@link RegexRequestMatcher} instances.
*
* @param httpMethod the {@link HttpMethod} to use or {@code null} for any {@link HttpMethod}.
* @param regexPatterns the regular expressions to create {@link RegexRequestMatcher} from
*
* @return a {@link List} of {@link RegexRequestMatcher} instances
*/
public static List<RequestMatcher> regexMatchers(HttpMethod httpMethod, String...regexPatterns) {
String method = httpMethod == null ? null : httpMethod.toString();
List<RequestMatcher> matchers = new ArrayList<RequestMatcher>();
for(String pattern : regexPatterns) {
matchers.add(new RegexRequestMatcher(pattern, method));
}
return matchers;
}
/**
* Create a {@link List} of {@link RegexRequestMatcher} instances that do not specify an {@link HttpMethod}.
*
* @param regexPatterns the regular expressions to create {@link RegexRequestMatcher} from
*
* @return a {@link List} of {@link RegexRequestMatcher} instances
*/
public static List<RequestMatcher> regexMatchers(String...regexPatterns) {
return regexMatchers(null, regexPatterns);
}
private RequestMatchers() {}
}
}

View File

@ -0,0 +1,177 @@
/*
* Copyright 2002-2013 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.annotation.web;
import javax.servlet.Filter;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.config.annotation.SecurityBuilder;
import org.springframework.security.config.annotation.SecurityConfigurer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.openid.OpenIDAuthenticationFilter;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.access.ExceptionTranslationFilter;
import org.springframework.security.web.access.channel.ChannelProcessingFilter;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.AnonymousAuthenticationFilter;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutFilter;
import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter;
import org.springframework.security.web.authentication.preauth.x509.X509AuthenticationFilter;
import org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter;
import org.springframework.security.web.authentication.switchuser.SwitchUserFilter;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.security.web.authentication.www.DigestAuthenticationFilter;
import org.springframework.security.web.context.SecurityContextPersistenceFilter;
import org.springframework.security.web.jaasapi.JaasApiIntegrationFilter;
import org.springframework.security.web.savedrequest.RequestCacheAwareFilter;
import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter;
import org.springframework.security.web.session.ConcurrentSessionFilter;
import org.springframework.security.web.session.SessionManagementFilter;
/**
*
* @author Rob Winch
*
* @param <H>
*/
public interface HttpSecurityBuilder<H extends HttpSecurityBuilder<H>> extends SecurityBuilder<DefaultSecurityFilterChain> {
/**
* Gets the {@link SecurityConfigurer} by its class name or
* <code>null</code> if not found. Note that object hierarchies are not
* considered.
*
* @param clazz the Class of the {@link SecurityConfigurer} to attempt to get.
*/
<C extends SecurityConfigurer<DefaultSecurityFilterChain, H>> C getConfigurer(
Class<C> clazz);
/**
* Removes the {@link SecurityConfigurer} by its class name or
* <code>null</code> if not found. Note that object hierarchies are not
* considered.
*
* @param clazz the Class of the {@link SecurityConfigurer} to attempt to remove.
* @return the {@link SecurityConfigurer} that was removed or null if not found
*/
<C extends SecurityConfigurer<DefaultSecurityFilterChain, H>> C removeConfigurer(Class<C> clazz);
/**
* Sets an object that is shared by multiple {@link SecurityConfigurer}.
*
* @param sharedType the Class to key the shared object by.
* @param object the Object to store
*/
<C> void setSharedObject(Class<C> sharedType, C object);
/**
* Gets a shared Object. Note that object heirarchies are not considered.
*
* @param sharedType the type of the shared Object
* @return the shared Object or null if it is not found
*/
<C> C getSharedObject(Class<C> sharedType);
/**
* Allows adding an additional {@link AuthenticationProvider} to be used
*
* @param authenticationProvider the {@link AuthenticationProvider} to be added
* @return the {@link HttpSecurity} for further customizations
*/
H authenticationProvider(
AuthenticationProvider authenticationProvider);
/**
* Allows adding an additional {@link UserDetailsService} to be used
*
* @param userDetailsService the {@link UserDetailsService} to be added
* @return the {@link HttpSecurity} for further customizations
*/
H userDetailsService(
UserDetailsService userDetailsService) throws Exception;
/**
* Allows adding a {@link Filter} after one of the known {@link Filter}
* classes. The known {@link Filter} instances are either a {@link Filter}
* listed in {@link #addFilter(Filter)} or a {@link Filter} that has already
* been added using {@link #addFilterAfter(Filter, Class)} or
* {@link #addFilterBefore(Filter, Class)}.
*
* @param filter the {@link Filter} to register before the type {@code afterFilter}
* @param afterFilter the Class of the known {@link Filter}.
* @return the {@link HttpSecurity} for further customizations
*/
H addFilterAfter(Filter filter,
Class<? extends Filter> afterFilter);
/**
* Allows adding a {@link Filter} before one of the known {@link Filter}
* classes. The known {@link Filter} instances are either a {@link Filter}
* listed in {@link #addFilter(Filter)} or a {@link Filter} that has already
* been added using {@link #addFilterAfter(Filter, Class)} or
* {@link #addFilterBefore(Filter, Class)}.
*
* @param filter the {@link Filter} to register before the type {@code beforeFilter}
* @param beforeFilter the Class of the known {@link Filter}.
* @return the {@link HttpSecurity} for further customizations
*/
H addFilterBefore(Filter filter,
Class<? extends Filter> beforeFilter);
/**
* Adds a {@link Filter} that must be an instance of or extend one of the
* Filters provided within the Security framework. The method ensures that
* the ordering of the Filters is automatically taken care of.
*
* The ordering of the Filters is:
*
* <ul>
* <li>{@link ChannelProcessingFilter}</li>
* <li>{@link ConcurrentSessionFilter}</li>
* <li>{@link SecurityContextPersistenceFilter}</li>
* <li>{@link LogoutFilter}</li>
* <li>{@link X509AuthenticationFilter}</li>
* <li>{@link AbstractPreAuthenticatedProcessingFilter}</li>
* <li>{@link org.springframework.security.cas.web.CasAuthenticationFilter}</li>
* <li>{@link UsernamePasswordAuthenticationFilter}</li>
* <li>{@link ConcurrentSessionFilter}</li>
* <li>{@link OpenIDAuthenticationFilter}</li>
* <li>{@link DefaultLoginPageViewFilter}</li>
* <li>{@link ConcurrentSessionFilter}</li>
* <li>{@link DigestAuthenticationFilter}</li>
* <li>{@link BasicAuthenticationFilter}</li>
* <li>{@link RequestCacheAwareFilter}</li>
* <li>{@link SecurityContextHolderAwareRequestFilter}</li>
* <li>{@link JaasApiIntegrationFilter}</li>
* <li>{@link RememberMeAuthenticationFilter}</li>
* <li>{@link AnonymousAuthenticationFilter}</li>
* <li>{@link SessionManagementFilter}</li>
* <li>{@link ExceptionTranslationFilter}</li>
* <li>{@link FilterSecurityInterceptor}</li>
* <li>{@link SwitchUserFilter}</li>
* </ul>
*
* @param filter the {@link Filter} to add
* @return the {@link HttpSecurity} for further customizations
*/
H addFilter(Filter filter);
// FIXME shared object or explicit?
AuthenticationManager getAuthenticationManager();
}

View File

@ -0,0 +1,41 @@
/*
* Copyright 2002-2013 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.annotation.web;
import javax.servlet.Filter;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.SecurityBuilder;
import org.springframework.security.config.annotation.SecurityConfigurer;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
/**
* Allows customization to the {@link WebSecurity}. In most instances
* users will use {@link EnableWebSecurity} and a create {@link Configuration}
* that extends {@link WebSecurityConfigurerAdapter} which will automatically be
* applied to the {@link WebSecurity} by the {@link EnableWebSecurity}
* annotation.
*
* @see WebSecurityConfigurerAdapter
*
* @author Rob Winch
* @since 3.2
*/
public interface WebSecurityConfigurer<T extends SecurityBuilder<Filter>> extends SecurityConfigurer<Filter, T> {
}

View File

@ -0,0 +1,191 @@
/*
* Copyright 2002-2013 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.annotation.web.builders;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.List;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.util.UrlUtils;
/**
* Spring Security debugging filter.
* <p>
* Logs information (such as session creation) to help the user understand how requests are being handled
* by Spring Security and provide them with other relevant information (such as when sessions are being created).
*
*
* @author Luke Taylor
* @author Rob Winch
* @since 3.1
*/
class DebugFilter implements Filter {
private static final String ALREADY_FILTERED_ATTR_NAME = DebugFilter.class.getName().concat(".FILTERED");
private final FilterChainProxy fcp;
private final Logger logger = new Logger();
public DebugFilter(FilterChainProxy fcp) {
this.fcp = fcp;
}
public final void doFilter(ServletRequest srvltRequest, ServletResponse srvltResponse, FilterChain filterChain)
throws ServletException, IOException {
if (!(srvltRequest instanceof HttpServletRequest) || !(srvltResponse instanceof HttpServletResponse)) {
throw new ServletException("DebugFilter just supports HTTP requests");
}
HttpServletRequest request = (HttpServletRequest) srvltRequest;
HttpServletResponse response = (HttpServletResponse) srvltResponse;
List<Filter> filters = getFilters(request);
logger.log("Request received for '" + UrlUtils.buildRequestUrl(request) + "':\n\n" +
request + "\n\n" +
"servletPath:" + request.getServletPath() + "\n" +
"pathInfo:" + request.getPathInfo() + "\n\n" +
formatFilters(filters));
if (request.getAttribute(ALREADY_FILTERED_ATTR_NAME) == null) {
invokeWithWrappedRequest(request, response, filterChain);
} else {
fcp.doFilter(request, response, filterChain);
}
}
private void invokeWithWrappedRequest(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain) throws IOException, ServletException {
request.setAttribute(ALREADY_FILTERED_ATTR_NAME, Boolean.TRUE);
request = new DebugRequestWrapper(request);
try {
fcp.doFilter(request, response, filterChain);
}
finally {
request.removeAttribute(ALREADY_FILTERED_ATTR_NAME);
}
}
String formatFilters(List<Filter> filters) {
StringBuilder sb = new StringBuilder();
sb.append("Security filter chain: ");
if (filters == null) {
sb.append("no match");
} else if (filters.isEmpty()) {
sb.append("[] empty (bypassed by security='none') ");
} else {
sb.append("[\n");
for (Filter f : filters) {
sb.append(" ").append(f.getClass().getSimpleName()).append("\n");
}
sb.append("]");
}
return sb.toString();
}
private List<Filter> getFilters(HttpServletRequest request) {
for (SecurityFilterChain chain : fcp.getFilterChains()) {
if (chain.matches(request)) {
return chain.getFilters();
}
}
return null;
}
public void init(FilterConfig filterConfig) throws ServletException {
}
public void destroy() {
}
}
class DebugRequestWrapper extends HttpServletRequestWrapper {
private static final Logger logger = new Logger();
public DebugRequestWrapper(HttpServletRequest request) {
super(request);
}
@Override
public HttpSession getSession() {
boolean sessionExists = super.getSession(false) != null;
HttpSession session = super.getSession();
if (!sessionExists) {
logger.log("New HTTP session created: " + session.getId(), true);
}
return session;
}
@Override
public HttpSession getSession(boolean create) {
if (!create) {
return super.getSession(create);
}
return getSession();
}
}
/**
* Controls output for the Spring Security debug feature.
*
* @author Luke Taylor
* @since 3.1
*/
final class Logger {
final static Log logger = LogFactory.getLog("Spring Security Debugger");
void log(String message) {
log(message, false);
}
void log(String message, boolean dumpStack) {
StringBuilder output = new StringBuilder(256);
output.append("\n\n************************************************************\n\n");
output.append(message).append("\n");
if (dumpStack) {
StringWriter os = new StringWriter();
new Exception().printStackTrace(new PrintWriter(os));
StringBuffer buffer = os.getBuffer();
// Remove the exception in case it scares people.
int start = buffer.indexOf("java.lang.Exception");
buffer.replace(start, start + 19, "");
output.append("\nCall stack: \n").append(os.toString());
}
output.append("\n\n************************************************************\n\n");
logger.info(output.toString());
}
}

View File

@ -0,0 +1,173 @@
/*
* Copyright 2002-2013 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.annotation.web.builders;
import java.io.Serializable;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.Filter;
import org.springframework.security.web.access.ExceptionTranslationFilter;
import org.springframework.security.web.access.channel.ChannelProcessingFilter;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.AnonymousAuthenticationFilter;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutFilter;
import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter;
import org.springframework.security.web.authentication.preauth.x509.X509AuthenticationFilter;
import org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter;
import org.springframework.security.web.authentication.switchuser.SwitchUserFilter;
import org.springframework.security.web.authentication.ui.DefaultLoginPageViewFilter;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.security.web.authentication.www.DigestAuthenticationFilter;
import org.springframework.security.web.context.SecurityContextPersistenceFilter;
import org.springframework.security.web.jaasapi.JaasApiIntegrationFilter;
import org.springframework.security.web.savedrequest.RequestCacheAwareFilter;
import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter;
import org.springframework.security.web.session.ConcurrentSessionFilter;
import org.springframework.security.web.session.SessionManagementFilter;
/**
* An internal use only {@link Comparator} that sorts the Security {@link Filter} instances to ensure they are in the
* correct order.
*
* @author Rob Winch
* @since 3.2
*/
@SuppressWarnings("serial")
final class FilterComparator implements Comparator<Filter>, Serializable {
private static final int STEP = 100;
private Map<String,Integer> filterToOrder = new HashMap<String,Integer>();
FilterComparator() {
int order = 100;
put(ChannelProcessingFilter.class, order);
order += STEP;
put(ConcurrentSessionFilter.class, order);
order += STEP;
put(SecurityContextPersistenceFilter.class, order);
order += STEP;
put(LogoutFilter.class, order);
order += STEP;
put(X509AuthenticationFilter.class, order);
order += STEP;
put(AbstractPreAuthenticatedProcessingFilter.class, order);
order += STEP;
filterToOrder.put("org.springframework.security.cas.web.CasAuthenticationFilter", order);
order += STEP;
put(UsernamePasswordAuthenticationFilter.class, order);
order += STEP;
put(ConcurrentSessionFilter.class, order);
order += STEP;
filterToOrder.put("org.springframework.security.openid.OpenIDAuthenticationFilter", order);
order += STEP;
put(DefaultLoginPageViewFilter.class, order);
order += STEP;
put(ConcurrentSessionFilter.class, order);
order += STEP;
put(DigestAuthenticationFilter.class, order);
order += STEP;
put(BasicAuthenticationFilter.class, order);
order += STEP;
put(RequestCacheAwareFilter.class, order);
order += STEP;
put(SecurityContextHolderAwareRequestFilter.class, order);
order += STEP;
put(JaasApiIntegrationFilter.class, order);
order += STEP;
put(RememberMeAuthenticationFilter.class, order);
order += STEP;
put(AnonymousAuthenticationFilter.class, order);
order += STEP;
put(SessionManagementFilter.class, order);
order += STEP;
put(ExceptionTranslationFilter.class, order);
order += STEP;
put(FilterSecurityInterceptor.class, order);
order += STEP;
put(SwitchUserFilter.class, order);
}
@Override
public int compare(Filter lhs, Filter rhs) {
Integer left = getOrder(lhs.getClass());
Integer right = getOrder(rhs.getClass());
return left - right;
}
/**
* Determines if a particular {@link Filter} is registered to be sorted
*
* @param filter
* @return
*/
public boolean isRegistered(Class<? extends Filter> filter) {
return getOrder(filter) != null;
}
/**
* Registers a {@link Filter} to exist after a particular {@link Filter} that is already registered.
* @param filter the {@link Filter} to register
* @param afterFilter the {@link Filter} that is already registered and that {@code filter} should be placed after.
*/
public void registerAfter(Class<? extends Filter> filter, Class<? extends Filter> afterFilter) {
Integer position = getOrder(afterFilter);
if(position == null) {
throw new IllegalArgumentException("Cannot register after unregistered Filter "+afterFilter);
}
put(filter, position + 1);
}
/**
* Registers a {@link Filter} to exist before a particular {@link Filter} that is already registered.
* @param filter the {@link Filter} to register
* @param beforeFilter the {@link Filter} that is already registered and that {@code filter} should be placed before.
*/
public void registerBefore(Class<? extends Filter> filter, Class<? extends Filter> beforeFilter) {
Integer position = getOrder(beforeFilter);
if(position == null) {
throw new IllegalArgumentException("Cannot register after unregistered Filter "+beforeFilter);
}
put(filter, position - 1);
}
private void put(Class<? extends Filter> filter, int position) {
String className = filter.getName();
filterToOrder.put(className, position);
}
/**
* Gets the order of a particular {@link Filter} class taking into consideration superclasses.
*
* @param clazz the {@link Filter} class to determine the sort order
* @return the sort order or null if not defined
*/
private Integer getOrder(Class<?> clazz) {
while(clazz != null) {
Integer result = filterToOrder.get(clazz.getName());
if(result != null) {
return result;
}
clazz = clazz.getSuperclass();
}
return null;
}
}

View File

@ -0,0 +1,309 @@
/*
* Copyright 2002-2013 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.annotation.web.builders;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.Filter;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.security.access.expression.SecurityExpressionHandler;
import org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder;
import org.springframework.security.config.annotation.SecurityBuilder;
import org.springframework.security.config.annotation.web.AbstractRequestMatcherConfigurer;
import org.springframework.security.config.annotation.web.WebSecurityConfigurer;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.DefaultWebInvocationPrivilegeEvaluator;
import org.springframework.security.web.access.WebInvocationPrivilegeEvaluator;
import org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.firewall.DefaultHttpFirewall;
import org.springframework.security.web.firewall.HttpFirewall;
import org.springframework.security.web.util.RequestMatcher;
import org.springframework.util.Assert;
import org.springframework.web.filter.DelegatingFilterProxy;
/**
* <p>
* The {@link WebSecurity} is created by {@link WebSecurityConfiguration}
* to create the {@link FilterChainProxy} known as the Spring Security Filter
* Chain (springSecurityFilterChain). The springSecurityFilterChain is the
* {@link Filter} that the {@link DelegatingFilterProxy} delegates to.
* </p>
*
* <p>
* Customizations to the {@link WebSecurity} can be made by creating a
* {@link WebSecurityConfigurer} or more likely by overriding
* {@link WebSecurityConfigurerAdapter}.
* </p>
*
* @see EnableWebSecurity
* @see WebSecurityConfiguration
*
* @author Rob Winch
* @since 3.2
*/
public final class WebSecurity extends
AbstractConfiguredSecurityBuilder<Filter, WebSecurity> implements SecurityBuilder<Filter> {
private final Log logger = LogFactory.getLog(getClass());
private final List<RequestMatcher> ignoredRequests = new ArrayList<RequestMatcher>();
private final List<SecurityBuilder<? extends SecurityFilterChain>> securityFilterChainBuilders =
new ArrayList<SecurityBuilder<? extends SecurityFilterChain>>();
private final IgnoredRequestConfigurer ignoredRequestRegistry =
new IgnoredRequestConfigurer();
private FilterSecurityInterceptor filterSecurityInterceptor;
private HttpFirewall httpFirewall;
private boolean debugEnabled;
private WebInvocationPrivilegeEvaluator privilegeEvaluator;
private SecurityExpressionHandler<FilterInvocation> expressionHandler = new DefaultWebSecurityExpressionHandler();
/**
* Creates a new instance
* @see WebSecurityConfiguration
*/
public WebSecurity() {
}
/**
* <p>
* Allows adding {@link RequestMatcher} instances that should that Spring
* Security should ignore. Web Security provided by Spring Security
* (including the {@link SecurityContext}) will not be available on
* {@link HttpServletRequest} that match. Typically the requests that are
* registered should be that of only static resources. For requests that are
* dynamic, consider mapping the request to allow all users instead.
* </p>
*
* Example Usage:
*
* <pre>
* webSecurityBuilder
* .ignoring()
* // ignore all URLs that start with /resources/ or /static/
* .antMatchers(&quot;/resources/**&quot;, &quot;/static/**&quot;);
* </pre>
*
* Alternatively this will accomplish the same result:
*
* <pre>
* webSecurityBuilder
* .ignoring()
* // ignore all URLs that start with /resources/ or /static/
* .antMatchers(&quot;/resources/**&quot;)
* .antMatchers(&quot;/static/**&quot;);
* </pre>
*
* Multiple invocations of ignoring() are also additive, so the following is
* also equivalent to the previous two examples:
*
* Alternatively this will accomplish the same result:
*
* <pre>
* webSecurityBuilder
* .ignoring()
* // ignore all URLs that start with /resources/
* .antMatchers(&quot;/resources/**&quot;);
* webSecurityBuilder
* .ignoring()
* // ignore all URLs that start with /static/
* .antMatchers(&quot;/static/**&quot;);
* // now both URLs that start with /resources/ and /static/ will be ignored
* </pre>
*
* @return the {@link IgnoredRequestConfigurer} to use for registering request
* that should be ignored
*/
public IgnoredRequestConfigurer ignoring() {
return ignoredRequestRegistry;
}
/**
* Allows customizing the {@link HttpFirewall}. The default is
* {@link DefaultHttpFirewall}.
*
* @param httpFirewall the custom {@link HttpFirewall}
* @return the {@link WebSecurity} for further customizations
*/
public WebSecurity httpFirewall(HttpFirewall httpFirewall) {
this.httpFirewall = httpFirewall;
return this;
}
/**
* Controls debugging support for Spring Security.
*
* @param debugEnabled
* if true, enables debug support with Spring Security. Default
* is false.
*
* @return the {@link WebSecurity} for further customization.
* @see EnableWebSecurity#debug()
*/
public WebSecurity debug(boolean debugEnabled) {
this.debugEnabled = debugEnabled;
return this;
}
/**
* <p>
* Adds builders to create {@link SecurityFilterChain} instances.
* </p>
*
* <p>
* Typically this method is invoked automatically within the framework from
* {@link WebSecurityConfigurerAdapter#init(WebSecurity)}
* </p>
*
* @param securityFilterChainBuilder
* the builder to use to create the {@link SecurityFilterChain}
* instances
* @return the {@link WebSecurity} for further customizations
*/
public WebSecurity addSecurityFilterChainBuilder(SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder) {
this.securityFilterChainBuilders.add(securityFilterChainBuilder);
return this;
}
/**
* Set the {@link WebInvocationPrivilegeEvaluator} to be used. If this is
* null, then a {@link DefaultWebInvocationPrivilegeEvaluator} will be
* created when {@link #setSecurityInterceptor(FilterSecurityInterceptor)}
* is non null.
*
* @param privilegeEvaluator
* the {@link WebInvocationPrivilegeEvaluator} to use
* @return the {@link WebSecurity} for further customizations
*/
public WebSecurity privilegeEvaluator(WebInvocationPrivilegeEvaluator privilegeEvaluator) {
this.privilegeEvaluator = privilegeEvaluator;
return this;
}
/**
* Set the {@link SecurityExpressionHandler} to be used. If this is null,
* then a {@link DefaultWebSecurityExpressionHandler} will be used.
*
* @param expressionHandler
* the {@link SecurityExpressionHandler} to use
* @return the {@link WebSecurity} for further customizations
*/
public WebSecurity expressionHandler(SecurityExpressionHandler<FilterInvocation> expressionHandler) {
Assert.notNull(expressionHandler, "expressionHandler cannot be null");
this.expressionHandler = expressionHandler;
return this;
}
/**
* Gets the {@link SecurityExpressionHandler} to be used.
* @return
*/
public SecurityExpressionHandler<FilterInvocation> getExpressionHandler() {
return expressionHandler;
}
/**
* Gets the {@link WebInvocationPrivilegeEvaluator} to be used.
* @return
*/
public WebInvocationPrivilegeEvaluator getPrivilegeEvaluator() {
if(privilegeEvaluator != null) {
return privilegeEvaluator;
}
return filterSecurityInterceptor == null ? null : new DefaultWebInvocationPrivilegeEvaluator(filterSecurityInterceptor);
}
/**
* Sets the {@link FilterSecurityInterceptor}. This is typically invoked by {@link WebSecurityConfigurerAdapter}.
* @param securityInterceptor the {@link FilterSecurityInterceptor} to use
*/
public void setSecurityInterceptor(FilterSecurityInterceptor securityInterceptor) {
this.filterSecurityInterceptor = securityInterceptor;
}
@Override
protected Filter performBuild() throws Exception {
Assert.state(!securityFilterChainBuilders.isEmpty(), "At least one SecurityFilterBuilder needs to be specified. Invoke FilterChainProxyBuilder.securityFilterChains");
int chainSize = ignoredRequests.size() + securityFilterChainBuilders.size();
List<SecurityFilterChain> securityFilterChains = new ArrayList<SecurityFilterChain>(chainSize);
for(RequestMatcher ignoredRequest : ignoredRequests) {
securityFilterChains.add(new DefaultSecurityFilterChain(ignoredRequest));
}
for(SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder : securityFilterChainBuilders) {
securityFilterChains.add(securityFilterChainBuilder.build());
}
FilterChainProxy filterChainProxy = new FilterChainProxy(securityFilterChains);
if(httpFirewall != null) {
filterChainProxy.setFirewall(httpFirewall);
}
filterChainProxy.afterPropertiesSet();
Filter result = filterChainProxy;
if(debugEnabled) {
logger.warn("\n\n" +
"********************************************************************\n" +
"********** Security debugging is enabled. *************\n" +
"********** This may include sensitive information. *************\n" +
"********** Do not use in a production system! *************\n" +
"********************************************************************\n\n");
result = new DebugFilter(filterChainProxy);
}
return result;
}
/**
* Allows registering {@link RequestMatcher} instances that should be
* ignored by Spring Security.
*
* @author Rob Winch
* @since 3.2
*/
public final class IgnoredRequestConfigurer extends AbstractRequestMatcherConfigurer<WebSecurity,IgnoredRequestConfigurer,Filter> {
@Override
protected IgnoredRequestConfigurer chainRequestMatchers(List<RequestMatcher> requestMatchers) {
ignoredRequests.addAll(requestMatchers);
return this;
}
/**
* Returns the {@link WebSecurity} to be returned for chaining.
*/
@Override
public WebSecurity and() {
return WebSecurity.this;
}
private IgnoredRequestConfigurer(){}
}
}

View File

@ -0,0 +1,87 @@
/*
* Copyright 2002-2013 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.annotation.web.configuration;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Import;
import org.springframework.security.config.annotation.configuration.ObjectPostProcessorConfiguration;
import org.springframework.security.config.annotation.web.WebSecurityConfigurer;
/**
* Add this annotation to an {@code @Configuration} class to have the Spring Security
* configuration defined in any {@link WebSecurityConfigurer} or more likely by extending the
* {@link WebSecurityConfigurerAdapter} base class and overriding individual methods:
*
* <pre class="code">
* &#064;Configuration
* &#064;EnableWebSecurity
* public class MyWebSecurityConfiguration extends WebSecurityConfigurerAdapter {
*
* &#064;Override
* public void configure(WebSecurity web) throws Exception {
* web
* .ignoring()
* // Spring Security should completely ignore URLs starting with /resources/
* .antMatchers("/resources/**");
* }
*
* &#064;Override
* protected void configure(HttpSecurity http) throws Exception {
* http
* .authorizeUrls()
* .antMatchers("/public/**").permitAll()
* .anyRequest().hasRole("USER")
* .and()
* // Possibly more configuration ...
* .formLogin() // enable form based log in
* // set permitAll for all URLs associated with Form Login
* .permitAll();
* }
*
* &#064;Override
* protected void registerAuthentication(AuthenticationManagerBuilder auth) {
* registry
* // enable in memory based authentication with a user named "user" and "admin"
* .inMemoryAuthentication()
* .withUser("user").password("password").roles("USER").and()
* .withUser("admin").password("password").roles("USER", "ADMIN");
* }
*
* // Possibly more overridden methods ...
* }
* </pre>
*
* @see WebSecurityConfigurer
* @see WebSecurityConfigurerAdapter
*
* @author Rob Winch
* @since 3.2
*/
@Retention(value=java.lang.annotation.RetentionPolicy.RUNTIME)
@Target(value={java.lang.annotation.ElementType.TYPE})
@Documented
@Import({WebSecurityConfiguration.class,ObjectPostProcessorConfiguration.class})
public @interface EnableWebSecurity {
/**
* Controls debugging support for Spring Security. Default is false.
* @return if true, enables debug support with Spring Security
*/
boolean debug() default false;
}

View File

@ -0,0 +1,187 @@
/*
* Copyright 2002-2013 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.annotation.web.configuration;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import javax.servlet.Filter;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.context.annotation.ImportAware;
import org.springframework.core.OrderComparator;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.Order;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.security.access.expression.SecurityExpressionHandler;
import org.springframework.security.config.annotation.SecurityConfigurer;
import org.springframework.security.config.annotation.web.WebSecurityConfigurer;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.WebInvocationPrivilegeEvaluator;
import org.springframework.util.ClassUtils;
/**
* Uses a {@link WebSecurity} to create the {@link FilterChainProxy} that
* performs the web based security for Spring Security. It then exports the
* necessary beans. Customizations can be made to {@link WebSecurity} by
* extending {@link WebSecurityConfigurerAdapter} and exposing it as a
* {@link Configuration} or implementing {@link WebSecurityConfigurer} and
* exposing it as a {@link Configuration}. This configuration is imported when
* using {@link EnableWebSecurity}.
*
* @see EnableWebSecurity
* @see WebSecurity
*
* @author Rob Winch
* @since 3.2
*/
@Configuration
public class WebSecurityConfiguration implements ImportAware, BeanClassLoaderAware {
private final WebSecurity webSecurity = new WebSecurity();
private List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers;
private ClassLoader beanClassLoader;
@Bean
@DependsOn("springSecurityFilterChain")
public SecurityExpressionHandler<FilterInvocation> webSecurityExpressionHandler() {
return webSecurity.getExpressionHandler();
}
/**
* Creates the Spring Security Filter Chain
* @return
* @throws Exception
*/
@Bean(name="springSecurityFilterChain")
public Filter springSecurityFilterChain() throws Exception {
boolean hasConfigurers = webSecurityConfigurers != null && !webSecurityConfigurers.isEmpty();
if(!hasConfigurers) {
throw new IllegalStateException("At least one non-null instance of "+ WebSecurityConfigurer.class.getSimpleName()+" must be exposed as a @Bean when using @EnableWebSecurity. Hint try extending "+ WebSecurityConfigurerAdapter.class.getSimpleName());
}
return webSecurity.build();
}
/**
* Creates the {@link WebInvocationPrivilegeEvaluator} that is necessary for the JSP tag support.
* @return the {@link WebInvocationPrivilegeEvaluator}
* @throws Exception
*/
@Bean
@DependsOn("springSecurityFilterChain")
public WebInvocationPrivilegeEvaluator privilegeEvaluator() throws Exception {
return webSecurity.getPrivilegeEvaluator();
}
/**
* Sets the {@code <SecurityConfigurer<FilterChainProxy, WebSecurityBuilder>} instances used to create the web configuration.
*
* @param webSecurityConfigurers the {@code <SecurityConfigurer<FilterChainProxy, WebSecurityBuilder>} instances used to create the web configuration
* @throws Exception
*/
@Autowired(required = false)
public void setFilterChainProxySecurityConfigurer(
List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers) throws Exception {
Collections.sort(webSecurityConfigurers, AnnotationAwareOrderComparator.INSTANCE);
Integer previousOrder = null;
for(SecurityConfigurer<Filter, WebSecurity> config : webSecurityConfigurers) {
Integer order = AnnotationAwareOrderComparator.lookupOrder(config);
if(previousOrder != null && previousOrder.equals(order)) {
throw new IllegalStateException("@Order on WebSecurityConfigurers must be unique. Order of " + order + " was already used, so it cannot be used on " + config + " too.");
}
previousOrder = order;
}
for(SecurityConfigurer<Filter, WebSecurity> webSecurityConfigurer : webSecurityConfigurers) {
webSecurity.apply(webSecurityConfigurer);
}
this.webSecurityConfigurers = webSecurityConfigurers;
}
/**
* A custom verision of the Spring provided AnnotationAwareOrderComparator
* that uses {@link AnnotationUtils#findAnnotation(Class, Class)} to look on
* super class instances for the {@link Order} annotation.
*
* @author Rob Winch
* @since 3.2
*/
private static class AnnotationAwareOrderComparator extends OrderComparator {
private static final AnnotationAwareOrderComparator INSTANCE = new AnnotationAwareOrderComparator();
@Override
protected int getOrder(Object obj) {
return lookupOrder(obj);
}
private static int lookupOrder(Object obj) {
if (obj instanceof Ordered) {
return ((Ordered) obj).getOrder();
}
if (obj != null) {
Class<?> clazz = (obj instanceof Class ? (Class<?>) obj : obj.getClass());
Order order = AnnotationUtils.findAnnotation(clazz,Order.class);
if (order != null) {
return order.value();
}
}
return Ordered.LOWEST_PRECEDENCE;
}
}
/* (non-Javadoc)
* @see org.springframework.context.annotation.ImportAware#setImportMetadata(org.springframework.core.type.AnnotationMetadata)
*/
@Override
public void setImportMetadata(AnnotationMetadata importMetadata) {
Map<String, Object> enableWebSecurityAttrMap = importMetadata.getAnnotationAttributes(EnableWebSecurity.class.getName());
AnnotationAttributes enableWebSecurityAttrs = AnnotationAttributes.fromMap(enableWebSecurityAttrMap);
if(enableWebSecurityAttrs == null) {
// search parent classes
Class<?> currentClass = ClassUtils.resolveClassName(importMetadata.getClassName(), beanClassLoader);
for(Class<?> classToInspect = currentClass ;classToInspect != null; classToInspect = classToInspect.getSuperclass()) {
EnableWebSecurity enableWebSecurityAnnotation = AnnotationUtils.findAnnotation(classToInspect, EnableWebSecurity.class);
if(enableWebSecurityAnnotation == null) {
continue;
}
enableWebSecurityAttrMap = AnnotationUtils
.getAnnotationAttributes(enableWebSecurityAnnotation);
enableWebSecurityAttrs = AnnotationAttributes.fromMap(enableWebSecurityAttrMap);
}
}
boolean debugEnabled = enableWebSecurityAttrs.getBoolean("debug");
this.webSecurity.debug(debugEnabled);
}
/* (non-Javadoc)
* @see org.springframework.beans.factory.BeanClassLoaderAware#setBeanClassLoader(java.lang.ClassLoader)
*/
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
this.beanClassLoader = classLoader;
}
}

View File

@ -0,0 +1,326 @@
/*
* Copyright 2002-2013 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.annotation.web.configuration;
import javax.servlet.Filter;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.DefaultAuthenticationEventPublisher;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.SecurityConfigurer;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.WebSecurityConfigurer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configurers.DefaultLoginPageConfigurer;
import org.springframework.security.config.annotation.web.configurers.SecurityContextConfigurer;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
/**
* Provides a convenient base class for creating a {@link WebSecurityConfigurer}
* instance. The implementation allows customization by overriding methods.
*
* @see EnableWebSecurity
*
* @author Rob Winch
*/
public abstract class WebSecurityConfigurerAdapter implements SecurityConfigurer<Filter,WebSecurity> {
private final Log logger = LogFactory.getLog(getClass());
@Autowired
private ApplicationContext context;
@Autowired(required=false)
private ObjectPostProcessor<Object> objectPostProcessor = new ObjectPostProcessor<Object>() {
@Override
public <T> T postProcess(T object) {
throw new IllegalStateException(ObjectPostProcessor.class.getName()+ " is a required bean. Ensure you have used @EnableWebSecurity and @Configuration");
}
};
private final AuthenticationManagerBuilder authenticationBuilder = new AuthenticationManagerBuilder();
private final AuthenticationManagerBuilder parentAuthenticationBuilder = new AuthenticationManagerBuilder() {
@Override
public AuthenticationManagerBuilder eraseCredentials(boolean eraseCredentials) {
authenticationBuilder.eraseCredentials(eraseCredentials);
return super.eraseCredentials(eraseCredentials);
}
};
private boolean disableAuthenticationRegistration;
private boolean authenticationManagerInitialized;
private AuthenticationManager authenticationManager;
private HttpSecurity http;
private boolean disableDefaults;
/**
* Creates an instance with the default configuration enabled.
*/
protected WebSecurityConfigurerAdapter() {
this(false);
}
/**
* Creates an instance which allows specifying if the default configuration
* should be enabled. Disabling the default configuration should be
* considered more advanced usage as it requires more understanding of how
* the framework is implemented.
*
* @param disableDefaults
* true if the default configuration should be enabled, else
* false
*/
protected WebSecurityConfigurerAdapter(boolean disableDefaults) {
this.disableDefaults = disableDefaults;
}
/**
* Used by the default implementation of {@link #authenticationManager()} to attempt to obtain an
* {@link AuthenticationManager}. If overridden, the {@link AuthenticationManagerBuilder} should be used to specify
* the {@link AuthenticationManager}. The resulting {@link AuthenticationManager}
* will be exposed as a Bean as will the last populated {@link UserDetailsService} that is created with the
* {@link AuthenticationManagerBuilder}. The {@link UserDetailsService} will also automatically be populated on
* {@link HttpSecurity#getSharedObject(Class)} for use with other {@link SecurityContextConfigurer}
* (i.e. RememberMeConfigurer )
*
* <p>For example, the following configuration could be used to register
* in memory authentication that exposes an in memory {@link UserDetailsService}:</p>
*
* <pre>
* &#064;Override
* protected void registerAuthentication(AuthenticationManagerBuilder auth) {
* registry
* // enable in memory based authentication with a user named "user" and "admin"
* .inMemoryAuthentication()
* .withUser("user").password("password").roles("USER").and()
* .withUser("admin").password("password").roles("USER", "ADMIN");
* }
* </pre>
*
* @param auth the {@link AuthenticationManagerBuilder} to use
* @throws Exception
*/
protected void registerAuthentication(AuthenticationManagerBuilder auth) throws Exception {
this.disableAuthenticationRegistration = true;
}
/**
* Creates the {@link HttpSecurity} or returns the current instance
*
* @return the {@link HttpSecurity}
* @throws Exception
*/
protected final HttpSecurity getHttp() throws Exception {
if(http != null) {
return http;
}
authenticationBuilder.objectPostProcessor(objectPostProcessor);
parentAuthenticationBuilder.objectPostProcessor(objectPostProcessor);
DefaultAuthenticationEventPublisher eventPublisher = objectPostProcessor.postProcess(new DefaultAuthenticationEventPublisher());
parentAuthenticationBuilder.authenticationEventPublisher(eventPublisher);
AuthenticationManager authenticationManager = authenticationManager();
authenticationBuilder.parentAuthenticationManager(authenticationManager);
http = new HttpSecurity(objectPostProcessor,authenticationBuilder, parentAuthenticationBuilder.getSharedObjects());
http.setSharedObject(UserDetailsService.class, userDetailsService());
if(!disableDefaults) {
http
.exceptionHandling().and()
.sessionManagement().and()
.securityContext().and()
.requestCache().and()
.anonymous().and()
.servletApi().and()
.apply(new DefaultLoginPageConfigurer<HttpSecurity>()).and()
.logout();
}
configure(http);
return http;
}
/**
* Override this method to expose the {@link AuthenticationManager} from
* {@link #registerAuthentication(AuthenticationManagerBuilder)} to be exposed as
* a Bean. For example:
*
* <pre>
* &#064;Bean(name name="myAuthenticationManager")
* &#064;Override
* public AuthenticationManager authenticationManagerBean() throws Exception {
* return super.authenticationManagerBean();
* }
* </pre>
*
* @return the {@link AuthenticationManager}
* @throws Exception
*/
public AuthenticationManager authenticationManagerBean() throws Exception {
return new AuthenticationManagerDelegator(authenticationBuilder);
}
/**
* Gets the {@link AuthenticationManager} to use. The default strategy is if
* {@link #registerAuthentication(AuthenticationManagerBuilder)} method is
* overridden to use the {@link AuthenticationManagerBuilder} that was passed in.
* Otherwise, autowire the {@link AuthenticationManager} by type.
*
* @return
* @throws Exception
*/
protected AuthenticationManager authenticationManager() throws Exception {
if(!authenticationManagerInitialized) {
registerAuthentication(parentAuthenticationBuilder);
if(disableAuthenticationRegistration) {
try {
authenticationManager = context.getBean(AuthenticationManager.class);
} catch(NoSuchBeanDefinitionException e) {
logger.debug("The AuthenticationManager was not found. This is ok for now as it may not be required.",e);
}
} else {
authenticationManagerInitialized = true;
authenticationManager = parentAuthenticationBuilder.build();
}
authenticationManagerInitialized = true;
}
return authenticationManager;
}
/**
* Override this method to expose a {@link UserDetailsService} created from
* {@link #registerAuthentication(AuthenticationManagerBuilder)} as a bean. In
* general only the following override should be done of this method:
*
* <pre>
* &#064;Bean(name = "myUserDetailsService") // any or no name specified is allowed
* &#064;Override
* public UserDetailsService userDetailsServiceBean() throws Exception {
* return super.userDetailsServiceBean();
* }
* </pre>
*
* To change the instance returned, developers should change
* {@link #userDetailsService()} instead
* @return
* @throws Exception
* @see {@link #userDetailsService()}
*/
public UserDetailsService userDetailsServiceBean() throws Exception {
return userDetailsService();
}
/**
* Allows modifying and accessing the {@link UserDetailsService} from
* {@link #userDetailsServiceBean()()} without interacting with the
* {@link ApplicationContext}. Developers should override this method when
* changing the instance of {@link #userDetailsServiceBean()}.
*
* @return
*/
protected UserDetailsService userDetailsService() {
return parentAuthenticationBuilder.getDefaultUserDetailsService();
}
@Override
public void init(WebSecurity web) throws Exception {
HttpSecurity http = getHttp();
FilterSecurityInterceptor securityInterceptor = http.getSharedObject(FilterSecurityInterceptor.class);
web
.addSecurityFilterChainBuilder(http)
.setSecurityInterceptor(securityInterceptor);
}
/**
* Override this method to configure {@link WebSecurity}. For
* example, if you wish to ignore certain requests.
*/
@Override
public void configure(WebSecurity web) throws Exception {
}
/**
* Override this method to configure the {@link HttpSecurity}.
* Typically subclasses should not invoke this method by calling super
* as it may override their configuration. The default configuration is:
*
* <pre>
* http
* .authorizeUrls()
* .anyRequest().authenticated().and()
* .formLogin().and()
* .httpBasic();
* </pre>
*
* @param http
* the {@link HttpSecurity} to modify
* @throws Exception
* if an error occurs
*/
protected void configure(HttpSecurity http) throws Exception {
logger.debug("Using default configure(HttpSecurity). If subclassed this will potentially override subclass configure(HttpSecurity).");
http
.authorizeUrls()
.anyRequest().authenticated()
.and()
.formLogin().and()
.httpBasic();
}
/**
* Delays the use of the {@link AuthenticationManager} build from the
* {@link AuthenticationManagerBuilder} to ensure that it has been fully
* configured.
*
* @author Rob Winch
* @since 3.2
*/
static final class AuthenticationManagerDelegator implements AuthenticationManager {
private AuthenticationManagerBuilder delegateBuilder;
private AuthenticationManager delegate;
private final Object delegateMonitor = new Object();
AuthenticationManagerDelegator(AuthenticationManagerBuilder authentication) {
this.delegateBuilder = authentication;
}
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
if(delegate != null) {
return delegate.authenticate(authentication);
}
synchronized(delegateMonitor) {
if (delegate == null) {
delegate = this.delegateBuilder.getObject();
this.delegateBuilder = null;
}
}
return delegate.authenticate(authentication);
}
}
}

View File

@ -0,0 +1,319 @@
/*
* Copyright 2002-2013 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.annotation.web.configurers;
import javax.servlet.http.HttpServletRequest;
import org.springframework.security.authentication.AuthenticationDetailsSource;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configurers.openid.OpenIDLoginConfigurer;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.PortMapper;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
import org.springframework.security.web.authentication.RememberMeServices;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
/**
* Base class for confuring {@link AbstractAuthenticationFilterConfigurer}. This is intended for internal use only.
*
* @see FormLoginConfigurer
* @see OpenIDLoginConfigurer
*
* @param T refers to "this" for returning the current configurer
* @param F refers to the {@link AbstractAuthenticationProcessingFilter} that is being built
*
* @author Rob Winch
* @since 3.2
*/
public abstract class AbstractAuthenticationFilterConfigurer<B extends HttpSecurityBuilder<B>,T extends AbstractAuthenticationFilterConfigurer<B,T, F>, F extends AbstractAuthenticationProcessingFilter> extends AbstractHttpConfigurer<B> {
private final F authFilter;
private AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource;
private AuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();
private LoginUrlAuthenticationEntryPoint authenticationEntryPoint;
private boolean customLoginPage;
private String loginPage;
private String loginProcessingUrl;
private AuthenticationFailureHandler failureHandler;
private boolean permitAll;
private String failureUrl;
/**
* Creates a new instance
* @param authenticationFilter the {@link AbstractAuthenticationProcessingFilter} to use
* @param defaultLoginProcessingUrl the default URL to use for {@link #loginProcessingUrl(String)}
*/
protected AbstractAuthenticationFilterConfigurer(F authenticationFilter, String defaultLoginProcessingUrl) {
this.authFilter = authenticationFilter;
loginUrl("/login");
failureUrl("/login?error");
loginProcessingUrl(defaultLoginProcessingUrl);
this.customLoginPage = false;
}
/**
* Specifies where users will go after authenticating successfully if they
* have not visited a secured page prior to authenticating. This is a
* shortcut for calling {@link #defaultSuccessUrl(String)}.
*
* @param defaultSuccessUrl
* the default success url
* @return the {@link FormLoginConfigurer} for additional customization
*/
public final T defaultSuccessUrl(String defaultSuccessUrl) {
return defaultSuccessUrl(defaultSuccessUrl, false);
}
/**
* Specifies where users will go after authenticating successfully if they
* have not visited a secured page prior to authenticating or
* {@code alwaysUse} is true. This is a shortcut for calling
* {@link #successHandler(AuthenticationSuccessHandler)}.
*
* @param defaultSuccessUrl
* the default success url
* @param alwaysUse
* true if the {@code defaultSuccesUrl} should be used after
* authentication despite if a protected page had been previously
* visited
* @return the {@link FormLoginConfigurer} for additional customization
*/
public final T defaultSuccessUrl(String defaultSuccessUrl, boolean alwaysUse) {
SavedRequestAwareAuthenticationSuccessHandler handler = new SavedRequestAwareAuthenticationSuccessHandler();
handler.setDefaultTargetUrl(defaultSuccessUrl);
handler.setAlwaysUseDefaultTargetUrl(alwaysUse);
return successHandler(handler);
}
/**
* Specifies the URL used to log in. If the request matches the URL and is an HTTP POST, the
* {@link UsernamePasswordAuthenticationFilter} will attempt to authenticate
* the request. Otherwise, if the request matches the URL the user will be sent to the login form.
*
* @param loginUrl the URL used to perform authentication
* @return the {@link FormLoginConfigurer} for additional customization
*/
public final T loginUrl(String loginUrl) {
loginProcessingUrl(loginUrl);
return loginPage(loginUrl);
}
/**
* Specifies the URL to validate the credentials.
*
* @param loginProcessingUrl
* the URL to validate username and password
* @return the {@link FormLoginConfigurer} for additional customization
*/
public T loginProcessingUrl(String loginProcessingUrl) {
this.loginProcessingUrl = loginProcessingUrl;
authFilter.setFilterProcessesUrl(loginProcessingUrl);
return getSelf();
}
/**
* Specifies a custom {@link AuthenticationDetailsSource}. The default is {@link WebAuthenticationDetailsSource}.
*
* @param authenticationDetailsSource the custom {@link AuthenticationDetailsSource}
* @return the {@link FormLoginConfigurer} for additional customization
*/
public final T authenticationDetailsSource(AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource) {
this.authenticationDetailsSource = authenticationDetailsSource;
return getSelf();
}
/**
* Specifies the {@link AuthenticationSuccessHandler} to be used. The
* default is {@link SavedRequestAwareAuthenticationSuccessHandler} with no
* additional properites set.
*
* @param successHandler
* the {@link AuthenticationSuccessHandler}.
* @return the {@link FormLoginConfigurer} for additional customization
*/
public final T successHandler(AuthenticationSuccessHandler successHandler) {
this.successHandler = successHandler;
return getSelf();
}
/**
* Equivalent of invoking permitAll(true)
* @return
*/
public final T permitAll() {
return permitAll(true);
}
/**
* Ensures the urls for {@link #failureUrl(String)} and
* {@link #loginUrl(String)} are granted access to any user.
*
* @param permitAll true to grant access to the URLs false to skip this step
* @return the {@link FormLoginConfigurer} for additional customization
*/
public final T permitAll(boolean permitAll) {
this.permitAll = permitAll;
return getSelf();
}
/**
* The URL to send users if authentication fails. This is a shortcut for
* invoking {@link #failureHandler(AuthenticationFailureHandler)}. The
* default is "/login?error".
*
* @param authenticationFailureUrl
* the URL to send users if authentication fails (i.e.
* "/login?error").
* @return the {@link FormLoginConfigurer} for additional customization
*/
public final T failureUrl(String authenticationFailureUrl) {
T result = failureHandler(new SimpleUrlAuthenticationFailureHandler(authenticationFailureUrl));
this.failureUrl = authenticationFailureUrl;
return result;
}
/**
* Specifies the {@link AuthenticationFailureHandler} to use when
* authentication fails. The default is redirecting to "/login?error" using
* {@link SimpleUrlAuthenticationFailureHandler}
*
* @param authenticationFailureHandler
* the {@link AuthenticationFailureHandler} to use when
* authentication fails.
* @return the {@link FormLoginConfigurer} for additional customization
*/
public final T failureHandler(AuthenticationFailureHandler authenticationFailureHandler) {
this.failureUrl = null;
this.failureHandler = authenticationFailureHandler;
return getSelf();
}
@Override
public void init(B http) throws Exception {
if(permitAll) {
PermitAllSupport.permitAll(http, loginPage, loginProcessingUrl, failureUrl);
}
http.setSharedObject(AuthenticationEntryPoint.class, postProcess(authenticationEntryPoint));
}
@Override
public void configure(B http) throws Exception {
PortMapper portMapper = http.getSharedObject(PortMapper.class);
if(portMapper != null) {
authenticationEntryPoint.setPortMapper(portMapper);
}
authFilter.setAuthenticationManager(http.getAuthenticationManager());
authFilter.setAuthenticationSuccessHandler(successHandler);
authFilter.setAuthenticationFailureHandler(failureHandler);
if(authenticationDetailsSource != null) {
authFilter.setAuthenticationDetailsSource(authenticationDetailsSource);
}
SessionAuthenticationStrategy sessionAuthenticationStrategy = http.getSharedObject(SessionAuthenticationStrategy.class);
if(sessionAuthenticationStrategy != null) {
authFilter.setSessionAuthenticationStrategy(sessionAuthenticationStrategy);
}
RememberMeServices rememberMeServices = http.getSharedObject(RememberMeServices.class);
if(rememberMeServices != null) {
authFilter.setRememberMeServices(rememberMeServices);
}
F filter = postProcess(authFilter);
http.addFilter(filter);
}
/**
* <p>
* Specifies the URL to send users to if login is required. If used with
* {@link WebSecurityConfigurerAdapter} a default login page will be
* generated when this attribute is not specified.
* </p>
*
* <p>
* If a URL is specified or this is not being used in conjuction with
* {@link WebSecurityConfigurerAdapter}, users are required to process the
* specified URL to generate a login page.
* </p>
*/
protected T loginPage(String loginPage) {
this.loginPage = loginPage;
this.authenticationEntryPoint = new LoginUrlAuthenticationEntryPoint(loginPage);
this.customLoginPage = true;
return getSelf();
}
/**
*
* @return true if a custom login page has been specified, else false
*/
public final boolean isCustomLoginPage() {
return customLoginPage;
}
/**
* Gets the Authentication Filter
* @return
*/
protected final F getAuthenticationFilter() {
return authFilter;
}
/**
* Gets the login page
* @return the login page
*/
protected final String getLoginPage() {
return loginPage;
}
/**
* Gets the URL to submit an authentication request to (i.e. where
* username/password must be submitted)
*
* @return the URL to submit an authentication request to
*/
protected final String getLoginProcessingUrl() {
return loginProcessingUrl;
}
/**
* Gets the URL to send users to if authentication fails
* @return
*/
protected final String getFailureUrl() {
return failureUrl;
}
@SuppressWarnings("unchecked")
private T getSelf() {
return (T) this;
}
}

View File

@ -0,0 +1,33 @@
/*
* Copyright 2002-2013 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.annotation.web.configurers;
import org.springframework.security.config.annotation.SecurityConfigurer;
import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.DefaultSecurityFilterChain;
/**
* Adds a convenient base class for {@link SecurityConfigurer} instances that
* operate on {@link HttpSecurity}.
*
* @author Rob Winch
*
*/
abstract class AbstractHttpConfigurer<B extends HttpSecurityBuilder<B>> extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, B> {
}

View File

@ -0,0 +1,181 @@
/*
* Copyright 2002-2013 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.annotation.web.configurers;
import java.util.List;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDecisionVoter;
import org.springframework.security.access.vote.AffirmativeBased;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.SecurityConfigurer;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
/**
* A base class for configuring the {@link FilterSecurityInterceptor}.
*
* <h2>Security Filters</h2>
*
* The following Filters are populated
*
* <ul>
* <li>{@link FilterSecurityInterceptor}</li>
* </ul>
*
* <h2>Shared Objects Created</h2>
*
* The following shared objects are populated to allow other {@link SecurityConfigurer}'s to customize:
* <ul>
* <li>{@link FilterSecurityInterceptor}</li>
* </ul>
*
* <h2>Shared Objects Used</h2>
*
* The following shared objects are used:
*
* <ul>
* <li>{@link org.springframework.security.config.annotation.web.builders.HttpSecurity#getAuthenticationManager()}</li>
* </ul>
*
* @param <H> the type of {@link HttpSecurityBuilder} that is being configured
* @param <C> the type of object that is changed
* @param <R> the type of object that is changed for the {@link AbstractRequestMatcherMappingConfigurer}
*
* @author Rob Winch
* @since 3.2
* @see ExpressionUrlAuthorizationConfigurer
* @see UrlAuthorizationConfigurer
*/
abstract class AbstractInterceptUrlConfigurer<H extends HttpSecurityBuilder<H>,C,R> extends
AbstractRequestMatcherMappingConfigurer<H,R,DefaultSecurityFilterChain> implements
SecurityConfigurer<DefaultSecurityFilterChain,H> {
private Boolean filterSecurityInterceptorOncePerRequest;
private AccessDecisionManager accessDecisionManager;
/**
* Allows setting the {@link AccessDecisionManager}. If none is provided, a default {@l AccessDecisionManager} is
* created.
*
* @param accessDecisionManager the {@link AccessDecisionManager} to use
* @return the {@link AbstractInterceptUrlConfigurer} for further customization
*/
public C accessDecisionManager(
AccessDecisionManager accessDecisionManager) {
this.accessDecisionManager = accessDecisionManager;
return getSelf();
}
/**
* Allows setting if the {@link FilterSecurityInterceptor} should be only applied once per request (i.e. if the
* filter intercepts on a forward, should it be applied again).
*
* @param filterSecurityInterceptorOncePerRequest if the {@link FilterSecurityInterceptor} should be only applied
* once per request
* @return the {@link AbstractInterceptUrlConfigurer} for further customization
*/
public C filterSecurityInterceptorOncePerRequest(
boolean filterSecurityInterceptorOncePerRequest) {
this.filterSecurityInterceptorOncePerRequest = filterSecurityInterceptorOncePerRequest;
return getSelf();
}
@Override
public void configure(H http) throws Exception {
FilterInvocationSecurityMetadataSource metadataSource = createMetadataSource();
if(metadataSource == null) {
return;
}
FilterSecurityInterceptor securityInterceptor = createFilterSecurityInterceptor(metadataSource, http.getAuthenticationManager());
if(filterSecurityInterceptorOncePerRequest != null) {
securityInterceptor.setObserveOncePerRequest(filterSecurityInterceptorOncePerRequest);
}
http.addFilter(securityInterceptor);
http.setSharedObject(FilterSecurityInterceptor.class, securityInterceptor);
}
/**
* Subclasses should implement this method to provide a {@link FilterInvocationSecurityMetadataSource} for the
* {@link FilterSecurityInterceptor}.
*
* @return the {@link FilterInvocationSecurityMetadataSource} to set on the {@link FilterSecurityInterceptor}.
* Cannot be null.
*/
abstract FilterInvocationSecurityMetadataSource createMetadataSource();
/**
* Subclasses should implement this method to provide the {@link AccessDecisionVoter} instances used to create the
* default {@link AccessDecisionManager}
*
* @return the {@link AccessDecisionVoter} instances used to create the
* default {@link AccessDecisionManager}
*/
@SuppressWarnings("rawtypes")
abstract List<AccessDecisionVoter> getDecisionVoters();
/**
* Creates the default {@code AccessDecisionManager}
* @return the default {@code AccessDecisionManager}
*/
private AccessDecisionManager createDefaultAccessDecisionManager() {
return new AffirmativeBased(getDecisionVoters());
}
/**
* If currently null, creates a default {@link AccessDecisionManager} using
* {@link #createDefaultAccessDecisionManager()}. Otherwise returns the {@link AccessDecisionManager}.
*
* @return the {@link AccessDecisionManager} to use
*/
private AccessDecisionManager getAccessDecisionManager() {
if (accessDecisionManager == null) {
accessDecisionManager = createDefaultAccessDecisionManager();
}
return accessDecisionManager;
}
/**
* Creates the {@link FilterSecurityInterceptor}
*
* @param metadataSource the {@link FilterInvocationSecurityMetadataSource} to use
* @param authenticationManager the {@link AuthenticationManager} to use
* @return the {@link FilterSecurityInterceptor}
* @throws Exception
*/
private FilterSecurityInterceptor createFilterSecurityInterceptor(FilterInvocationSecurityMetadataSource metadataSource,
AuthenticationManager authenticationManager) throws Exception {
FilterSecurityInterceptor securityInterceptor = new FilterSecurityInterceptor();
securityInterceptor.setSecurityMetadataSource(metadataSource);
securityInterceptor.setAccessDecisionManager(getAccessDecisionManager());
securityInterceptor.setAuthenticationManager(authenticationManager);
securityInterceptor.afterPropertiesSet();
return securityInterceptor;
}
/**
* Returns a reference to the current object with a single suppression of
* the type
*
* @return a reference to the current object
*/
@SuppressWarnings("unchecked")
private C getSelf() {
return (C) this;
}
}

View File

@ -0,0 +1,144 @@
/*
* Copyright 2002-2013 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.annotation.web.configurers;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.config.annotation.SecurityBuilder;
import org.springframework.security.config.annotation.web.AbstractRequestMatcherConfigurer;
import org.springframework.security.web.util.RequestMatcher;
/**
* A base class for registering {@link RequestMatcher}'s. For example, it might allow for specifying which
* {@link RequestMatcher} require a certain level of authorization.
*
* @author Rob Winch
* @since 3.2
*
* @param <B> The Builder that is building Object O and is configured by this {@link AbstractRequestMatcherMappingConfigurer}
* @param <C> The object that is returned or Chained after creating the RequestMatcher
* @param <O> The Object being built by Builder B
*
* @see ChannelSecurityConfigurer
* @see UrlAuthorizationConfigurer
* @see ExpressionUrlAuthorizationConfigurer
*/
public abstract class AbstractRequestMatcherMappingConfigurer<B extends SecurityBuilder<O>,C,O> extends AbstractRequestMatcherConfigurer<B,C,O> {
private List<UrlMapping> urlMappings = new ArrayList<UrlMapping>();
private List<RequestMatcher> unmappedMatchers;
/**
* Gets the {@link UrlMapping} added by subclasses in {@link #chainRequestMatchers(java.util.List)}. May be empty.
*
* @return the {@link UrlMapping} added by subclasses in {@link #chainRequestMatchers(java.util.List)}
*/
final List<UrlMapping> getUrlMappings() {
return urlMappings;
}
/**
* Adds a {@link UrlMapping} added by subclasses in
* {@link #chainRequestMatchers(java.util.List)} and resets the unmapped
* {@link RequestMatcher}'s.
*
* @param urlMapping
* {@link UrlMapping} the mapping to add
*/
final void addMapping(UrlMapping urlMapping) {
this.unmappedMatchers = null;
this.urlMappings.add(urlMapping);
}
/**
* Marks the {@link RequestMatcher}'s as unmapped and then calls {@link #chainRequestMatchersInternal(List)}.
*
* @param requestMatchers the {@link RequestMatcher} instances that were created
* @return the chained Object for the subclass which allows association of something else to the
* {@link RequestMatcher}
*/
protected final C chainRequestMatchers(List<RequestMatcher> requestMatchers) {
this.unmappedMatchers = requestMatchers;
return chainRequestMatchersInternal(requestMatchers);
}
/**
* Subclasses should implement this method for returning the object that is chained to the creation of the
* {@link RequestMatcher} instances.
*
* @param requestMatchers the {@link RequestMatcher} instances that were created
* @return the chained Object for the subclass which allows association of something else to the
* {@link RequestMatcher}
*/
protected abstract C chainRequestMatchersInternal(List<RequestMatcher> requestMatchers);
/**
* Adds a {@link UrlMapping} added by subclasses in {@link #chainRequestMatchers(java.util.List)} at a particular
* index.
*
* @param index the index to add a {@link UrlMapping}
* @param urlMapping {@link UrlMapping} the mapping to add
*/
final void addMapping(int index, UrlMapping urlMapping) {
this.urlMappings.add(index, urlMapping);
}
/**
* Creates the mapping of {@link RequestMatcher} to {@link Collection} of {@link ConfigAttribute} instances
*
* @return the mapping of {@link RequestMatcher} to {@link Collection} of {@link ConfigAttribute} instances. Cannot
* be null.
*/
final LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>> createRequestMap() {
if(unmappedMatchers != null) {
throw new IllegalStateException("An incomplete mapping was found for " + unmappedMatchers +". Try completing it with something like requestUrls().<something>.hasRole('USER')");
}
LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>> requestMap = new LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>>();
for (UrlMapping mapping : getUrlMappings()) {
RequestMatcher matcher = mapping.getRequestMatcher();
Collection<ConfigAttribute> configAttrs = mapping.getConfigAttrs();
requestMap.put(matcher,configAttrs);
}
return requestMap;
}
/**
* A mapping of {@link RequestMatcher} to {@link Collection} of {@link ConfigAttribute} instances
*/
static final class UrlMapping {
private RequestMatcher requestMatcher;
private Collection<ConfigAttribute> configAttrs;
UrlMapping(RequestMatcher requestMatcher,
Collection<ConfigAttribute> configAttrs) {
this.requestMatcher = requestMatcher;
this.configAttrs = configAttrs;
}
public RequestMatcher getRequestMatcher() {
return requestMatcher;
}
public Collection<ConfigAttribute> getConfigAttrs() {
return configAttrs;
}
}
}

View File

@ -0,0 +1,165 @@
/*
* Copyright 2002-2013 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.annotation.web.configurers;
import java.util.List;
import java.util.UUID;
import org.springframework.security.authentication.AnonymousAuthenticationProvider;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.config.annotation.SecurityConfigurer;
import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.authentication.AnonymousAuthenticationFilter;
/**
* Configures Anonymous authentication (i.e. populate an {@link Authentication} that represents an anonymous user
* instead of having a null value) for an {@link HttpSecurity}. Specifically this will configure an
* {@link AnonymousAuthenticationFilter} and an {@link AnonymousAuthenticationProvider}. All properties have reasonable
* defaults, so no additional configuration is required other than applying this {@link SecurityConfigurer}.
*
* @author Rob Winch
* @since 3.2
*/
public final class AnonymousConfigurer<H extends HttpSecurityBuilder<H>> extends SecurityConfigurerAdapter<DefaultSecurityFilterChain,H> {
private String key;
private AuthenticationProvider authenticationProvider;
private AnonymousAuthenticationFilter authenticationFilter;
private Object principal = "anonymousUser";
private List<GrantedAuthority> authorities = AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS");
/**
* Creates a new instance
* @see HttpSecurity#anonymous()
*/
public AnonymousConfigurer() {
}
/**
* Disables anonymous authentication.
*
* @return the {@link HttpSecurity} since no further customization of anonymous authentication would be
* meaningful.
*/
@SuppressWarnings("unchecked")
public H disable() {
getBuilder().removeConfigurer(getClass());
return getBuilder();
}
/**
* Sets the key to identify tokens created for anonymous authentication. Default is a secure randomly generated
* key.
*
* @param key the key to identify tokens created for anonymous authentication. Default is a secure randomly generated
* key.
* @return the {@link AnonymousConfigurer} for further customization of anonymous authentication
*/
public AnonymousConfigurer<H> key(String key) {
this.key = key;
return this;
}
/**
* Sets the principal for {@link Authentication} objects of anonymous users
*
* @param principal used for the {@link Authentication} object of anonymous users
* @return the {@link AnonymousConfigurer} for further customization of anonymous authentication
*/
public AnonymousConfigurer<H> principal(Object principal) {
this.principal = principal;
return this;
}
/**
* Sets the {@link org.springframework.security.core.Authentication#getAuthorities()} for anonymous users
*
* @param authorities Sets the {@link org.springframework.security.core.Authentication#getAuthorities()} for anonymous users
* @return the {@link AnonymousConfigurer} for further customization of anonymous authentication
*/
public AnonymousConfigurer<H> authorities(List<GrantedAuthority> authorities) {
this.authorities = authorities;
return this;
}
/**
* Sets the {@link org.springframework.security.core.Authentication#getAuthorities()} for anonymous users
*
* @param authorities Sets the {@link org.springframework.security.core.Authentication#getAuthorities()} for
* anonymous users (i.e. "ROLE_ANONYMOUS")
* @return the {@link AnonymousConfigurer} for further customization of anonymous authentication
*/
public AnonymousConfigurer<H> authorities(String... authorities) {
return authorities(AuthorityUtils.createAuthorityList(authorities));
}
/**
* Sets the {@link AuthenticationProvider} used to validate an anonymous user. If this is set, no attributes
* on the {@link AnonymousConfigurer} will be set on the {@link AuthenticationProvider}.
*
* @param authenticationProvider the {@link AuthenticationProvider} used to validate an anonymous user. Default is
* {@link AnonymousAuthenticationProvider}
*
* @return the {@link AnonymousConfigurer} for further customization of anonymous authentication
*/
public AnonymousConfigurer<H> authenticationProvider(AuthenticationProvider authenticationProvider) {
this.authenticationProvider = authenticationProvider;
return this;
}
/**
* Sets the {@link AnonymousAuthenticationFilter} used to populate an anonymous user. If this is set, no attributes
* on the {@link AnonymousConfigurer} will be set on the {@link AnonymousAuthenticationFilter}.
*
* @param authenticationFilter the {@link AnonymousAuthenticationFilter} used to populate an anonymous user.
*
* @return the {@link AnonymousConfigurer} for further customization of anonymous authentication
*/
public AnonymousConfigurer<H> authenticationFilter(AnonymousAuthenticationFilter authenticationFilter) {
this.authenticationFilter = authenticationFilter;
return this;
}
@Override
public void init(H http) throws Exception {
if(authenticationProvider == null) {
authenticationProvider = new AnonymousAuthenticationProvider(getKey());
}
if(authenticationFilter == null) {
authenticationFilter = new AnonymousAuthenticationFilter(getKey(), principal, authorities);
}
authenticationProvider = postProcess(authenticationProvider);
http.authenticationProvider(authenticationProvider);
}
@Override
public void configure(H http) throws Exception {
authenticationFilter.afterPropertiesSet();
http.addFilter(authenticationFilter);
}
private String getKey() {
if(key == null) {
key = UUID.randomUUID().toString();
}
return key;
}
}

View File

@ -0,0 +1,168 @@
/*
* Copyright 2002-2013 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.annotation.web.configurers;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.PortMapper;
import org.springframework.security.web.access.channel.ChannelDecisionManagerImpl;
import org.springframework.security.web.access.channel.ChannelProcessingFilter;
import org.springframework.security.web.access.channel.ChannelProcessor;
import org.springframework.security.web.access.channel.InsecureChannelProcessor;
import org.springframework.security.web.access.channel.RetryWithHttpEntryPoint;
import org.springframework.security.web.access.channel.RetryWithHttpsEntryPoint;
import org.springframework.security.web.access.channel.SecureChannelProcessor;
import org.springframework.security.web.access.intercept.DefaultFilterInvocationSecurityMetadataSource;
import org.springframework.security.web.util.RequestMatcher;
/**
* Adds channel security (i.e. requires HTTPS or HTTP) to an application. In order for
* {@link ChannelSecurityConfigurer} to be useful, at least one {@link RequestMatcher} should be mapped to HTTP
* or HTTPS.
*
* <p>
* By default an {@link InsecureChannelProcessor} and a {@link SecureChannelProcessor} will be registered.
* </p>
*
* <h2>Security Filters</h2>
*
* The following Filters are populated
*
* <ul>
* <li>{@link ChannelProcessingFilter}</li>
* </ul>
*
* <h2>Shared Objects Created</h2>
*
* No shared objects are created.
*
* <h2>Shared Objects Used</h2>
*
* The following shared objects are used:
*
* <ul>
* <li>{@link PortMapper} is used to create the default {@link ChannelProcessor} instances</li>
* </ul>
*
* @param <H> the type of {@link HttpSecurityBuilder} that is being configured
*
* @author Rob Winch
* @since 3.2
*/
public final class ChannelSecurityConfigurer<H extends HttpSecurityBuilder<H>> extends
AbstractRequestMatcherMappingConfigurer<H,ChannelSecurityConfigurer<H>.RequiresChannelUrl,DefaultSecurityFilterChain> {
private ChannelProcessingFilter channelFilter = new ChannelProcessingFilter();
private LinkedHashMap<RequestMatcher,Collection<ConfigAttribute>> requestMap = new LinkedHashMap<RequestMatcher,Collection<ConfigAttribute>>();
private List<ChannelProcessor> channelProcessors;
/**
* Creates a new instance
* @see HttpSecurity#requiresChannel()
*/
public ChannelSecurityConfigurer() {
}
@Override
public void configure(H http) throws Exception {
ChannelDecisionManagerImpl channelDecisionManager = new ChannelDecisionManagerImpl();
channelDecisionManager.setChannelProcessors(getChannelProcessors(http));
channelDecisionManager = postProcess(channelDecisionManager);
channelFilter.setChannelDecisionManager(channelDecisionManager);
DefaultFilterInvocationSecurityMetadataSource filterInvocationSecurityMetadataSource =
new DefaultFilterInvocationSecurityMetadataSource(requestMap);
channelFilter.setSecurityMetadataSource(filterInvocationSecurityMetadataSource);
channelFilter = postProcess(channelFilter);
http.addFilter(channelFilter);
}
/**
* Sets the {@link ChannelProcessor} instances to use in {@link ChannelDecisionManagerImpl}
* @param channelProcessors
* @return
*/
public ChannelSecurityConfigurer<H> channelProcessors(List<ChannelProcessor> channelProcessors) {
this.channelProcessors = channelProcessors;
return this;
}
private List<ChannelProcessor> getChannelProcessors(H http) {
if(channelProcessors != null) {
return channelProcessors;
}
InsecureChannelProcessor insecureChannelProcessor = new InsecureChannelProcessor();
SecureChannelProcessor secureChannelProcessor = new SecureChannelProcessor();
PortMapper portMapper = http.getSharedObject(PortMapper.class);
if(portMapper != null) {
RetryWithHttpEntryPoint httpEntryPoint = new RetryWithHttpEntryPoint();
httpEntryPoint.setPortMapper(portMapper);
insecureChannelProcessor.setEntryPoint(httpEntryPoint);
RetryWithHttpsEntryPoint httpsEntryPoint = new RetryWithHttpsEntryPoint();
httpsEntryPoint.setPortMapper(portMapper);
secureChannelProcessor.setEntryPoint(httpsEntryPoint);
}
insecureChannelProcessor = postProcess(insecureChannelProcessor);
secureChannelProcessor = postProcess(secureChannelProcessor);
return Arrays.<ChannelProcessor>asList(insecureChannelProcessor, secureChannelProcessor);
}
private ChannelSecurityConfigurer<H> addAttribute(String attribute, List<RequestMatcher> matchers) {
for(RequestMatcher matcher : matchers) {
Collection<ConfigAttribute> attrs = Arrays.<ConfigAttribute>asList(new SecurityConfig(attribute));
requestMap.put(matcher, attrs);
}
return this;
}
@Override
protected RequiresChannelUrl chainRequestMatchersInternal(List<RequestMatcher> requestMatchers) {
return new RequiresChannelUrl(requestMatchers);
}
public final class RequiresChannelUrl {
private List<RequestMatcher> requestMatchers;
private RequiresChannelUrl(List<RequestMatcher> requestMatchers) {
this.requestMatchers = requestMatchers;
}
public ChannelSecurityConfigurer<H> requiresSecure() {
return requires("REQUIRES_SECURE_CHANNEL");
}
public ChannelSecurityConfigurer<H> requiresInsecure() {
return requires("REQUIRES_INSECURE_CHANNEL");
}
public ChannelSecurityConfigurer<H> requires(String attribute) {
return addAttribute(attribute, requestMatchers);
}
}
}

View File

@ -0,0 +1,82 @@
/*
* Copyright 2002-2013 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.annotation.web.configurers;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.ui.DefaultLoginPageViewFilter;
/**
* Adds a Filter that will generate a login page if one is not specified otherwise when using {@link WebSecurityConfigurerAdapter}.
*
* <p>
* By default an {@link org.springframework.security.web.access.channel.InsecureChannelProcessor} and a {@link org.springframework.security.web.access.channel.SecureChannelProcessor} will be registered.
* </p>
*
* <h2>Security Filters</h2>
*
* The following Filters are conditionally populated
*
* <ul>
* <li>{@link DefaultLoginPageViewFilter} if the {@link FormLoginConfigurer} did not have a login page specified</li>
* </ul>
*
* <h2>Shared Objects Created</h2>
*
* No shared objects are created.
*isLogoutRequest
* <h2>Shared Objects Used</h2>
*
* The following shared objects are used:
*
* <ul>
* <li>{@link org.springframework.security.web.PortMapper} is used to create the default {@link org.springframework.security.web.access.channel.ChannelProcessor} instances</li>
* <li>{@link FormLoginConfigurer} is used to determine if the {@link DefaultLoginPageConfigurer} should be added and how to configure it.</li>
* </ul>
*
* @see WebSecurityConfigurerAdapter
*
* @author Rob Winch
* @since 3.2
*/
public final class DefaultLoginPageConfigurer<H extends HttpSecurityBuilder<H>> extends
AbstractHttpConfigurer<H> {
private DefaultLoginPageViewFilter loginPageGeneratingFilter = new DefaultLoginPageViewFilter();
@Override
public void init(H http) throws Exception {
http.setSharedObject(DefaultLoginPageViewFilter.class, loginPageGeneratingFilter);
}
@Override
@SuppressWarnings("unchecked")
public void configure(H http) throws Exception {
AuthenticationEntryPoint authenticationEntryPoint = null;
ExceptionHandlingConfigurer<?> exceptionConf = http.getConfigurer(ExceptionHandlingConfigurer.class);
if(exceptionConf != null) {
authenticationEntryPoint = exceptionConf.getAuthenticationEntryPoint();
}
if(loginPageGeneratingFilter.isEnabled() && authenticationEntryPoint == null) {
loginPageGeneratingFilter = postProcess(loginPageGeneratingFilter);
http.addFilter(loginPageGeneratingFilter);
}
}
}

View File

@ -0,0 +1,164 @@
/*
* Copyright 2002-2013 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.annotation.web.configurers;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.access.AccessDeniedHandlerImpl;
import org.springframework.security.web.access.ExceptionTranslationFilter;
import org.springframework.security.web.authentication.Http403ForbiddenEntryPoint;
import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
import org.springframework.security.web.savedrequest.RequestCache;
/**
* Adds exception handling for Spring Security related exceptions to an application. All properties have reasonable
* defaults, so no additional configuration is required other than applying this
* {@link org.springframework.security.config.annotation.SecurityConfigurer}.
*
* <h2>Security Filters</h2>
*
* The following Filters are populated
*
* <ul>
* <li>{@link ExceptionTranslationFilter}</li>
* </ul>
*
* <h2>Shared Objects Created</h2>
*
* No shared objects are created.
*
* <h2>Shared Objects Used</h2>
*
* The following shared objects are used:
*
* <ul>
* <li>{@link HttpSecurity#authenticationEntryPoint()} is used to process requests that require
* authentication</li>
* <li>If no explicit {@link RequestCache}, is provided a {@link RequestCache} shared object is used to replay
* the request after authentication is successful</li>
* <li>{@link AuthenticationEntryPoint} - see {@link #authenticationEntryPoint(AuthenticationEntryPoint)} </li>
* </ul>
*
* @author Rob Winch
* @since 3.2
*/
public final class ExceptionHandlingConfigurer<H extends HttpSecurityBuilder<H>> extends AbstractHttpConfigurer<H> {
private AuthenticationEntryPoint authenticationEntryPoint;
private AccessDeniedHandler accessDeniedHandler;
/**
* Creates a new instance
* @see HttpSecurity#exceptionHandling()
*/
public ExceptionHandlingConfigurer() {
}
/**
* Shortcut to specify the {@link AccessDeniedHandler} to be used is a specific error page
*
* @param accessDeniedUrl the URL to the access denied page (i.e. /errors/401)
* @return the {@link ExceptionHandlingConfigurer} for further customization
* @see AccessDeniedHandlerImpl
* @see {@link #accessDeniedHandler(org.springframework.security.web.access.AccessDeniedHandler)}
*/
public ExceptionHandlingConfigurer<H> accessDeniedPage(String accessDeniedUrl) {
AccessDeniedHandlerImpl accessDeniedHandler = new AccessDeniedHandlerImpl();
accessDeniedHandler.setErrorPage(accessDeniedUrl);
return accessDeniedHandler(accessDeniedHandler);
}
/**
* Specifies the {@link AccessDeniedHandler} to be used
*
* @param accessDeniedHandler the {@link AccessDeniedHandler} to be used
* @return the {@link ExceptionHandlingConfigurer} for further customization
*/
public ExceptionHandlingConfigurer<H> accessDeniedHandler(AccessDeniedHandler accessDeniedHandler) {
this.accessDeniedHandler = accessDeniedHandler;
return this;
}
/**
* Sets the {@link AuthenticationEntryPoint} to be used. Defaults to the
* {@link HttpSecurity#getSharedObject(Class)} value. If that is not
* provided defaults to {@link Http403ForbiddenEntryPoint}.
*
* @param authenticationEntryPoint the {@link AuthenticationEntryPoint} to use
* @return the {@link ExceptionHandlingConfigurer} for further customizations
*/
public ExceptionHandlingConfigurer<H> authenticationEntryPoint(AuthenticationEntryPoint authenticationEntryPoint) {
this.authenticationEntryPoint = authenticationEntryPoint;
return this;
}
/**
* Gets any explicitly configured {@link AuthenticationEntryPoint}
* @return
*/
AuthenticationEntryPoint getAuthenticationEntryPoint() {
return this.authenticationEntryPoint;
}
@Override
public void configure(H http) throws Exception {
AuthenticationEntryPoint entryPoint = getEntryPoint(http);
ExceptionTranslationFilter exceptionTranslationFilter = new ExceptionTranslationFilter(entryPoint, getRequestCache(http));
if(accessDeniedHandler != null) {
exceptionTranslationFilter.setAccessDeniedHandler(accessDeniedHandler);
}
exceptionTranslationFilter = postProcess(exceptionTranslationFilter);
http.addFilter(exceptionTranslationFilter);
}
/**
* Gets the {@link AuthenticationEntryPoint} according to the rules specified by {@link #authenticationEntryPoint(AuthenticationEntryPoint)}
* @param http the {@link HttpSecurity} used to look up shared {@link AuthenticationEntryPoint}
* @return the {@link AuthenticationEntryPoint} to use
*/
private AuthenticationEntryPoint getEntryPoint(H http) {
AuthenticationEntryPoint entryPoint = this.authenticationEntryPoint;
if(entryPoint == null) {
AuthenticationEntryPoint sharedEntryPoint = http.getSharedObject(AuthenticationEntryPoint.class);
if(sharedEntryPoint != null) {
entryPoint = sharedEntryPoint;
} else {
entryPoint = new Http403ForbiddenEntryPoint();
}
}
return entryPoint;
}
/**
* Gets the {@link RequestCache} to use. If one is defined using
* {@link #requestCache(org.springframework.security.web.savedrequest.RequestCache)}, then it is used. Otherwise, an
* attempt to find a {@link RequestCache} shared object is made. If that fails, an {@link HttpSessionRequestCache}
* is used
*
* @param http the {@link HttpSecurity} to attempt to fined the shared object
* @return the {@link RequestCache} to use
*/
private RequestCache getRequestCache(H http) {
RequestCache result = http.getSharedObject(RequestCache.class);
if(result != null) {
return result;
}
return new HttpSessionRequestCache();
}
}

View File

@ -0,0 +1,296 @@
/*
* Copyright 2002-2013 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.annotation.web.configurers;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import org.springframework.security.access.AccessDecisionVoter;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.access.expression.SecurityExpressionHandler;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler;
import org.springframework.security.web.access.expression.ExpressionBasedFilterInvocationSecurityMetadataSource;
import org.springframework.security.web.access.expression.WebExpressionVoter;
import org.springframework.security.web.util.RequestMatcher;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* Adds URL based authorization based upon SpEL expressions to an application. At least one
* {@link org.springframework.web.bind.annotation.RequestMapping} needs to be mapped to {@link ConfigAttribute}'s for
* this {@link SecurityContextConfigurer} to have meaning.
* <h2>Security Filters</h2>
*
* The following Filters are populated
*
* <ul>
* <li>{@link org.springframework.security.web.access.intercept.FilterSecurityInterceptor}</li>
* </ul>
*
* <h2>Shared Objects Created</h2>
*
* The following shared objects are populated to allow other {@link org.springframework.security.config.annotation.SecurityConfigurer}'s to customize:
* <ul>
* <li>{@link org.springframework.security.web.access.intercept.FilterSecurityInterceptor}</li>
* </ul>
*
* <h2>Shared Objects Used</h2>
*
* The following shared objects are used:
*
* <ul>
* <li>{@link org.springframework.security.config.annotation.web.builders.HttpSecurity#getAuthenticationManager()}</li>
* </ul>
*
* @param <H> the type of {@link HttpSecurityBuilder} that is being configured
*
* @author Rob Winch
* @since 3.2
* @see {@link org.springframework.security.config.annotation.web.builders.HttpSecurity#authorizeUrls()}
*/
public final class ExpressionUrlAuthorizationConfigurer<H extends HttpSecurityBuilder<H>> extends AbstractInterceptUrlConfigurer<H,ExpressionUrlAuthorizationConfigurer<H>,ExpressionUrlAuthorizationConfigurer<H>.AuthorizedUrl> {
static final String permitAll = "permitAll";
private static final String denyAll = "denyAll";
private static final String anonymous = "anonymous";
private static final String authenticated = "authenticated";
private static final String fullyAuthenticated = "fullyAuthenticated";
private static final String rememberMe = "rememberMe";
private SecurityExpressionHandler<FilterInvocation> expressionHandler = new DefaultWebSecurityExpressionHandler();
/**
* Creates a new instance
* @see HttpSecurity#authorizeUrls()
*/
public ExpressionUrlAuthorizationConfigurer() {
}
/**
* Allows customization of the {@link SecurityExpressionHandler} to be used. The default is {@link DefaultWebSecurityExpressionHandler}
*
* @param expressionHandler the {@link SecurityExpressionHandler} to be used
* @return the {@link ExpressionUrlAuthorizationConfigurer} for further customization.
*/
public ExpressionUrlAuthorizationConfigurer<H> expressionHandler(SecurityExpressionHandler<FilterInvocation> expressionHandler) {
this.expressionHandler = expressionHandler;
return this;
}
@Override
protected final AuthorizedUrl chainRequestMatchersInternal(List<RequestMatcher> requestMatchers) {
return new AuthorizedUrl(requestMatchers);
}
@Override
@SuppressWarnings("rawtypes")
final List<AccessDecisionVoter> getDecisionVoters() {
List<AccessDecisionVoter> decisionVoters = new ArrayList<AccessDecisionVoter>();
WebExpressionVoter expressionVoter = new WebExpressionVoter();
expressionVoter.setExpressionHandler(expressionHandler);
decisionVoters.add(expressionVoter);
return decisionVoters;
}
@Override
final ExpressionBasedFilterInvocationSecurityMetadataSource createMetadataSource() {
LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>> requestMap = createRequestMap();
if(requestMap.isEmpty()) {
throw new IllegalStateException("At least one mapping is required (i.e. authorizeUrls().anyRequest.authenticated())");
}
return new ExpressionBasedFilterInvocationSecurityMetadataSource(requestMap, expressionHandler);
}
/**
* Allows registering multiple {@link RequestMatcher} instances to a collection of {@link ConfigAttribute} instances
*
* @param requestMatchers the {@link RequestMatcher} instances to register to the {@link ConfigAttribute} instances
* @param configAttributes the {@link ConfigAttribute} to be mapped by the {@link RequestMatcher} instances
* @return the {@link ExpressionUrlAuthorizationConfigurer} for further customization.
*/
private ExpressionUrlAuthorizationConfigurer<H> interceptUrl(Iterable<? extends RequestMatcher> requestMatchers, Collection<ConfigAttribute> configAttributes) {
for(RequestMatcher requestMatcher : requestMatchers) {
addMapping(new UrlMapping(requestMatcher, configAttributes));
}
return this;
}
private static String hasRole(String role) {
Assert.notNull(role, "role cannot be null");
if (role.startsWith("ROLE_")) {
throw new IllegalArgumentException("role should not start with 'ROLE_' since it is automatically inserted. Got '" + role + "'");
}
return "hasRole('ROLE_" + role + "')";
}
private static String hasAuthority(String authority) {
return "hasAuthority('" + authority + "')";
}
private static String hasAnyAuthority(String... authorities) {
String anyAuthorities = StringUtils.arrayToDelimitedString(authorities, "','");
return "hasAnyAuthority('" + anyAuthorities + "')";
}
private static String hasIpAddress(String ipAddressExpression) {
return "hasIpAddress('" + ipAddressExpression + "')";
}
public final class AuthorizedUrl {
private List<RequestMatcher> requestMatchers;
private boolean not;
/**
* Creates a new instance
*
* @param requestMatchers the {@link RequestMatcher} instances to map
*/
private AuthorizedUrl(List<RequestMatcher> requestMatchers) {
this.requestMatchers = requestMatchers;
}
/**
* Negates the following expression.
*
* @param role the role to require (i.e. USER, ADMIN, etc). Note, it should not start with "ROLE_" as
* this is automatically inserted.
* @return the {@link ExpressionUrlAuthorizationConfigurer} for further customization
*/
public AuthorizedUrl not() {
this.not = true;
return this;
}
/**
* Shortcut for specifying URLs require a particular role. If you do not want to have "ROLE_" automatically
* inserted see {@link #hasAuthority(String)}.
*
* @param role the role to require (i.e. USER, ADMIN, etc). Note, it should not start with "ROLE_" as
* this is automatically inserted.
* @return the {@link ExpressionUrlAuthorizationConfigurer} for further customization
*/
public ExpressionUrlAuthorizationConfigurer<H> hasRole(String role) {
return access(ExpressionUrlAuthorizationConfigurer.hasRole(role));
}
/**
* Specify that URLs require a particular authority.
*
* @param authority the authority to require (i.e. ROLE_USER, ROLE_ADMIN, etc).
* @return the {@link ExpressionUrlAuthorizationConfigurer} for further customization
*/
public ExpressionUrlAuthorizationConfigurer<H> hasAuthority(String authority) {
return access(ExpressionUrlAuthorizationConfigurer.hasAuthority(authority));
}
/**
* Specify that URLs requires any of a number authorities.
*
* @param authorities the requests require at least one of the authorities (i.e. "ROLE_USER","ROLE_ADMIN" would
* mean either "ROLE_USER" or "ROLE_ADMIN" is required).
* @return the {@link ExpressionUrlAuthorizationConfigurer} for further customization
*/
public ExpressionUrlAuthorizationConfigurer<H> hasAnyAuthority(String... authorities) {
return access(ExpressionUrlAuthorizationConfigurer.hasAnyAuthority(authorities));
}
/**
* Specify that URLs requires a specific IP Address or
* <a href="http://forum.springsource.org/showthread.php?102783-How-to-use-hasIpAddress&p=343971#post343971">subnet</a>.
*
* @param ipaddressExpression the ipaddress (i.e. 192.168.1.79) or local subnet (i.e. 192.168.0/24)
* @return the {@link ExpressionUrlAuthorizationConfigurer} for further customization
*/
public ExpressionUrlAuthorizationConfigurer<H> hasIpAddress(String ipaddressExpression) {
return access(ExpressionUrlAuthorizationConfigurer.hasIpAddress(ipaddressExpression));
}
/**
* Specify that URLs are allowed by anyone.
*
* @return the {@link ExpressionUrlAuthorizationConfigurer} for further customization
*/
public ExpressionUrlAuthorizationConfigurer<H> permitAll() {
return access(permitAll);
}
/**
* Specify that URLs are allowed by anonymous users.
*
* @return the {@link ExpressionUrlAuthorizationConfigurer} for further customization
*/
public ExpressionUrlAuthorizationConfigurer<H> anonymous() {
return access(anonymous);
}
/**
* Specify that URLs are allowed by users that have been remembered.
*
* @return the {@link ExpressionUrlAuthorizationConfigurer} for further customization
* @see {@link RememberMeConfigurer}
*/
public ExpressionUrlAuthorizationConfigurer<H> rememberMe() {
return access(rememberMe);
}
/**
* Specify that URLs are not allowed by anyone.
*
* @return the {@link ExpressionUrlAuthorizationConfigurer} for further customization
*/
public ExpressionUrlAuthorizationConfigurer<H> denyAll() {
return access(denyAll);
}
/**
* Specify that URLs are allowed by any authenticated user.
*
* @return the {@link ExpressionUrlAuthorizationConfigurer} for further customization
*/
public ExpressionUrlAuthorizationConfigurer<H> authenticated() {
return access(authenticated);
}
/**
* Specify that URLs are allowed by users who have authenticated and were not "remembered".
*
* @return the {@link ExpressionUrlAuthorizationConfigurer} for further customization
* @see {@link RememberMeConfigurer}
*/
public ExpressionUrlAuthorizationConfigurer<H> fullyAuthenticated() {
return access(fullyAuthenticated);
}
/**
* Allows specifying that URLs are secured by an arbitrary expression
*
* @param attribute the expression to secure the URLs (i.e. "hasRole('ROLE_USER') and hasRole('ROLE_SUPER')")
* @return the {@link ExpressionUrlAuthorizationConfigurer} for further customization
*/
public ExpressionUrlAuthorizationConfigurer<H> access(String attribute) {
if(not) {
attribute = "!" + attribute;
}
interceptUrl(requestMatchers, SecurityConfig.createList(attribute));
return ExpressionUrlAuthorizationConfigurer.this;
}
}
}

View File

@ -0,0 +1,239 @@
/*
* Copyright 2002-2013 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.annotation.web.configurers;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.RememberMeServices;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
import org.springframework.security.web.authentication.ui.DefaultLoginPageViewFilter;
/**
* Adds form based authentication. All attributes have reasonable defaults
* making all parameters are optional. If no {@link #loginPage(String)} is
* specified, a default login page will be generated by the framework.
*
* <h2>Security Filters</h2>
*
* The following Filters are populated
*
* <ul>
* <li>
* {@link UsernamePasswordAuthenticationFilter}
* </li>
* </ul>
*
* <h2>Shared Objects Created</h2>
*
* The following shared objects are populated
*
* <ul>
* <li> {@link AuthenticationEntryPoint} </li>
* </ul>
*
* <h2>Shared Objects Used</h2>
*
* The following shared objects are used:
*
* <ul>
* <li>{@link HttpSecurity#getAuthenticationManager()}</li>
* <li>{@link RememberMeServices} - is optionally used. See {@link RememberMeConfigurer}</li>
* <li>{@link SessionAuthenticationStrategy} - is optionally used. See {@link SessionManagementConfigurer}</li>
* <li>{@link DefaultLoginPageViewFilter} - if present will be populated with information from the configuration</li>
* </ul>
*
* @author Rob Winch
* @since 3.2
*/
public final class FormLoginConfigurer<H extends HttpSecurityBuilder<H>> extends AbstractAuthenticationFilterConfigurer<H,FormLoginConfigurer<H>,UsernamePasswordAuthenticationFilter> {
/**
* Creates a new instance
* @see HttpSecurity#formLogin()
*/
public FormLoginConfigurer() {
super(createUsernamePasswordAuthenticationFilter(),"/login");
usernameParameter("username");
passwordParameter("password");
}
/**
* <p>
* Specifies the URL to send users to if login is required. If used with
* {@link WebSecurityConfigurerAdapter} a default login page will be
* generated when this attribute is not specified.
* </p>
*
* <p>
* If a URL is specified or this is not being used in conjuction with
* {@link WebSecurityConfigurerAdapter}, users are required to process the
* specified URL to generate a login page. In general, the login page should
* create a form that submits a request with the following requirements to
* work with {@link UsernamePasswordAuthenticationFilter}:
* </p>
*
* <ul>
* <li>It must be an HTTP POST</li>
* <li>It must be submitted to {@link #loginProcessingUrl(String)}</li>
* <li>It should include the username as an HTTP parameter by the name of
* {@link #usernameParameter(String)}</li>
* <li>It should include the password as an HTTP parameter by the name of
* {@link #passwordParameter(String)}</li>
* </ul>
*
* <h2>Example login.jsp</h2>
*
* Login pages can be rendered with any technology you choose so long as the
* rules above are followed. Below is an example login.jsp that can be used as
* a quick start when using JSP's or as a baseline to translate into another view
* technology.
*
* <pre>
* <!-- loginProcessingUrl should correspond to FormLoginConfigurer#loginProcessingUrl. Don't forget to perform a POST -->
* &lt;c:url value="/login" var="loginProcessingUrl"/&gt;
* &lt;form action="${loginProcessingUrl}" method="post"&gt;
* &lt;fieldset&gt;
* &lt;legend&gt;Please Login&lt;/legend&gt;
* &lt;!-- use param.error assuming FormLoginConfigurer#failureUrl contains the query parameter error --&gt;
* &lt;c:if test="${param.error != null}"&gt;
* &lt;div&gt;
* Failed to login.
* &lt;c:if test="${SPRING_SECURITY_LAST_EXCEPTION != null}"&gt;
* Reason: &lt;c:out value="${SPRING_SECURITY_LAST_EXCEPTION.message}" /&gt;
* &lt;/c:if&gt;
* &lt;/div&gt;
* &lt;/c:if&gt;
* &lt;!-- the configured LogoutConfigurer#logoutSuccessUrl is /login?logout and contains the query param logout --&gt;
* &lt;c:if test="${param.logout != null}"&gt;
* &lt;div&gt;
* You have been logged out.
* &lt;/div&gt;
* &lt;/c:if&gt;
* &lt;p&gt;
* &lt;label for="username"&gt;Username&lt;/label&gt;
* &lt;input type="text" id="username" name="username"/&gt;
* &lt;/p&gt;
* &lt;p&gt;
* &lt;label for="password"&gt;Password&lt;/label&gt;
* &lt;input type="password" id="password" name="password"/&gt;
* &lt;/p&gt;
* &lt;!-- if using RememberMeConfigurer make sure remember-me matches RememberMeConfigurer#rememberMeParameter --&gt;
* &lt;p&gt;
* &lt;label for="remember-me"&gt;Remember Me?&lt;/label&gt;
* &lt;input type="checkbox" id="remember-me" name="remember-me"/&gt;
* &lt;/p&gt;
* &lt;div&gt;
* &lt;button type="submit" class="btn"&gt;Log in&lt;/button&gt;
* &lt;/div&gt;
* &lt;/fieldset&gt;
* &lt;/form&gt;
* </pre>
*
* @param loginPage
* the login page to redirect to if authentication is required
* (i.e. "/login")
* @return the {@link FormLoginConfigurer} for additional customization
*/
public FormLoginConfigurer<H> loginPage(String loginPage) {
return super.loginPage(loginPage);
}
/**
* The HTTP parameter to look for the username when performing
* authentication. Default is "username".
*
* @param usernameParameter
* the HTTP parameter to look for the username when performing
* authentication
* @return the {@link FormLoginConfigurer} for additional customization
*/
public FormLoginConfigurer<H> usernameParameter(String usernameParameter) {
getAuthenticationFilter().setUsernameParameter(usernameParameter);
return this;
}
/**
* The HTTP parameter to look for the password when performing
* authentication. Default is "password".
*
* @param passwordParameter
* the HTTP parameter to look for the password when performing
* authentication
* @return the {@link FormLoginConfigurer} for additional customization
*/
public FormLoginConfigurer<H> passwordParameter(String passwordParameter) {
getAuthenticationFilter().setPasswordParameter(passwordParameter);
return this;
}
@Override
public void init(H http) throws Exception {
super.init(http);
initDefaultLoginFilter(http);
}
/**
* Gets the HTTP parameter that is used to submit the username.
*
* @return the HTTP parameter that is used to submit the username
*/
private String getUsernameParameter() {
return getAuthenticationFilter().getUsernameParameter();
}
/**
* Gets the HTTP parameter that is used to submit the password.
*
* @return the HTTP parameter that is used to submit the password
*/
private String getPasswordParameter() {
return getAuthenticationFilter().getPasswordParameter();
}
/**
* If available, initializes the {@link DefaultLoginPageViewFilter} shared object.
*
* @param http the {@link HttpSecurityBuilder} to use
*/
private void initDefaultLoginFilter(H http) {
DefaultLoginPageViewFilter loginPageGeneratingFilter = http.getSharedObject(DefaultLoginPageViewFilter.class);
if(loginPageGeneratingFilter != null && !isCustomLoginPage()) {
loginPageGeneratingFilter.setFormLoginEnabled(true);
loginPageGeneratingFilter.setUsernameParameter(getUsernameParameter());
loginPageGeneratingFilter.setPasswordParameter(getPasswordParameter());
loginPageGeneratingFilter.setLoginPageUrl(getLoginPage());
loginPageGeneratingFilter.setFailureUrl(getFailureUrl());
loginPageGeneratingFilter.setAuthenticationUrl(getLoginProcessingUrl());
}
}
private static UsernamePasswordAuthenticationFilter createUsernamePasswordAuthenticationFilter() {
return new UsernamePasswordAuthenticationFilter() {
@Override
protected boolean requiresAuthentication(HttpServletRequest request, HttpServletResponse response) {
return "POST".equals(request.getMethod()) && super.requiresAuthentication(request, response);
}
};
}
}

View File

@ -0,0 +1,127 @@
/*
* Copyright 2002-2013 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.annotation.web.configurers;
import javax.servlet.http.HttpServletRequest;
import org.springframework.security.authentication.AuthenticationDetailsSource;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
/**
* Adds HTTP basic based authentication. All attributes have reasonable defaults
* making all parameters are optional.
*
* <h2>Security Filters</h2>
*
* The following Filters are populated
*
* <ul>
* <li>
* {@link BasicAuthenticationFilter}
* </li>
* </ul>
*
* <h2>Shared Objects Created</h2>
*
* No shared objects are populated
*
* <h2>Shared Objects Used</h2>
*
* The following shared objects are used:
*
* <ul>
* <li>{@link HttpSecurity#getAuthenticationManager()} </li>
* </ul>
*
* @author Rob Winch
* @since 3.2
*/
public final class HttpBasicConfigurer<B extends HttpSecurityBuilder<B>> extends AbstractHttpConfigurer<B> {
private static final String DEFAULT_REALM = "Spring Security Application";
private AuthenticationEntryPoint authenticationEntryPoint;
private AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource;
/**
* Creates a new instance
* @throws Exception
* @see {@link HttpSecurity#httpBasic()}
*/
public HttpBasicConfigurer() throws Exception {
realmName(DEFAULT_REALM);
}
/**
* Shortcut for {@link #authenticationEntryPoint(AuthenticationEntryPoint)}
* specifying a {@link BasicAuthenticationEntryPoint} with the specified
* realm name.
*
* @param realmName
* the HTTP Basic realm to use
* @return {@link HttpBasicConfigurer} for additional customization
* @throws Exception
*/
public HttpBasicConfigurer<B> realmName(String realmName) throws Exception {
BasicAuthenticationEntryPoint basicAuthEntryPoint = new BasicAuthenticationEntryPoint();
basicAuthEntryPoint.setRealmName(realmName);
basicAuthEntryPoint.afterPropertiesSet();
return authenticationEntryPoint(basicAuthEntryPoint);
}
/**
* The {@link AuthenticationEntryPoint} to be po pulated on
* {@link BasicAuthenticationFilter} in the event that authentication fails.
* The default to use {@link BasicAuthenticationEntryPoint} with the realm
* "Spring Security Application".
*
* @param authenticationEntryPoint the {@link AuthenticationEntryPoint} to use
* @return {@link HttpBasicConfigurer} for additional customization
*/
public HttpBasicConfigurer<B> authenticationEntryPoint(AuthenticationEntryPoint authenticationEntryPoint) {
this.authenticationEntryPoint = authenticationEntryPoint;
return this;
}
/**
* Specifies a custom {@link AuthenticationDetailsSource} to use for basic
* authentication. The default is {@link WebAuthenticationDetailsSource}.
*
* @param authenticationDetailsSource
* the custom {@link AuthenticationDetailsSource} to use
* @return {@link HttpBasicConfigurer} for additional customization
*/
public HttpBasicConfigurer<B> authenticationDetailsSource(AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource) {
this.authenticationDetailsSource = authenticationDetailsSource;
return this;
}
@Override
public void configure(B http) throws Exception {
AuthenticationManager authenticationManager = http.getAuthenticationManager();
BasicAuthenticationFilter basicAuthenticationFilter = new BasicAuthenticationFilter(authenticationManager, authenticationEntryPoint);
if(authenticationDetailsSource != null) {
basicAuthenticationFilter.setAuthenticationDetailsSource(authenticationDetailsSource);
}
basicAuthenticationFilter = postProcess(basicAuthenticationFilter);
http.addFilter(basicAuthenticationFilter);
}
}

View File

@ -0,0 +1,261 @@
/*
* Copyright 2002-2013 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.annotation.web.configurers;
import java.util.HashSet;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.authority.mapping.SimpleMappableAttributesRetriever;
import org.springframework.security.core.userdetails.AuthenticationUserDetailsService;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.Http403ForbiddenEntryPoint;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedGrantedAuthoritiesUserDetailsService;
import org.springframework.security.web.authentication.preauth.j2ee.J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource;
import org.springframework.security.web.authentication.preauth.j2ee.J2eePreAuthenticatedProcessingFilter;
/**
* Adds support for J2EE pre authentication.
*
* <h2>Security Filters</h2>
*
* The following Filters are populated
*
* <ul>
* <li>
* {@link J2eePreAuthenticatedProcessingFilter}</li>
* </ul>
*
* <h2>Shared Objects Created</h2>
*
* <ul>
* <li>
* {@link AuthenticationEntryPoint}
* is populated with an {@link Http403ForbiddenEntryPoint}</li>
* <li>A {@link PreAuthenticatedAuthenticationProvider} is populated into
* {@link HttpSecurity#authenticationProvider(org.springframework.security.authentication.AuthenticationProvider)}
* </li>
* </ul>
*
* <h2>Shared Objects Used</h2>
*
* The following shared objects are used:
*
* <ul>
* <li>{@link HttpSecurity#getAuthenticationManager()}</li>
* </ul>
*
* @author Rob Winch
* @since 3.2
*/
public final class JeeConfigurer<H extends HttpSecurityBuilder<H>> extends AbstractHttpConfigurer<H> {
private J2eePreAuthenticatedProcessingFilter j2eePreAuthenticatedProcessingFilter;
private AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken> authenticationUserDetailsService;
private Set<String> mappableRoles = new HashSet<String>();
/**
* Creates a new instance
* @see HttpSecurity#jee()
*/
public JeeConfigurer() {
}
/**
* Specifies roles to use map from the {@link HttpServletRequest} to the
* {@link UserDetails}. If {@link HttpServletRequest#isUserInRole(String)}
* returns true, the role is added to the {@link UserDetails}. This method
* is the equivalent of invoking {@link #mappableAuthorities(Set)}. Multiple
* invocations of {@link #mappableAuthorities(String...)} will override previous
* invocations.
*
* <p>
* There are no default roles that are mapped.
* </p>
*
* @param mappableRoles
* the roles to attempt to map to the {@link UserDetails} (i.e.
* "ROLE_USER", "ROLE_ADMIN", etc).
* @return the {@link JeeConfigurer} for further customizations
* @see SimpleMappableAttributesRetriever
* @see #mappableRoles(String...)
*/
public JeeConfigurer<H> mappableAuthorities(String... mappableRoles) {
this.mappableRoles.clear();
for(String role : mappableRoles) {
this.mappableRoles.add(role);
}
return this;
}
/**
* Specifies roles to use map from the {@link HttpServletRequest} to the
* {@link UserDetails} and automatically prefixes it with "ROLE_". If
* {@link HttpServletRequest#isUserInRole(String)} returns true, the role is
* added to the {@link UserDetails}. This method is the equivalent of
* invoking {@link #mappableAuthorities(Set)}. Multiple invocations of
* {@link #mappableRoles(String...)} will override previous invocations.
*
* <p>
* There are no default roles that are mapped.
* </p>
*
* @param mappableRoles
* the roles to attempt to map to the {@link UserDetails} (i.e.
* "USER", "ADMIN", etc).
* @return the {@link JeeConfigurer} for further customizations
* @see SimpleMappableAttributesRetriever
* @see #mappableAuthorities(String...)
*/
public JeeConfigurer<H> mappableRoles(String... mappableRoles) {
this.mappableRoles.clear();
for(String role : mappableRoles) {
this.mappableRoles.add("ROLE_" + role);
}
return this;
}
/**
* Specifies roles to use map from the {@link HttpServletRequest} to the
* {@link UserDetails}. If {@link HttpServletRequest#isUserInRole(String)}
* returns true, the role is added to the {@link UserDetails}. This is the
* equivalent of {@link #mappableRoles(String...)}. Multiple invocations of
* {@link #mappableAuthorities(Set)} will override previous invocations.
*
* <p>
* There are no default roles that are mapped.
* </p>
*
* @param mappableRoles
* the roles to attempt to map to the {@link UserDetails}.
* @return the {@link JeeConfigurer} for further customizations
* @see SimpleMappableAttributesRetriever
*/
public JeeConfigurer<H> mappableAuthorities(Set<String> mappableRoles) {
this.mappableRoles = mappableRoles;
return this;
}
/**
* Specifies the {@link AuthenticationUserDetailsService} that is used with
* the {@link PreAuthenticatedAuthenticationProvider}. The default is a
* {@link PreAuthenticatedGrantedAuthoritiesUserDetailsService}.
*
* @param authenticatedUserDetailsService the {@link AuthenticationUserDetailsService} to use.
* @return the {@link JeeConfigurer} for further configuration
*/
public JeeConfigurer<H> authenticatedUserDetailsService(
AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken> authenticatedUserDetailsService) {
this.authenticationUserDetailsService = authenticatedUserDetailsService;
return this;
}
/**
* Allows specifying the {@link J2eePreAuthenticatedProcessingFilter} to
* use. If {@link J2eePreAuthenticatedProcessingFilter} is provided, all of its attributes must also be
* configured manually (i.e. all attributes populated in the {@link JeeConfigurer} are not used).
*
* @param j2eePreAuthenticatedProcessingFilter the {@link J2eePreAuthenticatedProcessingFilter} to use.
* @return the {@link JeeConfigurer} for further configuration
*/
public JeeConfigurer<H> j2eePreAuthenticatedProcessingFilter(
J2eePreAuthenticatedProcessingFilter j2eePreAuthenticatedProcessingFilter) {
this.j2eePreAuthenticatedProcessingFilter = j2eePreAuthenticatedProcessingFilter;
return this;
}
/**
* Populates a {@link PreAuthenticatedAuthenticationProvider} into
* {@link HttpSecurity#authenticationProvider(org.springframework.security.authentication.AuthenticationProvider)}
* and a {@link Http403ForbiddenEntryPoint} into
* {@link HttpSecurity#authenticationEntryPoint(org.springframework.security.web.AuthenticationEntryPoint)}
*
* @see org.springframework.security.config.annotation.SecurityConfigurerAdapter#init(org.springframework.security.config.annotation.SecurityBuilder)
*/
@Override
public void init(H http) throws Exception {
PreAuthenticatedAuthenticationProvider authenticationProvider = new PreAuthenticatedAuthenticationProvider();
authenticationProvider.setPreAuthenticatedUserDetailsService(getUserDetailsService());
authenticationProvider = postProcess(authenticationProvider);
http
.authenticationProvider(authenticationProvider)
.setSharedObject(AuthenticationEntryPoint.class,new Http403ForbiddenEntryPoint());
}
@Override
public void configure(H http) throws Exception {
J2eePreAuthenticatedProcessingFilter filter = getFilter(http
.getAuthenticationManager());
http.addFilter(filter);
}
/**
* Gets the {@link J2eePreAuthenticatedProcessingFilter} or creates a default instance using the properties provided.
* @param authenticationManager the {@link AuthenticationManager} to use.
* @return the {@link J2eePreAuthenticatedProcessingFilter} to use.
*/
private J2eePreAuthenticatedProcessingFilter getFilter(
AuthenticationManager authenticationManager) {
if (j2eePreAuthenticatedProcessingFilter == null) {
j2eePreAuthenticatedProcessingFilter = new J2eePreAuthenticatedProcessingFilter();
j2eePreAuthenticatedProcessingFilter
.setAuthenticationManager(authenticationManager);
j2eePreAuthenticatedProcessingFilter
.setAuthenticationDetailsSource(createWebAuthenticationDetailsSource());
j2eePreAuthenticatedProcessingFilter = postProcess(j2eePreAuthenticatedProcessingFilter);
}
return j2eePreAuthenticatedProcessingFilter;
}
/**
* Gets the {@link AuthenticationUserDetailsService} that was specified or
* defaults to {@link PreAuthenticatedGrantedAuthoritiesUserDetailsService}.
*
* @return the {@link AuthenticationUserDetailsService} to use
*/
private AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken> getUserDetailsService() {
return authenticationUserDetailsService == null ? new PreAuthenticatedGrantedAuthoritiesUserDetailsService()
: authenticationUserDetailsService;
}
/**
* Creates the
* {@link J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource} to set on
* the {@link J2eePreAuthenticatedProcessingFilter}. It is populated with a
* {@link SimpleMappableAttributesRetriever}.
*
* @return the
* {@link J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource}
* to use.
*/
private J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource createWebAuthenticationDetailsSource() {
J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource detailsSource = new J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource();
SimpleMappableAttributesRetriever rolesRetriever = new SimpleMappableAttributesRetriever();
rolesRetriever.setMappableAttributes(mappableRoles);
detailsSource.setMappableRolesRetriever(rolesRetriever);
detailsSource = postProcess(detailsSource);
return detailsSource;
}
}

View File

@ -0,0 +1,243 @@
/*
* Copyright 2002-2013 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.annotation.web.configurers;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.http.HttpSession;
import org.springframework.security.config.annotation.SecurityConfigurer;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.authentication.logout.CookieClearingLogoutHandler;
import org.springframework.security.web.authentication.logout.LogoutFilter;
import org.springframework.security.web.authentication.logout.LogoutHandler;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler;
import org.springframework.security.web.authentication.ui.DefaultLoginPageViewFilter;
/**
* Adds logout support. Other {@link SecurityConfigurer} instances may invoke
* {@link #addLogoutHandler(LogoutHandler)} in the
* {@link #init(HttpSecurity)} phase.
*
* <h2>Security Filters</h2>
*
* The following Filters are populated
*
* <ul>
* <li>
* {@link LogoutFilter}</li>
* </ul>
*
* <h2>Shared Objects Created</h2>
*
* No shared Objects are created
*
* <h2>Shared Objects Used</h2>
*
* No shared objects are used.
*
* @author Rob Winch
* @since 3.2
* @see RememberMeConfigurer
*/
public final class LogoutConfigurer<H extends HttpSecurityBuilder<H>> extends AbstractHttpConfigurer<H> {
private List<LogoutHandler> logoutHandlers = new ArrayList<LogoutHandler>();
private SecurityContextLogoutHandler contextLogoutHandler = new SecurityContextLogoutHandler();
private String logoutSuccessUrl = "/login?logout";
private LogoutSuccessHandler logoutSuccessHandler;
private String logoutUrl = "/logout";
private boolean permitAll;
private boolean customLogoutSuccess;
/**
* Creates a new instance
* @see HttpSecurity#logout()
*/
public LogoutConfigurer() {
}
/**
* Adds a {@link LogoutHandler}. The {@link SecurityContextLogoutHandler} is
* added as the last {@link LogoutHandler} by default.
*
* @param logoutHandler the {@link LogoutHandler} to add
* @return the {@link LogoutConfigurer} for further customization
*/
public LogoutConfigurer<H> addLogoutHandler(LogoutHandler logoutHandler) {
this.logoutHandlers.add(logoutHandler);
return this;
}
/**
* Configures {@link SecurityContextLogoutHandler} to invalidate the {@link HttpSession} at the time of logout.
* @param invalidateHttpSession true if the {@link HttpSession} should be invalidated (default), or false otherwise.
* @return the {@link LogoutConfigurer} for further customization
*/
public LogoutConfigurer<H> invalidateHttpSession(boolean invalidateHttpSession) {
contextLogoutHandler.setInvalidateHttpSession(invalidateHttpSession);
return this;
}
/**
* The URL that triggers logout to occur. The default is "/logout"
* @param logoutUrl the URL that will invoke logout.
* @return the {@link LogoutConfigurer} for further customization
*/
public LogoutConfigurer<H> logoutUrl(String logoutUrl) {
this.logoutUrl = logoutUrl;
return this;
}
/**
* The URL to redirect to after logout has occurred. The default is
* "/login?logout". This is a shortcut for invoking
* {@link #logoutSuccessHandler(LogoutSuccessHandler)} with a
* {@link SimpleUrlLogoutSuccessHandler}.
*
* @param logoutSuccessUrl
* the URL to redirect to after logout occurred
* @return the {@link LogoutConfigurer} for further customization
*/
public LogoutConfigurer<H> logoutSuccessUrl(String logoutSuccessUrl) {
this.customLogoutSuccess = true;
this.logoutSuccessUrl = logoutSuccessUrl;
return this;
}
/**
* A shortcut for {@link #permitAll(boolean)} with <code>true</code> as an argument.
* @return the {@link LogoutConfigurer} for further customizations
*/
public LogoutConfigurer<H> permitAll() {
return permitAll(true);
}
/**
* Allows specifying the names of cookies to be removed on logout success.
* This is a shortcut to easily invoke
* {@link #addLogoutHandler(LogoutHandler)} with a
* {@link CookieClearingLogoutHandler}.
*
* @param cookieNamesToClear the names of cookies to be removed on logout success.
* @return the {@link LogoutConfigurer} for further customization
*/
public LogoutConfigurer<H> deleteCookies(String... cookieNamesToClear) {
return addLogoutHandler(new CookieClearingLogoutHandler(cookieNamesToClear));
}
/**
* Sets the {@link LogoutSuccessHandler} to use. If this is specified,
* {@link #logoutSuccessUrl(String)} is ignored.
*
* @param logoutSuccessHandler
* the {@link LogoutSuccessHandler} to use after a user has been
* logged out.
* @return the {@link LogoutConfigurer} for further customizations
*/
public LogoutConfigurer<H> logoutSuccessHandler(LogoutSuccessHandler logoutSuccessHandler) {
this.logoutSuccessUrl = null;
this.customLogoutSuccess = true;
this.logoutSuccessHandler = logoutSuccessHandler;
return this;
}
/**
* Grants access to the {@link #logoutSuccessUrl(String)} and the {@link #logoutUrl(String)} for every user.
*
* @param permitAll if true grants access, else nothing is done
* @return the {@link LogoutConfigurer} for further customization.
*/
public LogoutConfigurer<H> permitAll(boolean permitAll) {
this.permitAll = permitAll;
return this;
}
/**
* Gets the {@link LogoutSuccessHandler} if not null, otherwise creates a
* new {@link SimpleUrlLogoutSuccessHandler} using the
* {@link #logoutSuccessUrl(String)}.
*
* @return the {@link LogoutSuccessHandler} to use
*/
private LogoutSuccessHandler getLogoutSuccessHandler() {
if(logoutSuccessHandler != null) {
return logoutSuccessHandler;
}
SimpleUrlLogoutSuccessHandler logoutSuccessHandler = new SimpleUrlLogoutSuccessHandler();
logoutSuccessHandler.setDefaultTargetUrl(logoutSuccessUrl);
return logoutSuccessHandler;
}
@Override
public void init(H http) throws Exception {
if(permitAll) {
PermitAllSupport.permitAll(http, this.logoutUrl, this.logoutSuccessUrl);
}
DefaultLoginPageViewFilter loginPageGeneratingFilter = http.getSharedObject(DefaultLoginPageViewFilter.class);
if(loginPageGeneratingFilter != null && !isCustomLogoutSuccess()) {
loginPageGeneratingFilter.setLogoutSuccessUrl(getLogoutSuccessUrl());
}
}
@Override
public void configure(H http) throws Exception {
LogoutFilter logoutFilter = createLogoutFilter();
http.addFilter(logoutFilter);
}
/**
* Returns true if the logout success has been customized via
* {@link #logoutSuccessUrl(String)} or
* {@link #logoutSuccessHandler(LogoutSuccessHandler)}.
*
* @return true if logout success handling has been customized, else false
*/
private boolean isCustomLogoutSuccess() {
return customLogoutSuccess;
}
/**
* Gets the logoutSuccesUrl or null if a
* {@link #logoutSuccessHandler(LogoutSuccessHandler)} was configured.
*
* @return the logoutSuccessUrl
*/
private String getLogoutSuccessUrl() {
return logoutSuccessUrl;
}
/**
* Creates the {@link LogoutFilter} using the {@link LogoutHandler}
* instances, the {@link #logoutSuccessHandler(LogoutSuccessHandler)} and
* the {@link #logoutUrl(String)}.
*
* @return the {@link LogoutFilter} to use.
* @throws Exception
*/
private LogoutFilter createLogoutFilter() throws Exception {
logoutHandlers.add(contextLogoutHandler);
LogoutHandler[] handlers = logoutHandlers.toArray(new LogoutHandler[logoutHandlers.size()]);
LogoutFilter result = new LogoutFilter(getLogoutSuccessHandler(), handlers);
result.setFilterProcessesUrl(logoutUrl);
result = postProcess(result);
return result;
}
}

View File

@ -0,0 +1,73 @@
/*
* Copyright 2002-2013 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.annotation.web.configurers;
import javax.servlet.http.HttpServletRequest;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.configurers.AbstractRequestMatcherMappingConfigurer.UrlMapping;
import org.springframework.security.web.util.RequestMatcher;
/**
* Configures non-null URL's to grant access to every URL
* @author Rob Winch
* @since 3.2
*/
final class PermitAllSupport {
@SuppressWarnings("unchecked")
public static void permitAll(HttpSecurityBuilder<? extends HttpSecurityBuilder<?>> http, String... urls) {
ExpressionUrlAuthorizationConfigurer<?> configurer = http.getConfigurer(ExpressionUrlAuthorizationConfigurer.class);
if(configurer == null) {
throw new IllegalStateException("permitAll only works with HttpSecurity.authorizeUrls()");
}
for(String url : urls) {
if(url != null) {
configurer.addMapping(0, new UrlMapping(new ExactUrlRequestMatcher(url), SecurityConfig.createList(ExpressionUrlAuthorizationConfigurer.permitAll)));
}
}
}
private final static class ExactUrlRequestMatcher implements RequestMatcher {
private String processUrl;
private ExactUrlRequestMatcher(String processUrl) {
this.processUrl = processUrl;
}
public boolean matches(HttpServletRequest request) {
String uri = request.getRequestURI();
String query = request.getQueryString();
if(query != null) {
uri += "?" + query;
}
if ("".equals(request.getContextPath())) {
return uri.equals(processUrl);
}
return uri.equals(request.getContextPath() + processUrl);
}
}
private PermitAllSupport() {}
}

View File

@ -0,0 +1,115 @@
/*
* Copyright 2002-2013 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.annotation.web.configurers;
import java.util.HashMap;
import java.util.Map;
import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.PortMapper;
import org.springframework.security.web.PortMapperImpl;
/**
* Allows configuring a shared {@link PortMapper} instance used to determine the
* ports when redirecting between HTTP and HTTPS. The {@link PortMapper} can be
* obtained from {@link HttpSecurity#getSharedObject(Class)}.
*
* @author Rob Winch
* @since 3.2
*/
public final class PortMapperConfigurer<H extends HttpSecurityBuilder<H>> extends SecurityConfigurerAdapter<DefaultSecurityFilterChain,H> {
private PortMapper portMapper;
private Map<String, String> httpsPortMappings = new HashMap<String,String>();
/**
* Creates a new instance
*/
public PortMapperConfigurer() {
}
/**
* Allows specifying the {@link PortMapper} instance.
* @param portMapper
* @return
*/
public PortMapperConfigurer<H> portMapper(PortMapper portMapper) {
this.portMapper = portMapper;
return this;
}
/**
* Adds a port mapping
* @param httpPort the HTTP port that maps to a specific HTTPS port.
* @return {@link HttpPortMapping} to define the HTTPS port
*/
public HttpPortMapping http(int httpPort) {
return new HttpPortMapping(httpPort);
}
@Override
public void init(H http) throws Exception {
http.setSharedObject(PortMapper.class, getPortMapper());
}
/**
* Gets the {@link PortMapper} to use. If {@link #portMapper(PortMapper)}
* was not invoked, builds a {@link PortMapperImpl} using the port mappings
* specified with {@link #http(int)}.
*
* @return the {@link PortMapper} to use
*/
private PortMapper getPortMapper() {
if(portMapper == null) {
PortMapperImpl portMapper = new PortMapperImpl();
portMapper.setPortMappings(httpsPortMappings);
this.portMapper = portMapper;
}
return portMapper;
}
/**
* Allows specifying the HTTPS port for a given HTTP port when redirecting
* between HTTP and HTTPS.
*
* @author Rob Winch
* @since 3.2
*/
public final class HttpPortMapping {
private final int httpPort;
/**
* Creates a new instance
* @param httpPort
* @see PortMapperConfigurer#http(int)
*/
private HttpPortMapping(int httpPort) {
this.httpPort = httpPort;
}
/**
* Maps the given HTTP port to the provided HTTPS port and vice versa.
* @param httpsPort the HTTPS port to map to
* @return the {@link PortMapperConfigurer} for further customization
*/
public PortMapperConfigurer<H> mapsTo(int httpsPort) {
httpsPortMappings.put(String.valueOf(httpPort), String.valueOf(httpsPort));
return PortMapperConfigurer.this;
}
}
}

View File

@ -0,0 +1,352 @@
/*
* Copyright 2002-2013 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.annotation.web.configurers;
import java.util.UUID;
import org.springframework.security.authentication.RememberMeAuthenticationProvider;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.RememberMeServices;
import org.springframework.security.web.authentication.logout.LogoutHandler;
import org.springframework.security.web.authentication.rememberme.AbstractRememberMeServices;
import org.springframework.security.web.authentication.rememberme.PersistentTokenBasedRememberMeServices;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
import org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter;
import org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices;
import org.springframework.security.web.authentication.ui.DefaultLoginPageViewFilter;
/**
* Configures Remember Me authentication. This typically involves the user
* checking a box when they enter their username and password that states to
* "Remember Me".
*
* <h2>Security Filters</h2>
*
* The following Filters are populated
*
* <ul>
* <li>
* {@link RememberMeAuthenticationFilter}</li>
* </ul>
*
* <h2>Shared Objects Created</h2>
*
* The following shared objects are populated
*
* <ul>
* <li>
* {@link HttpSecurity#authenticationProvider(org.springframework.security.authentication.AuthenticationProvider)}
* is populated with a {@link RememberMeAuthenticationProvider}</li>
* <li>{@link RememberMeServices} is populated as a shared object and available on {@link HttpSecurity#getSharedObject(Class)}</li>
* <li>{@link LogoutConfigurer#addLogoutHandler(LogoutHandler)} is used to add a logout handler to clean up the remember me authentication.</li>
* </ul>
*
* <h2>Shared Objects Used</h2>
*
* The following shared objects are used:
*
* <ul>
* <li>{@link HttpSecurity#getAuthenticationManager()}</li>
* <li>{@link UserDetailsService} if no {@link #userDetailsService(UserDetailsService)} was specified.</li>
* <li> {@link DefaultLoginPageViewFilter} - if present will be populated with information from the configuration</li>
* </ul>
*
* @author Rob Winch
* @since 3.2
*/
public final class RememberMeConfigurer<H extends HttpSecurityBuilder<H>> extends AbstractHttpConfigurer<H> {
private AuthenticationSuccessHandler authenticationSuccessHandler;
private String key;
private RememberMeServices rememberMeServices;
private LogoutHandler logoutHandler;
private String rememberMeParameter = "remember-me";
private String rememberMeCookieName = "remember-me";
private PersistentTokenRepository tokenRepository;
private UserDetailsService userDetailsService;
private Integer tokenValiditySeconds;
private Boolean useSecureCookie;
/**
* Creates a new instance
*/
public RememberMeConfigurer() {
}
/**
* Allows specifying how long (in seconds) a token is valid for
*
* @param tokenValiditySeconds
* @return {@link RememberMeConfigurer} for further customization
* @see AbstractRememberMeServices#setTokenValiditySeconds(int)
*/
public RememberMeConfigurer<H> tokenValiditySeconds(int tokenValiditySeconds) {
this.tokenValiditySeconds = tokenValiditySeconds;
return this;
}
/**
*Whether the cookie should be flagged as secure or not. Secure cookies can only be sent over an HTTPS connection
* and thus cannot be accidentally submitted over HTTP where they could be intercepted.
* <p>
* By default the cookie will be secure if the request is secure. If you only want to use remember-me over
* HTTPS (recommended) you should set this property to {@code true}.
*
* @param useSecureCookie set to {@code true} to always user secure cookies, {@code false} to disable their use.
* @return the {@link RememberMeConfigurer} for further customization
* @see AbstractRememberMeServices#setUseSecureCookie(boolean)
*/
public RememberMeConfigurer<H> useSecureCookie(boolean useSecureCookie) {
this.useSecureCookie = useSecureCookie;
return this;
}
/**
* Specifies the {@link UserDetailsService} used to look up the
* {@link UserDetails} when a remember me token is valid. The default is to
* use the {@link UserDetailsService} found by invoking
* {@link HttpSecurity#getSharedObject(Class)} which is set when using
* {@link WebSecurityConfigurerAdapter#registerAuthentication(org.springframework.security.config.annotation.authentication.AuthenticationManagerBuilder)}.
* Alternatively, one can populate {@link #rememberMeServices(RememberMeServices)}.
*
* @param userDetailsService
* the {@link UserDetailsService} to configure
* @return the {@link RememberMeConfigurer} for further customization
* @see AbstractRememberMeServices
*/
public RememberMeConfigurer<H> userDetailsService(UserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
return this;
}
/**
* Specifies the {@link PersistentTokenRepository} to use. The default is to
* use {@link TokenBasedRememberMeServices} instead.
*
* @param tokenRepository
* the {@link PersistentTokenRepository} to use
* @return the {@link RememberMeConfigurer} for further customization
*/
public RememberMeConfigurer<H> tokenRepository(PersistentTokenRepository tokenRepository) {
this.tokenRepository = tokenRepository;
return this;
}
/**
* Sets the key to identify tokens created for remember me authentication. Default is a secure randomly generated
* key.
*
* @param key the key to identify tokens created for remember me authentication
* @return the {@link RememberMeConfigurer} for further customization
*/
public RememberMeConfigurer<H> key(String key) {
this.key = key;
return this;
}
/**
* Allows control over the destination a remembered user is sent to when they are successfully authenticated.
* By default, the filter will just allow the current request to proceed, but if an
* {@code AuthenticationSuccessHandler} is set, it will be invoked and the {@code doFilter()} method will return
* immediately, thus allowing the application to redirect the user to a specific URL, regardless of what the original
* request was for.
*
* @param authenticationSuccessHandler the strategy to invoke immediately before returning from {@code doFilter()}.
* @return {@link RememberMeConfigurer} for further customization
* @see RememberMeAuthenticationFilter#setAuthenticationSuccessHandler(AuthenticationSuccessHandler)
*/
public RememberMeConfigurer<H> authenticationSuccessHandler(AuthenticationSuccessHandler authenticationSuccessHandler) {
this.authenticationSuccessHandler = authenticationSuccessHandler;
return this;
}
/**
* Specify the {@link RememberMeServices} to use.
* @param rememberMeServices the {@link RememberMeServices} to use
* @return the {@link RememberMeConfigurer} for further customizations
* @see RememberMeServices
*/
public RememberMeConfigurer<H> rememberMeServices(RememberMeServices rememberMeServices) {
this.rememberMeServices = rememberMeServices;
return this;
}
@SuppressWarnings("unchecked")
@Override
public void init(H http) throws Exception {
String key = getKey();
RememberMeServices rememberMeServices = getRememberMeServices(http, key);
http.setSharedObject(RememberMeServices.class, rememberMeServices);
LogoutConfigurer<H> logoutConfigurer = http.getConfigurer(LogoutConfigurer.class);
if(logoutConfigurer != null) {
logoutConfigurer.addLogoutHandler(logoutHandler);
}
RememberMeAuthenticationProvider authenticationProvider = new RememberMeAuthenticationProvider(
key);
authenticationProvider = postProcess(authenticationProvider);
http.authenticationProvider(authenticationProvider);
initDefaultLoginFilter(http);
}
@Override
public void configure(H http) throws Exception {
RememberMeAuthenticationFilter rememberMeFilter = new RememberMeAuthenticationFilter(
http.getAuthenticationManager(), rememberMeServices);
if (authenticationSuccessHandler != null) {
rememberMeFilter
.setAuthenticationSuccessHandler(authenticationSuccessHandler);
}
rememberMeFilter = postProcess(rememberMeFilter);
http.addFilter(rememberMeFilter);
}
/**
* Returns the HTTP parameter used to indicate to remember the user at time of login.
* @return the HTTP parameter used to indicate to remember the user
*/
private String getRememberMeParameter() {
return rememberMeParameter;
}
/**
* If available, initializes the {@link DefaultLoginPageViewFilter} shared object.
*
* @param http the {@link HttpSecurityBuilder} to use
*/
private void initDefaultLoginFilter(H http) {
DefaultLoginPageViewFilter loginPageGeneratingFilter = http.getSharedObject(DefaultLoginPageViewFilter.class);
if(loginPageGeneratingFilter != null) {
loginPageGeneratingFilter.setRememberMeParameter(getRememberMeParameter());
}
}
/**
* Gets the {@link RememberMeServices} or creates the {@link RememberMeServices}.
* @param http the {@link HttpSecurity} to lookup shared objects
* @param key the {@link #key(String)}
* @return the {@link RememberMeServices} to use
* @throws Exception
*/
private RememberMeServices getRememberMeServices(H http,
String key) throws Exception {
if (rememberMeServices != null) {
if (rememberMeServices instanceof LogoutHandler
&& logoutHandler == null) {
this.logoutHandler = (LogoutHandler) rememberMeServices;
}
return rememberMeServices;
}
AbstractRememberMeServices tokenRememberMeServices = createRememberMeServices(
http, key);
tokenRememberMeServices.setParameter(rememberMeParameter);
tokenRememberMeServices.setCookieName(rememberMeCookieName);
if (tokenValiditySeconds != null) {
tokenRememberMeServices
.setTokenValiditySeconds(tokenValiditySeconds);
}
if (useSecureCookie != null) {
tokenRememberMeServices.setUseSecureCookie(useSecureCookie);
}
tokenRememberMeServices.afterPropertiesSet();
logoutHandler = tokenRememberMeServices;
rememberMeServices = tokenRememberMeServices;
return tokenRememberMeServices;
}
/**
* Creates the {@link RememberMeServices} to use when none is provided. The
* result is either {@link PersistentTokenRepository} (if a
* {@link PersistentTokenRepository} is specified, else
* {@link TokenBasedRememberMeServices}.
*
* @param http the {@link HttpSecurity} to lookup shared objects
* @param key the {@link #key(String)}
* @return the {@link RememberMeServices} to use
* @throws Exception
*/
private AbstractRememberMeServices createRememberMeServices(
H http, String key) throws Exception {
return tokenRepository == null ? createTokenBasedRememberMeServices(
http, key) : createPersistentRememberMeServices(http, key);
}
/**
* Creates {@link TokenBasedRememberMeServices}
*
* @param http the {@link HttpSecurity} to lookup shared objects
* @param key the {@link #key(String)}
* @return the {@link TokenBasedRememberMeServices}
*/
private AbstractRememberMeServices createTokenBasedRememberMeServices(
H http, String key) {
UserDetailsService userDetailsService = getUserDetailsService(http);
return new TokenBasedRememberMeServices(key, userDetailsService);
}
/**
* Creates {@link PersistentTokenBasedRememberMeServices}
*
* @param http the {@link HttpSecurity} to lookup shared objects
* @param key the {@link #key(String)}
* @return the {@link PersistentTokenBasedRememberMeServices}
*/
private AbstractRememberMeServices createPersistentRememberMeServices(
H http, String key) {
UserDetailsService userDetailsService = getUserDetailsService(http);
return new PersistentTokenBasedRememberMeServices(key,
userDetailsService, tokenRepository);
}
/**
* Gets the {@link UserDetailsService} to use. Either the explicitly
* configure {@link UserDetailsService} from
* {@link #userDetailsService(UserDetailsService)} or a shared object from
* {@link HttpSecurity#getSharedObject(Class)}.
*
* @param http {@link HttpSecurity} to get the shared {@link UserDetailsService}
* @return the {@link UserDetailsService} to use
*/
private UserDetailsService getUserDetailsService(H http) {
if(userDetailsService == null) {
userDetailsService = http.getSharedObject(UserDetailsService.class);
}
if(userDetailsService == null) {
throw new IllegalStateException("userDetailsService cannot be null. Invoke "
+ RememberMeConfigurer.class.getSimpleName() + "#userDetailsService(UserDetailsService) or see its javadoc for alternative approaches.");
}
return userDetailsService;
}
/**
* Gets the key to use for validating remember me tokens. Either the value
* passed into {@link #key(String)}, or a secure random string if none was
* specified.
*
* @return the remember me key to use
*/
private String getKey() {
if (key == null) {
key = UUID.randomUUID().toString();
}
return key;
}
}

View File

@ -0,0 +1,98 @@
/*
* Copyright 2002-2013 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.annotation.web.configurers;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
import org.springframework.security.web.savedrequest.RequestCache;
import org.springframework.security.web.savedrequest.RequestCacheAwareFilter;
/**
* Adds request cache for Spring Security. Specifically this ensures that
* requests that are saved (i.e. after authentication is required) are later
* replayed. All properties have reasonable defaults, so no additional
* configuration is required other than applying this
* {@link org.springframework.security.config.annotation.SecurityConfigurer}.
*
* <h2>Security Filters</h2>
*
* The following Filters are populated
*
* <ul>
* <li>{@link RequestCacheAwareFilter}</li>
* </ul>
*
* <h2>Shared Objects Created</h2>
*
* No shared objects are created.
*
* <h2>Shared Objects Used</h2>
*
* The following shared objects are used:
*
* <ul>
* <li>If no explicit {@link RequestCache}, is provided a {@link RequestCache}
* shared object is used to replay the request after authentication is
* successful</li>
* </ul>
*
* @author Rob Winch
* @since 3.2
* @see RequestCache
*/
public final class RequestCacheConfigurer<H extends HttpSecurityBuilder<H>> extends AbstractHttpConfigurer<H> {
public RequestCacheConfigurer() {
}
/**
* Allows explicit configuration of the {@link RequestCache} to be used. Defaults to try finding a
* {@link RequestCache} as a shared object. Then falls back to a {@link HttpSessionRequestCache}.
*
* @param requestCache the explicit {@link RequestCache} to use
* @return the {@link RequestCacheConfigurer} for further customization
*/
public RequestCacheConfigurer<H> requestCache(RequestCache requestCache) {
getBuilder().setSharedObject(RequestCache.class, requestCache);
return this;
}
@Override
public void configure(H http) throws Exception {
RequestCache requestCache = getRequestCache(http);
RequestCacheAwareFilter requestCacheFilter = new RequestCacheAwareFilter(requestCache);
requestCacheFilter = postProcess(requestCacheFilter);
http.addFilter(requestCacheFilter);
}
/**
* Gets the {@link RequestCache} to use. If one is defined using
* {@link #requestCache(org.springframework.security.web.savedrequest.RequestCache)}, then it is used. Otherwise, an
* attempt to find a {@link RequestCache} shared object is made. If that fails, an {@link HttpSessionRequestCache}
* is used
*
* @param http the {@link HttpSecurity} to attempt to fined the shared object
* @return the {@link RequestCache} to use
*/
private RequestCache getRequestCache(H http) {
RequestCache result = http.getSharedObject(RequestCache.class);
if(result != null) {
return result;
}
return new HttpSessionRequestCache();
}
}

View File

@ -0,0 +1,95 @@
/*
* Copyright 2002-2013 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.annotation.web.configurers;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.context.SecurityContextPersistenceFilter;
import org.springframework.security.web.context.SecurityContextRepository;
/**
* Allows persisting and restoring of the {@link SecurityContext} found on the
* {@link SecurityContextHolder} for each request by configuring the
* {@link SecurityContextPersistenceFilter}. All properties have reasonable
* defaults, so no additional configuration is required other than applying this
* {@link org.springframework.security.config.annotation.SecurityConfigurer}.
*
* <h2>Security Filters</h2>
*
* The following Filters are populated
*
* <ul>
* <li>{@link SecurityContextPersistenceFilter}</li>
* </ul>
*
* <h2>Shared Objects Created</h2>
*
* No shared objects are created.
*
* <h2>Shared Objects Used</h2>
*
* The following shared objects are used:
*
* <ul>
* <li>If {@link SessionManagementConfigurer}, is provided and set to always,
* then the
* {@link SecurityContextPersistenceFilter#setForceEagerSessionCreation(boolean)}
* will be set to true.</li>
* <li>{@link SecurityContextRepository} must be set and is used on
* {@link SecurityContextPersistenceFilter}.</li>
* </ul>
*
* @author Rob Winch
* @since 3.2
*/
public final class SecurityContextConfigurer<H extends HttpSecurityBuilder<H>> extends AbstractHttpConfigurer<H> {
/**
* Creates a new instance
* @see HttpSecurity#securityContext()
*/
public SecurityContextConfigurer() {
}
/**
* Specifies the shared {@link SecurityContextRepository} that is to be used
* @param securityContextRepository the {@link SecurityContextRepository} to use
* @return the {@link HttpSecurity} for further customizations
*/
public SecurityContextConfigurer<H> securityContextRepository(SecurityContextRepository securityContextRepository) {
getBuilder().setSharedObject(SecurityContextRepository.class, securityContextRepository);
return this;
}
@Override
@SuppressWarnings("unchecked")
public void configure(H http) throws Exception {
SecurityContextRepository securityContextRepository = http.getSharedObject(SecurityContextRepository.class);
SecurityContextPersistenceFilter securityContextFilter = new SecurityContextPersistenceFilter(
securityContextRepository);
SessionManagementConfigurer<?> sessionManagement = http.getConfigurer(SessionManagementConfigurer.class);
SessionCreationPolicy sessionCreationPolicy = sessionManagement == null ? null
: sessionManagement.getSessionCreationPolicy();
if (SessionCreationPolicy.always == sessionCreationPolicy) {
securityContextFilter.setForceEagerSessionCreation(true);
}
securityContextFilter = postProcess(securityContextFilter);
http.addFilter(securityContextFilter);
}
}

View File

@ -0,0 +1,75 @@
/*
* Copyright 2002-2013 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.annotation.web.configurers;
import javax.servlet.http.HttpServletRequest;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter;
/**
* Implements select methods from the {@link HttpServletRequest} using the {@link SecurityContext} from the {@link SecurityContextHolder}.
*
* <h2>Security Filters</h2>
*
* The following Filters are populated
*
* <ul>
* <li>{@link SecurityContextHolderAwareRequestFilter}</li>
* </ul>
*
* <h2>Shared Objects Created</h2>
*
* No shared objects are created.
*
* <h2>Shared Objects Used</h2>
*
* No shared Objects are used.
*
* @author Rob Winch
* @since 3.2
*/
public final class ServletApiConfigurer<H extends HttpSecurityBuilder<H>> extends AbstractHttpConfigurer<H> {
private SecurityContextHolderAwareRequestFilter securityContextRequestFilter = new SecurityContextHolderAwareRequestFilter();
/**
* Creates a new instance
* @see HttpSecurity#servletApi()
*/
public ServletApiConfigurer() {
}
public ServletApiConfigurer<H> rolePrefix(String rolePrefix) {
securityContextRequestFilter.setRolePrefix(rolePrefix);
return this;
}
@SuppressWarnings("unchecked")
public H disable() {
getBuilder().removeConfigurer(getClass());
return getBuilder();
}
@Override
public void configure(H builder)
throws Exception {
securityContextRequestFilter = postProcess(securityContextRequestFilter);
builder.addFilter(securityContextRequestFilter);
}
}

View File

@ -0,0 +1,39 @@
/*
* Copyright 2002-2013 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.annotation.web.configurers;
import javax.servlet.http.HttpSession;
import org.springframework.security.core.context.SecurityContext;
/**
* Specifies the various session creation policies for Spring Security.
*
* FIXME this should be removed once {@link org.springframework.security.config.http.SessionCreationPolicy} is made public.
*
* @author Rob Winch
* @since 3.2
*/
public enum SessionCreationPolicy {
/** Always create an {@link HttpSession} */
always,
/** Spring Security will never create an {@link HttpSession}, but will use the {@link HttpSession} if it already exists */
never,
/** Spring Security will only create an {@link HttpSession} if required */
ifRequired,
/** Spring Security will never create an {@link HttpSession} and it will never use it to obtain the {@link SecurityContext} */
stateless
}

View File

@ -0,0 +1,331 @@
/*
* Copyright 2002-2013 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.annotation.web.configurers;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.core.session.SessionRegistryImpl;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.security.web.authentication.session.ConcurrentSessionControlStrategy;
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
import org.springframework.security.web.authentication.session.SessionFixationProtectionStrategy;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
import org.springframework.security.web.context.NullSecurityContextRepository;
import org.springframework.security.web.context.SecurityContextRepository;
import org.springframework.security.web.savedrequest.NullRequestCache;
import org.springframework.security.web.savedrequest.RequestCache;
import org.springframework.security.web.session.ConcurrentSessionFilter;
import org.springframework.security.web.session.SessionManagementFilter;
import org.springframework.security.web.session.SimpleRedirectInvalidSessionStrategy;
import org.springframework.util.Assert;
/**
* Allows configuring session management.
*
* <h2>Security Filters</h2>
*
* The following Filters are populated
*
* <ul>
* <li>{@link SessionManagementFilter}</li>
* <li>{@link ConcurrentSessionFilter} if there are restrictions on how many concurrent sessions a user can have</li>
* </ul>
*
* <h2>Shared Objects Created</h2>
*
* The following shared objects are created:
*
* <ul>
* <li>{@link RequestCache}</li>
* <li>{@link SecurityContextRepository}</li>
* <li>{@link SessionManagementConfigurer}</li>
* </ul>
*
* <h2>Shared Objects Used</h2>
*
* <ul>
* <li>{@link SecurityContextRepository}</li>
* </ul>
*
* @author Rob Winch
* @since 3.2
* @see SessionManagementFilter
* @see ConcurrentSessionFilter
*/
public final class SessionManagementConfigurer<H extends HttpSecurityBuilder<H>> extends AbstractHttpConfigurer<H> {
private SessionAuthenticationStrategy sessionAuthenticationStrategy = new SessionFixationProtectionStrategy();
private SessionRegistry sessionRegistry = new SessionRegistryImpl();
private Integer maximumSessions;
private String expiredUrl;
private boolean maxSessionsPreventsLogin;
private SessionCreationPolicy sessionPolicy = SessionCreationPolicy.ifRequired;
private boolean enableSessionUrlRewriting;
private String invalidSessionUrl;
private String sessionAuthenticationErrorUrl;
/**
* Creates a new instance
* @see HttpSecurity#sessionManagement()
*/
public SessionManagementConfigurer() {
}
/**
* Setting this attribute will inject the {@link SessionManagementFilter} with a
* {@link SimpleRedirectInvalidSessionStrategy} configured with the attribute value.
* When an invalid session ID is submitted, the strategy will be invoked,
* redirecting to the configured URL.
*
* @param invalidSessionUrl the URL to redirect to when an invalid session is detected
* @return the {@link SessionManagementConfigurer} for further customization
*/
public SessionManagementConfigurer<H> invalidSessionUrl(String invalidSessionUrl) {
this.invalidSessionUrl = invalidSessionUrl;
return this;
}
/**
* Defines the URL of the error page which should be shown when the
* SessionAuthenticationStrategy raises an exception. If not set, an
* unauthorized (402) error code will be returned to the client. Note that
* this attribute doesn't apply if the error occurs during a form-based
* login, where the URL for authentication failure will take precedence.
*
* @param sessionAuthenticationErrorUrl
* the URL to redirect to
* @return the {@link SessionManagementConfigurer} for further customization
*/
public SessionManagementConfigurer<H> sessionAuthenticationErrorUrl(String sessionAuthenticationErrorUrl) {
this.sessionAuthenticationErrorUrl = sessionAuthenticationErrorUrl;
return this;
}
/**
* If set to true, allows HTTP sessions to be rewritten in the URLs when
* using {@link HttpServletResponse#encodeRedirectURL(String)} or
* {@link HttpServletResponse#encodeURL(String)}, otherwise disallows HTTP
* sessions to be included in the URL. This prevents leaking information to
* external domains.
*
* @param enableSessionUrlRewriting true if should allow the JSESSIONID to be rewritten into the URLs, else false (default)
* @return the {@link SessionManagementConfigurer} for further customization
* @see HttpSessionSecurityContextRepository#setDisableUrlRewriting(boolean)
*/
public SessionManagementConfigurer<H> enableSessionUrlRewriting(boolean enableSessionUrlRewriting) {
this.enableSessionUrlRewriting = enableSessionUrlRewriting;
return this;
}
/**
* Allows specifying the {@link SessionCreationPolicy}
* @param sessionCreationPolicy the {@link SessionCreationPolicy} to use. Cannot be null.
* @return the {@link SessionManagementConfigurer} for further customizations
* @see SessionCreationPolicy
* @throws IllegalArgumentException if {@link SessionCreationPolicy} is null.
*/
public SessionManagementConfigurer<H> sessionCreationPolicy(SessionCreationPolicy sessionCreationPolicy) {
Assert.notNull(sessionCreationPolicy, "sessionCreationPolicy cannot be null");
this.sessionPolicy = sessionCreationPolicy;
return this;
}
/**
* Allows explicitly specifying the {@link SessionAuthenticationStrategy}.
* The default is to use {@link SessionFixationProtectionStrategy}. If
* restricting the maximum number of sessions is configured,
* {@link ConcurrentSessionControlStrategy} will be used.
*
* @param sessionAuthenticationStrategy
* @return the {@link SessionManagementConfigurer} for further customizations
*/
public SessionManagementConfigurer<H> sessionAuthenticationStrategy(SessionAuthenticationStrategy sessionAuthenticationStrategy) {
this.sessionAuthenticationStrategy = sessionAuthenticationStrategy;
return this;
}
/**
* Controls the maximum number of sessions for a user. The default is to allow any number of users.
* @param maximumSessions the maximum number of sessions for a user
* @return the {@link SessionManagementConfigurer} for further customizations
*/
public ConcurrencyControlConfigurer maximumSessions(int maximumSessions) {
this.maximumSessions = maximumSessions;
this.sessionAuthenticationStrategy = null;
return new ConcurrencyControlConfigurer();
}
/**
* Allows configuring controlling of multiple sessions.
*
* @author Rob Winch
*/
public final class ConcurrencyControlConfigurer {
/**
* The URL to redirect to if a user tries to access a resource and their
* session has been expired due to too many sessions for the current user.
* The default is to write a simple error message to the response.
*
* @param expiredUrl the URL to redirect to
* @return the {@link ConcurrencyControlConfigurer} for further customizations
*/
public ConcurrencyControlConfigurer expiredUrl(String expiredUrl) {
SessionManagementConfigurer.this.expiredUrl = expiredUrl;
return this;
}
/**
* If true, prevents a user from authenticating when the
* {@link #maximumSessions(int)} has been reached. Otherwise (default), the user who
* authenticates is allowed access and an existing user's session is
* expired. The user's who's session is forcibly expired is sent to
* {@link #expiredUrl(String)}. The advantage of this approach is if a user
* accidentally does not log out, there is no need for an administrator to
* intervene or wait till their session expires.
*
* @param maxSessionsPreventsLogin true to have an error at time of authentication, else false (default)
* @return the {@link ConcurrencyControlConfigurer} for further customizations
*/
public ConcurrencyControlConfigurer maxSessionsPreventsLogin(boolean maxSessionsPreventsLogin) {
SessionManagementConfigurer.this.maxSessionsPreventsLogin = maxSessionsPreventsLogin;
return this;
}
/**
* Controls the {@link SessionRegistry} implementation used. The default
* is {@link SessionRegistryImpl} which is an in memory implementation.
*
* @param sessionRegistry the {@link SessionRegistry} to use
* @return the {@link ConcurrencyControlConfigurer} for further customizations
*/
public ConcurrencyControlConfigurer sessionRegistry(SessionRegistry sessionRegistry) {
SessionManagementConfigurer.this.sessionRegistry = sessionRegistry;
return this;
}
/**
* Used to chain back to the {@link SessionManagementConfigurer}
*
* @return the {@link SessionManagementConfigurer} for further customizations
*/
public SessionManagementConfigurer<H> and() {
return SessionManagementConfigurer.this;
}
private ConcurrencyControlConfigurer() {}
}
@Override
public void init(H builder) throws Exception {
SecurityContextRepository securityContextRepository = builder.getSharedObject(SecurityContextRepository.class);
boolean stateless = isStateless();
if(securityContextRepository == null) {
if(stateless) {
builder.setSharedObject(SecurityContextRepository.class, new NullSecurityContextRepository());
} else {
HttpSessionSecurityContextRepository httpSecurityRepository = new HttpSessionSecurityContextRepository();
httpSecurityRepository.setDisableUrlRewriting(!enableSessionUrlRewriting);
httpSecurityRepository.setAllowSessionCreation(isAllowSessionCreation());
builder.setSharedObject(SecurityContextRepository.class, httpSecurityRepository);
}
}
RequestCache requestCache = builder.getSharedObject(RequestCache.class);
if(requestCache == null) {
if(stateless) {
builder.setSharedObject(RequestCache.class, new NullRequestCache());
}
}
builder.setSharedObject(SessionAuthenticationStrategy.class, getSessionAuthenticationStrategy());
}
@Override
public void configure(H http) throws Exception {
SecurityContextRepository securityContextRepository = http.getSharedObject(SecurityContextRepository.class);
SessionManagementFilter sessionManagementFilter = new SessionManagementFilter(securityContextRepository, getSessionAuthenticationStrategy());
if(sessionAuthenticationErrorUrl != null) {
sessionManagementFilter.setAuthenticationFailureHandler(new SimpleUrlAuthenticationFailureHandler(sessionAuthenticationErrorUrl));
}
if(invalidSessionUrl != null) {
sessionManagementFilter.setInvalidSessionStrategy(new SimpleRedirectInvalidSessionStrategy(invalidSessionUrl));
}
sessionManagementFilter = postProcess(sessionManagementFilter);
http.addFilter(sessionManagementFilter);
if(isConcurrentSessionControlEnabled()) {
ConcurrentSessionFilter concurrentSessionFilter = new ConcurrentSessionFilter(sessionRegistry, expiredUrl);
concurrentSessionFilter = postProcess(concurrentSessionFilter);
http.addFilter(concurrentSessionFilter);
}
}
/**
* Gets the {@link SessionCreationPolicy}. Can not be null.
* @return the {@link SessionCreationPolicy}
*/
SessionCreationPolicy getSessionCreationPolicy() {
return sessionPolicy;
}
/**
* Returns true if the {@link SessionCreationPolicy} allows session creation, else false
* @return true if the {@link SessionCreationPolicy} allows session creation
*/
private boolean isAllowSessionCreation() {
return SessionCreationPolicy.always == sessionPolicy || SessionCreationPolicy.ifRequired == sessionPolicy;
}
/**
* Returns true if the {@link SessionCreationPolicy} is stateless
* @return
*/
private boolean isStateless() {
return SessionCreationPolicy.stateless == sessionPolicy;
}
/**
* Gets the customized {@link SessionAuthenticationStrategy} if
* {@link #sessionAuthenticationStrategy(SessionAuthenticationStrategy)} was
* specified. Otherwise creates a default
* {@link SessionAuthenticationStrategy}.
*
* @return the {@link SessionAuthenticationStrategy} to use
*/
private SessionAuthenticationStrategy getSessionAuthenticationStrategy() {
if(sessionAuthenticationStrategy != null) {
return sessionAuthenticationStrategy;
}
if(isConcurrentSessionControlEnabled()) {
ConcurrentSessionControlStrategy concurrentSessionControlStrategy = new ConcurrentSessionControlStrategy(sessionRegistry);
concurrentSessionControlStrategy.setMaximumSessions(maximumSessions);
concurrentSessionControlStrategy.setExceptionIfMaximumExceeded(maxSessionsPreventsLogin);
sessionAuthenticationStrategy = concurrentSessionControlStrategy;
}
return sessionAuthenticationStrategy;
}
/**
* Returns true if the number of concurrent sessions per user should be restricted.
* @return
*/
private boolean isConcurrentSessionControlEnabled() {
return maximumSessions != null;
}
}

View File

@ -0,0 +1,241 @@
/*
* Copyright 2002-2013 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.annotation.web.configurers;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDecisionVoter;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.access.vote.AuthenticatedVoter;
import org.springframework.security.access.vote.RoleVoter;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.web.access.intercept.DefaultFilterInvocationSecurityMetadataSource;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.security.web.util.RequestMatcher;
import org.springframework.util.Assert;
/**
* Adds URL based authorization using {@link DefaultFilterInvocationSecurityMetadataSource}. At least one
* {@link org.springframework.web.bind.annotation.RequestMapping} needs to be mapped to {@link ConfigAttribute}'s for
* this {@link SecurityContextConfigurer} to have meaning.
* <h2>Security Filters</h2>
*
* The following Filters are populated
*
* <ul>
* <li>{@link org.springframework.security.web.access.intercept.FilterSecurityInterceptor}</li>
* </ul>
*
* <h2>Shared Objects Created</h2>
*
* The following shared objects are populated to allow other {@link org.springframework.security.config.annotation.SecurityConfigurer}'s to customize:
* <ul>
* <li>{@link org.springframework.security.web.access.intercept.FilterSecurityInterceptor}</li>
* </ul>
*
* <h2>Shared Objects Used</h2>
*
* The following shared objects are used:
*
* <ul>
* <li>{@link org.springframework.security.config.annotation.web.builders.HttpSecurity#getAuthenticationManager()}</li>
* </ul>
*
* @param <H> the type of {@link HttpSecurityBuilder} that is being configured
* @param <C> the type of object that is being chained
*
* @author Rob Winch
* @since 3.2
* @see ExpressionUrlAuthorizationConfigurer
*/
public final class UrlAuthorizationConfigurer<H extends HttpSecurityBuilder<H>, C> extends AbstractInterceptUrlConfigurer<H,C,UrlAuthorizationConfigurer<H,C>.AuthorizedUrl> {
/**
* Creates the default {@link AccessDecisionVoter} instances used if an
* {@link AccessDecisionManager} was not specified using
* {@link #accessDecisionManager(AccessDecisionManager)}.
*/
@Override
@SuppressWarnings("rawtypes")
final List<AccessDecisionVoter> getDecisionVoters() {
List<AccessDecisionVoter> decisionVoters = new ArrayList<AccessDecisionVoter>();
decisionVoters.add(new RoleVoter());
decisionVoters.add(new AuthenticatedVoter());
return decisionVoters;
}
/**
* Creates the {@link FilterInvocationSecurityMetadataSource} to use. The
* implementation is a {@link DefaultFilterInvocationSecurityMetadataSource}
* .
*/
@Override
FilterInvocationSecurityMetadataSource createMetadataSource() {
return new DefaultFilterInvocationSecurityMetadataSource(createRequestMap());
}
/**
* Chains the {@link RequestMatcher} creation to the {@link AuthorizedUrl} class.
*/
@Override
protected AuthorizedUrl chainRequestMatchersInternal(List<RequestMatcher> requestMatchers) {
return new AuthorizedUrl(requestMatchers);
}
/**
* Adds a mapping of the {@link RequestMatcher} instances to the {@link ConfigAttribute} instances.
* @param requestMatchers the {@link RequestMatcher} instances that should map to the provided {@link ConfigAttribute} instances
* @param configAttributes the {@link ConfigAttribute} instances that should be mapped by the {@link RequestMatcher} instances
* @return the {@link UrlAuthorizationConfigurer} for further customizations
*/
private UrlAuthorizationConfigurer<H,C> addMapping(Iterable<? extends RequestMatcher> requestMatchers, Collection<ConfigAttribute> configAttributes) {
for(RequestMatcher requestMatcher : requestMatchers) {
addMapping(new UrlMapping(requestMatcher, configAttributes));
}
return this;
}
/**
* Creates a String for specifying a user requires a role.
*
* @param role
* the role that should be required which is prepended with ROLE_
* automatically (i.e. USER, ADMIN, etc). It should not start
* with ROLE_
* @return the {@link ConfigAttribute} expressed as a String
*/
private static String hasRole(String role) {
Assert.isTrue(
!role.startsWith("ROLE_"),
role
+ " should not start with ROLE_ since ROLE_ is automatically prepended when using hasRole. Consider using hasAuthority or access instead.");
return "ROLE_" + role;
}
/**
* Creates a String for specifying that a user requires one of many roles.
*
* @param roles
* the roles that the user should have at least one of (i.e.
* ADMIN, USER, etc). Each role should not start with ROLE_ since
* it is automatically prepended already.
* @return the {@link ConfigAttribute} expressed as a String
*/
private static String[] hasAnyRole(String... roles) {
for(int i=0;i<roles.length;i++) {
roles[i] = "ROLE_" + roles[i];
}
return roles;
}
/**
* Creates a String for specifying that a user requires one of many authorities
* @param authorities the authorities that the user should have at least one of (i.e. ROLE_USER, ROLE_ADMIN, etc).
* @return the {@link ConfigAttribute} expressed as a String.
*/
private static String[] hasAnyAuthority(String... authorities) {
return authorities;
}
/**
* Maps the specified {@link RequestMatcher} instances to {@link ConfigAttribute} instances.
*
* @author Rob Winch
* @since 3.2
*/
public final class AuthorizedUrl {
private final List<RequestMatcher> requestMatchers;
/**
* Creates a new instance
* @param requestMatchers the {@link RequestMatcher} instances to map to some {@link ConfigAttribute} instances.
* @see UrlAuthorizationConfigurer#chainRequestMatchers(List)
*/
private AuthorizedUrl(List<RequestMatcher> requestMatchers) {
Assert.notEmpty(requestMatchers, "requestMatchers must contain at least one value");
this.requestMatchers = requestMatchers;
}
/**
* Specifies a user requires a role.
*
* @param role
* the role that should be required which is prepended with ROLE_
* automatically (i.e. USER, ADMIN, etc). It should not start
* with ROLE_
* the {@link UrlAuthorizationConfigurer} for further customization
*/
public UrlAuthorizationConfigurer<H,C> hasRole(String role) {
return access(UrlAuthorizationConfigurer.hasRole(role));
}
/**
* Specifies that a user requires one of many roles.
*
* @param roles
* the roles that the user should have at least one of (i.e.
* ADMIN, USER, etc). Each role should not start with ROLE_ since
* it is automatically prepended already.
* @return the {@link UrlAuthorizationConfigurer} for further customization
*/
public UrlAuthorizationConfigurer<H,C> hasAnyRole(String... roles) {
return access(UrlAuthorizationConfigurer.hasAnyRole(roles));
}
/**
* Specifies a user requires an authority.
*
* @param authority
* the authority that should be required
* @return the {@link UrlAuthorizationConfigurer} for further customization
*/
public UrlAuthorizationConfigurer<H,C> hasAuthority(String authority) {
return access(authority);
}
/**
* Specifies that a user requires one of many authorities
* @param authorities the authorities that the user should have at least one of (i.e. ROLE_USER, ROLE_ADMIN, etc).
* @return the {@link UrlAuthorizationConfigurer} for further customization
*/
public UrlAuthorizationConfigurer<H,C> hasAnyAuthority(String... authorities) {
return access(UrlAuthorizationConfigurer.hasAnyAuthority(authorities));
}
/**
* Specifies that an anonymous user is allowed access
* @return the {@link UrlAuthorizationConfigurer} for further customization
*/
public UrlAuthorizationConfigurer<H,C> anonymous() {
return hasRole("ROLE_ANONYMOUS");
}
/**
* Specifies that the user must have the specified {@link ConfigAttribute}'s
* @param attributes the {@link ConfigAttribute}'s that restrict access to a URL
* @return the {@link UrlAuthorizationConfigurer} for further customization
*/
public UrlAuthorizationConfigurer<H,C> access(String... attributes) {
addMapping(requestMatchers, SecurityConfig.createList(attributes));
return UrlAuthorizationConfigurer.this;
}
}
}

View File

@ -0,0 +1,196 @@
/*
* Copyright 2002-2013 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.annotation.web.configurers;
import javax.servlet.http.HttpServletRequest;
import org.springframework.security.authentication.AuthenticationDetailsSource;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.AuthenticationUserDetailsService;
import org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.Http403ForbiddenEntryPoint;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedGrantedAuthoritiesWebAuthenticationDetails;
import org.springframework.security.web.authentication.preauth.x509.SubjectDnX509PrincipalExtractor;
import org.springframework.security.web.authentication.preauth.x509.X509AuthenticationFilter;
/**
* Adds X509 based pre authentication to an application. Since validating the
* certificate happens when the client connects, the requesting and validation
* of the client certificate should be performed by the container. Spring Security
* will then use the certificate to look up the {@link Authentication} for the user.
*
* <h2>Security Filters</h2>
*
* The following Filters are populated
*
* <ul>
* <li>{@link X509AuthenticationFilter}</li>
* </ul>
*
* <h2>Shared Objects Created</h2>
*
* The following shared objects are created
*
* <ul>
* <li>
* {@link AuthenticationEntryPoint}
* is populated with an {@link Http403ForbiddenEntryPoint}</li>
* <li>A {@link PreAuthenticatedAuthenticationProvider} is populated into
* {@link HttpSecurity#authenticationProvider(org.springframework.security.authentication.AuthenticationProvider)}
* </li>
* </ul>
*
* <h2>Shared Objects Used</h2>
*
* The following shared objects are used:
*
* <ul>
* <li>A {@link UserDetailsService} shared object is used if no {@link AuthenticationUserDetailsService} is specified</li>
* </ul>
*
* @author Rob Winch
* @since 3.2
*/
public final class X509Configurer<H extends HttpSecurityBuilder<H>> extends AbstractHttpConfigurer<H> {
private X509AuthenticationFilter x509AuthenticationFilter;
private AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken> authenticationUserDetailsService;
private String subjectPrincipalRegex;
private AuthenticationDetailsSource<HttpServletRequest, PreAuthenticatedGrantedAuthoritiesWebAuthenticationDetails> authenticationDetailsSource;
/**
* Creates a new instance
* @see HttpSecurity#x509()
*/
public X509Configurer() {
}
/**
* Allows specifying the entire {@link X509AuthenticationFilter}. If this is
* specified, the properties on {@link X509Configurer} will not be
* populated on the {@link X509AuthenticationFilter}.
*
* @param x509AuthenticationFilter the {@link X509AuthenticationFilter} to use
* @return the {@link X509Configurer} for further customizations
*/
public X509Configurer<H> x509AuthenticationFilter(
X509AuthenticationFilter x509AuthenticationFilter) {
this.x509AuthenticationFilter = x509AuthenticationFilter;
return this;
}
/**
* Specifies the {@link AuthenticationDetailsSource}
*
* @param authenticationDetailsSource the {@link AuthenticationDetailsSource} to use
* @return the {@link X509Configurer} to use
*/
public X509Configurer<H> authenticationDetailsSource(AuthenticationDetailsSource<HttpServletRequest, PreAuthenticatedGrantedAuthoritiesWebAuthenticationDetails> authenticationDetailsSource) {
this.authenticationDetailsSource = authenticationDetailsSource;
return this;
}
/**
* Shortcut for invoking {@link #authenticationUserDetailsService(AuthenticationUserDetailsService)} with a {@link UserDetailsByNameServiceWrapper}.
*
* @param userDetailsService the {@link UserDetailsService} to use
* @return the {@link X509Configurer} for further customizations
*/
public X509Configurer<H> userDetailsService(
UserDetailsService userDetailsService) {
UserDetailsByNameServiceWrapper<PreAuthenticatedAuthenticationToken> authenticationUserDetailsService = new UserDetailsByNameServiceWrapper<PreAuthenticatedAuthenticationToken>();
authenticationUserDetailsService.setUserDetailsService(userDetailsService);
return authenticationUserDetailsService(authenticationUserDetailsService);
}
/**
* Specifies the {@link AuthenticationUserDetailsService} to use. If not
* specified, the shared {@link UserDetailsService} will be used to create a
* {@link UserDetailsByNameServiceWrapper}.
*
* @param authenticationUserDetailsService the {@link AuthenticationUserDetailsService} to use
* @return the {@link X509Configurer} for further customizations
*/
public X509Configurer<H> authenticationUserDetailsService(
AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken> authenticationUserDetailsService) {
this.authenticationUserDetailsService = authenticationUserDetailsService;
return this;
}
/**
* Specifies the regex to extract the principal from the certificate. If not
* specified, the default expression from
* {@link SubjectDnX509PrincipalExtractor} is used.
*
* @param subjectPrincipalRegex
* the regex to extract the user principal from the certificate
* (i.e. "CN=(.*?)(?:,|$)").
* @return the {@link X509Configurer} for further customizations
*/
public X509Configurer<H> subjectPrincipalRegex(String subjectPrincipalRegex) {
this.subjectPrincipalRegex = subjectPrincipalRegex;
return this;
}
@Override
public void init(H http) throws Exception {
PreAuthenticatedAuthenticationProvider authenticationProvider = new PreAuthenticatedAuthenticationProvider();
authenticationProvider.setPreAuthenticatedUserDetailsService(getAuthenticationUserDetailsService(http));
http
.authenticationProvider(authenticationProvider)
.setSharedObject(AuthenticationEntryPoint.class,new Http403ForbiddenEntryPoint());
}
@Override
public void configure(H http) throws Exception {
X509AuthenticationFilter filter = getFilter(http.getAuthenticationManager());
http.addFilter(filter);
}
private X509AuthenticationFilter getFilter(
AuthenticationManager authenticationManager) {
if (x509AuthenticationFilter == null) {
x509AuthenticationFilter = new X509AuthenticationFilter();
x509AuthenticationFilter.setAuthenticationManager(authenticationManager);
if(subjectPrincipalRegex != null) {
SubjectDnX509PrincipalExtractor principalExtractor = new SubjectDnX509PrincipalExtractor();
principalExtractor.setSubjectDnRegex(subjectPrincipalRegex);
x509AuthenticationFilter.setPrincipalExtractor(principalExtractor);
}
if(authenticationDetailsSource != null) {
x509AuthenticationFilter.setAuthenticationDetailsSource(authenticationDetailsSource);
}
x509AuthenticationFilter = postProcess(x509AuthenticationFilter);
}
return x509AuthenticationFilter;
}
private AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken> getAuthenticationUserDetailsService(H http) {
if(authenticationUserDetailsService == null) {
userDetailsService(http.getSharedObject(UserDetailsService.class));
}
return authenticationUserDetailsService;
}
}

View File

@ -0,0 +1,463 @@
/*
* Copyright 2002-2013 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.annotation.web.configurers.openid;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.openid4java.consumer.ConsumerException;
import org.openid4java.consumer.ConsumerManager;
import org.springframework.security.authentication.AuthenticationDetailsSource;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configurers.AbstractAuthenticationFilterConfigurer;
import org.springframework.security.config.annotation.web.configurers.FormLoginConfigurer;
import org.springframework.security.config.annotation.web.configurers.RememberMeConfigurer;
import org.springframework.security.config.annotation.web.configurers.SessionManagementConfigurer;
import org.springframework.security.config.annotation.web.configurers.openid.OpenIDLoginConfigurer;
import org.springframework.security.core.userdetails.AuthenticationUserDetailsService;
import org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.openid.AxFetchListFactory;
import org.springframework.security.openid.OpenID4JavaConsumer;
import org.springframework.security.openid.OpenIDAttribute;
import org.springframework.security.openid.OpenIDAuthenticationFilter;
import org.springframework.security.openid.OpenIDAuthenticationProvider;
import org.springframework.security.openid.OpenIDAuthenticationToken;
import org.springframework.security.openid.OpenIDConsumer;
import org.springframework.security.openid.RegexBasedAxFetchListFactory;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
import org.springframework.security.web.authentication.RememberMeServices;
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
import org.springframework.security.web.authentication.ui.DefaultLoginPageViewFilter;
/**
* Adds support for OpenID based authentication.
*
* <h2>Example Configuration</h2>
*
* <pre>
*
* &#064;Configuration
* &#064;EnableWebSecurity
* public class OpenIDLoginConfig extends WebSecurityConfigurerAdapter {
*
* &#064;Override
* protected void configure(HttpSecurity http) {
* http
* .authorizeUrls()
* .antMatchers(&quot;/**&quot;).hasRole(&quot;USER&quot;)
* .and()
* .openidLogin()
* .permitAll();
* }
*
* &#064;Override
* protected void registerAuthentication(
* AuthenticationManagerBuilder auth) throws Exception {
* auth
* .inMemoryAuthentication()
* .withUser(&quot;https://www.google.com/accounts/o8/id?id=lmkCn9xzPdsxVwG7pjYMuDgNNdASFmobNkcRPaWU&quot;)
* .password(&quot;password&quot;)
* .roles(&quot;USER&quot;);
* }
* }
* </pre>
*
* <h2>Security Filters</h2>
*
* The following Filters are populated
*
* <ul>
* <li>
* {@link OpenIDAuthenticationFilter}</li>
* </ul>
*
* <h2>Shared Objects Created</h2>
*
* <ul>
* <li>
* {@link AuthenticationEntryPoint}
* is populated with a {@link LoginUrlAuthenticationEntryPoint}</li>
* <li>A {@link OpenIDAuthenticationProvider} is populated into
* {@link HttpSecurity#authenticationProvider(org.springframework.security.authentication.AuthenticationProvider)}
* </li>
* </ul>
*
* <h2>Shared Objects Used</h2>
*
* The following shared objects are used:
*
* <ul>
* <li>{@link HttpSecurity#getAuthenticationManager()}</li>
* <li>{@link RememberMeServices} - is optionally used. See
* {@link RememberMeConfigurer}</li>
* <li>{@link SessionAuthenticationStrategy} - is optionally used. See
* {@link SessionManagementConfigurer}</li>
* </ul>
*
* @author Rob Winch
* @since 3.2
*/
public final class OpenIDLoginConfigurer<H extends HttpSecurityBuilder<H>> extends AbstractAuthenticationFilterConfigurer<H,OpenIDLoginConfigurer<H>,OpenIDAuthenticationFilter> {
private OpenIDConsumer openIDConsumer;
private ConsumerManager consumerManager;
private AuthenticationUserDetailsService<OpenIDAuthenticationToken> authenticationUserDetailsService;
private List<AttributeExchangeConfigurer> attributeExchangeConfigurers = new ArrayList<AttributeExchangeConfigurer>();
/**
* Creates a new instance
*/
public OpenIDLoginConfigurer() {
super(new OpenIDAuthenticationFilter(),"/login/openid");
}
/**
* Sets up OpenID attribute exchange for OpenID's matching the specified
* pattern.
*
* @param identifierPattern
* the regular expression for matching on OpenID's (i.e.
* "https://www.google.com/.*", ".*yahoo.com.*", etc)
* @return a {@link AttributeExchangeConfigurer} for further customizations of the attribute exchange
*/
public AttributeExchangeConfigurer attributeExchange(String identifierPattern) {
AttributeExchangeConfigurer attributeExchangeConfigurer = new AttributeExchangeConfigurer(identifierPattern);
this.attributeExchangeConfigurers .add(attributeExchangeConfigurer);
return attributeExchangeConfigurer;
}
/**
* Allows specifying the {@link OpenIDConsumer} to be used. The default is
* using an {@link OpenID4JavaConsumer}.
*
* @param consumer
* the {@link OpenIDConsumer} to be used
* @return the {@link OpenIDLoginConfigurer} for further customizations
*/
public OpenIDLoginConfigurer<H> consumer(OpenIDConsumer consumer) {
this.openIDConsumer = consumer;
return this;
}
/**
* Allows specifying the {@link ConsumerManager} to be used. If specified,
* will be populated into an {@link OpenID4JavaConsumer}.
*
* <p>
* This is a shortcut for specifying the {@link OpenID4JavaConsumer} with a
* specific {@link ConsumerManager} on {@link #consumer(OpenIDConsumer)}.
* </p>
*
* @param consumerManager the {@link ConsumerManager} to use. Cannot be null.
* @return the {@link OpenIDLoginConfigurer} for further customizations
*/
public OpenIDLoginConfigurer<H> consumerManager(ConsumerManager consumerManager) {
this.consumerManager = consumerManager;
return this;
}
/**
* The {@link AuthenticationUserDetailsService} to use. By default a
* {@link UserDetailsByNameServiceWrapper} is used with the
* {@link UserDetailsService} shared object found with
* {@link HttpSecurity#getSharedObject(Class)}.
*
* @param authenticationUserDetailsService the {@link AuthenticationDetailsSource} to use
* @return the {@link OpenIDLoginConfigurer} for further customizations
*/
public OpenIDLoginConfigurer<H> authenticationUserDetailsService(AuthenticationUserDetailsService<OpenIDAuthenticationToken> authenticationUserDetailsService) {
this.authenticationUserDetailsService = authenticationUserDetailsService;
return this;
}
/**
* Specifies the URL used to authenticate OpenID requests. If the {@link HttpServletRequest}
* matches this URL the {@link OpenIDAuthenticationFilter} will attempt to
* authenticate the request. The default is "/login/openid".
*
* @param loginUrl
* the URL used to perform authentication
* @return the {@link OpenIDLoginConfigurer} for additional customization
*/
public OpenIDLoginConfigurer<H> loginProcessingUrl(String loginProcessingUrl) {
return super.loginProcessingUrl(loginProcessingUrl);
}
/**
* <p>
* Specifies the URL to send users to if login is required. If used with
* {@link WebSecurityConfigurerAdapter} a default login page will be
* generated when this attribute is not specified.
* </p>
*
* <p>
* If a URL is specified or this is not being used in conjuction with
* {@link WebSecurityConfigurerAdapter}, users are required to process the
* specified URL to generate a login page.
* </p>
*
* <ul>
* <li>It must be an HTTP POST</li>
* <li>It must be submitted to {@link #loginProcessingUrl(String)}</li>
* <li>It should include the OpenID as an HTTP parameter by the name of
* {@link OpenIDAuthenticationFilter#DEFAULT_CLAIMED_IDENTITY_FIELD}</li>
* </ul>
*
* @param loginPage the login page to redirect to if authentication is required (i.e. "/login")
* @return the {@link FormLoginConfigurer} for additional customization
*/
public OpenIDLoginConfigurer<H> loginPage(String loginPage) {
return super.loginPage(loginPage);
}
@Override
public void init(H http) throws Exception {
super.init(http);
OpenIDAuthenticationProvider authenticationProvider = new OpenIDAuthenticationProvider();
authenticationProvider.setAuthenticationUserDetailsService(getAuthenticationUserDetailsService(http));
authenticationProvider = postProcess(authenticationProvider);
http.authenticationProvider(authenticationProvider);
initDefaultLoginFilter(http);
}
@Override
public void configure(H http) throws Exception {
getAuthenticationFilter().setConsumer(getConsumer());
super.configure(http);
}
/**
* Gets the {@link OpenIDConsumer} that was configured or defaults to an {@link OpenID4JavaConsumer}.
* @return the {@link OpenIDConsumer} to use
* @throws ConsumerException
*/
private OpenIDConsumer getConsumer() throws ConsumerException {
if(openIDConsumer == null) {
openIDConsumer = new OpenID4JavaConsumer(getConsumerManager(), attributesToFetchFactory());
}
return openIDConsumer;
}
/**
* Gets the {@link ConsumerManager} that was configured or defaults to using a {@link ConsumerManager} with the default constructor.
* @return the {@link ConsumerManager} to use
*/
private ConsumerManager getConsumerManager() {
if(this.consumerManager != null) {
return this.consumerManager;
}
return new ConsumerManager();
}
/**
* Creates an {@link RegexBasedAxFetchListFactory} using the attributes
* populated by {@link AttributeExchangeConfigurer}
*
* @return the {@link AxFetchListFactory} to use
*/
private AxFetchListFactory attributesToFetchFactory() {
Map<String,List<OpenIDAttribute>> identityToAttrs = new HashMap<String,List<OpenIDAttribute>>();
for(AttributeExchangeConfigurer conf : attributeExchangeConfigurers) {
identityToAttrs.put(conf.identifier, conf.getAttributes());
}
return new RegexBasedAxFetchListFactory(identityToAttrs);
}
/**
* Gets the {@link AuthenticationUserDetailsService} that was configured or
* defaults to {@link UserDetailsByNameServiceWrapper} that uses a
* {@link UserDetailsService} looked up using
* {@link HttpSecurity#getSharedObject(Class)}
*
* @param http the current {@link HttpSecurity}
* @return the {@link AuthenticationUserDetailsService}.
*/
private AuthenticationUserDetailsService<OpenIDAuthenticationToken> getAuthenticationUserDetailsService(
H http) {
if(authenticationUserDetailsService != null) {
return authenticationUserDetailsService;
}
return new UserDetailsByNameServiceWrapper<OpenIDAuthenticationToken>(http.getSharedObject(UserDetailsService.class));
}
/**
* If available, initializes the {@link DefaultLoginPageViewFilter} shared object.
*
* @param http the {@link HttpSecurityBuilder} to use
*/
private void initDefaultLoginFilter(H http) {
DefaultLoginPageViewFilter loginPageGeneratingFilter = http.getSharedObject(DefaultLoginPageViewFilter.class);
if(loginPageGeneratingFilter != null && !isCustomLoginPage()) {
loginPageGeneratingFilter.setOpenIdEnabled(true);
loginPageGeneratingFilter.setOpenIDauthenticationUrl(getLoginProcessingUrl());
String loginPageUrl = loginPageGeneratingFilter.getLoginPageUrl();
if(loginPageUrl == null) {
loginPageGeneratingFilter.setLoginPageUrl(getLoginPage());
loginPageGeneratingFilter.setFailureUrl(getFailureUrl());
}
loginPageGeneratingFilter.setOpenIDusernameParameter(OpenIDAuthenticationFilter.DEFAULT_CLAIMED_IDENTITY_FIELD);
}
}
/**
* A class used to add OpenID attributes to look up
*
* @author Rob Winch
*/
public final class AttributeExchangeConfigurer {
private final String identifier;
private List<OpenIDAttribute> attributes = new ArrayList<OpenIDAttribute>();
private List<AttributeConfigurer> attributeConfigurers = new ArrayList<AttributeConfigurer>();
/**
* Creates a new instance
* @param identifierPattern the pattern that attempts to match on the OpenID
* @see OpenIDLoginConfigurer#attributeExchange(String)
*/
private AttributeExchangeConfigurer(String identifierPattern) {
this.identifier = identifierPattern;
}
/**
* Get the {@link OpenIDLoginConfigurer} to customize the OpenID configuration further
* @return the {@link OpenIDLoginConfigurer}
*/
public OpenIDLoginConfigurer<H> and() {
return OpenIDLoginConfigurer.this;
}
/**
* Adds an {@link OpenIDAttribute} to be obtained for the configured OpenID pattern.
* @param attribute the {@link OpenIDAttribute} to obtain
* @return the {@link AttributeExchangeConfigurer} for further customization of attribute exchange
*/
public AttributeExchangeConfigurer attribute(OpenIDAttribute attribute) {
this.attributes.add(attribute);
return this;
}
/**
* Adds an {@link OpenIDAttribute} with the given name
* @param name the name of the {@link OpenIDAttribute} to create
* @return an {@link AttributeConfigurer} to further configure the {@link OpenIDAttribute} that should be obtained.
*/
public AttributeConfigurer attribute(String name) {
AttributeConfigurer attributeConfigurer = new AttributeConfigurer(name);
this.attributeConfigurers.add(attributeConfigurer);
return attributeConfigurer;
}
/**
* Gets the {@link OpenIDAttribute}'s for the configured OpenID pattern
* @return
*/
private List<OpenIDAttribute> getAttributes() {
for(AttributeConfigurer config : attributeConfigurers) {
attributes.add(config.build());
}
attributeConfigurers.clear();
return attributes;
}
/**
* Configures an {@link OpenIDAttribute}
*
* @author Rob Winch
* @since 3.2
*/
public final class AttributeConfigurer {
private String name;
private int count = 1;
private boolean required = false;
private String type;
/**
* Creates a new instance
* @param name the name of the attribute
* @see AttributeExchangeConfigurer#attribute(String)
*/
private AttributeConfigurer(String name) {
this.name = name;
}
/**
* Specifies the number of attribute values to request. Default is 1.
* @param count the number of attributes to request.
* @return the {@link AttributeConfigurer} for further customization
*/
public AttributeConfigurer count(int count) {
this.count = count;
return this;
}
/**
* Specifies that this attribute is required. The default is
* <code>false</code>. Note that as outlined in the OpenID
* specification, required attributes are not validated by the
* OpenID Provider. Developers should perform any validation in
* custom code.
*
* @param required specifies the attribute is required
* @return the {@link AttributeConfigurer} for further customization
*/
public AttributeConfigurer required(boolean required) {
this.required = required;
return this;
}
/**
* The OpenID attribute type.
* @param type
* @return
*/
public AttributeConfigurer type(String type) {
this.type = type;
return this;
}
/**
* Gets the {@link AttributeExchangeConfigurer} for further
* customization of the attributes
*
* @return the {@link AttributeConfigurer}
*/
public AttributeExchangeConfigurer and() {
return AttributeExchangeConfigurer.this;
}
/**
* Builds the {@link OpenIDAttribute}.
* @return
*/
private OpenIDAttribute build() {
OpenIDAttribute attribute = new OpenIDAttribute(name, type);
attribute.setCount(count);
attribute.setRequired(required);
return attribute;
}
}
}
}

View File

@ -0,0 +1,25 @@
/*
* Copyright 2002-2013 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.annotation;
/**
* Exists for mocking purposes to ensure that the Type information is found.
*
* @author Rob Winch
*/
public interface AnyObjectPostProcessor extends ObjectPostProcessor<Object> {
}

View File

@ -0,0 +1,122 @@
/*
* Copyright 2002-2013 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.annotation;
import javax.servlet.Filter
import org.springframework.beans.factory.NoSuchBeanDefinitionException
import org.springframework.context.ConfigurableApplicationContext
import org.springframework.context.annotation.AnnotationConfigApplicationContext
import org.springframework.mock.web.MockFilterChain
import org.springframework.mock.web.MockHttpServletRequest
import org.springframework.mock.web.MockHttpServletResponse
import org.springframework.security.authentication.AuthenticationManager
import org.springframework.security.authentication.AuthenticationProvider
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder
import org.springframework.security.core.Authentication
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContextHolder
import org.springframework.security.core.context.SecurityContextImpl
import org.springframework.security.web.FilterChainProxy
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor
import org.springframework.security.web.context.HttpRequestResponseHolder
import org.springframework.security.web.context.HttpSessionSecurityContextRepository
import spock.lang.AutoCleanup
import spock.lang.Specification
/**
*
* @author Rob Winch
*/
abstract class BaseSpringSpec extends Specification {
@AutoCleanup
ConfigurableApplicationContext context
MockHttpServletRequest request
MockHttpServletResponse response
MockFilterChain chain
def setup() {
request = new MockHttpServletRequest(method:"GET")
response = new MockHttpServletResponse()
chain = new MockFilterChain()
}
AuthenticationManagerBuilder authenticationBldr = new AuthenticationManagerBuilder().inMemoryAuthentication().and()
def cleanup() {
SecurityContextHolder.clearContext()
}
def loadConfig(Class<?>... configs) {
context = new AnnotationConfigApplicationContext(configs)
context
}
def findFilter(Class<?> filter, int index = 0) {
filterChain(index).filters.find { filter.isAssignableFrom(it.class)}
}
def filterChain(int index=0) {
filterChains()[index]
}
def filterChains() {
context.getBean(FilterChainProxy).filterChains
}
Filter getSpringSecurityFilterChain() {
context.getBean("springSecurityFilterChain",Filter.class)
}
AuthenticationManager authenticationManager() {
context.getBean(AuthenticationManager)
}
AuthenticationManager getAuthenticationManager() {
try {
authenticationManager().delegateBuilder.getObject()
} catch(NoSuchBeanDefinitionException e) {}
findFilter(FilterSecurityInterceptor).authenticationManager
}
List<AuthenticationProvider> authenticationProviders() {
List<AuthenticationProvider> providers = new ArrayList<AuthenticationProvider>()
AuthenticationManager authenticationManager = getAuthenticationManager()
while(authenticationManager?.providers) {
providers.addAll(authenticationManager.providers)
authenticationManager = authenticationManager.parent
}
providers
}
AuthenticationProvider findAuthenticationProvider(Class<?> provider) {
authenticationProviders().find { provider.isAssignableFrom(it.class) }
}
def login(String username="user", String role="ROLE_USER") {
login(new UsernamePasswordAuthenticationToken(username, null, AuthorityUtils.createAuthorityList(role)))
}
def login(Authentication auth) {
HttpSessionSecurityContextRepository repo = new HttpSessionSecurityContextRepository()
HttpRequestResponseHolder requestResponseHolder = new HttpRequestResponseHolder(request, response)
repo.loadContext(requestResponseHolder)
repo.saveContext(new SecurityContextImpl(authentication:auth), requestResponseHolder.request, requestResponseHolder.response)
}
}

View File

@ -0,0 +1,52 @@
/*
* Copyright 2002-2013 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.annotation;
import org.springframework.context.ConfigurableApplicationContext
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.mock.web.MockFilterChain;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.security.core.context.SecurityContextHolder
import org.springframework.security.web.FilterChainProxy;
import spock.lang.AutoCleanup
import spock.lang.Specification
/**
*
* @author Rob Winch
*/
abstract class BaseWebSpecuritySpec extends BaseSpringSpec {
FilterChainProxy springSecurityFilterChain
MockHttpServletRequest request
MockHttpServletResponse response
MockFilterChain chain
def setup() {
request = new MockHttpServletRequest()
response = new MockHttpServletResponse()
chain = new MockFilterChain()
}
def loadConfig(Class<?>... configs) {
super.loadConfig(configs)
springSecurityFilterChain = context.getBean(FilterChainProxy)
}
}

View File

@ -0,0 +1,37 @@
/*
* Copyright 2002-2013 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.annotation;
import java.util.ArrayList;
import java.util.List;
/**
* @author Rob Winch
*
*/
class ConcereteSecurityConfigurerAdapter extends SecurityConfigurerAdapter<Object, SecurityBuilder<Object>> {
private List<Object> list = new ArrayList<Object>();
@Override
public void configure(SecurityBuilder<Object> builder) throws Exception {
list = postProcess(list);
}
public ConcereteSecurityConfigurerAdapter list(List<Object> l) {
this.list = l;
return this;
}
}

View File

@ -0,0 +1,50 @@
/*
* Copyright 2002-2013 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.annotation;
import static org.fest.assertions.Assertions.assertThat;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import org.junit.Test;
/**
* @author Rob Winch
*
*/
public class ObjectPostProcessorTests {
@Test
public void convertTypes() {
assertThat((Object)PerformConversion.perform(new ArrayList<Object>())).isInstanceOf(LinkedList.class);
}
}
class ListToLinkedListObjectPostProcessor implements ObjectPostProcessor<List<?>>{
@Override
public <O extends List<?>> O postProcess(O l) {
return (O) new LinkedList(l);
}
}
class PerformConversion {
public static List<?> perform(ArrayList<?> l) {
return new ListToLinkedListObjectPostProcessor().postProcess(l);
}
}

View File

@ -0,0 +1,40 @@
/*
* Copyright 2002-2013 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.annotation
import spock.lang.Specification
/**
* @author Rob Winch
*
*/
class SecurityConfigurerAdapterTests extends Specification {
ConcereteSecurityConfigurerAdapter conf = new ConcereteSecurityConfigurerAdapter()
def "addPostProcessor closure"() {
setup:
SecurityBuilder<Object> builder = Mock()
conf.addObjectPostProcessor({ List l ->
l.add("a")
l
} as ObjectPostProcessor<List>)
when:
conf.init(builder)
conf.configure(builder)
then:
conf.list.contains("a")
}
}

View File

@ -0,0 +1,90 @@
/*
* Copyright 2002-2013 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.annotation.authentication
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationEventPublisher
import org.springframework.security.authentication.AuthenticationManager
import org.springframework.security.authentication.AuthenticationProvider
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.config.annotation.BaseSpringSpec
import org.springframework.security.config.annotation.ObjectPostProcessor
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.Authentication;
/**
*
* @author Rob Winch
*
*/
class AuthenticationManagerBuilderTests extends BaseSpringSpec {
def "add(AuthenticationProvider) does not perform registration"() {
setup:
ObjectPostProcessor opp = Mock()
AuthenticationProvider provider = Mock()
AuthenticationManagerBuilder builder = new AuthenticationManagerBuilder().objectPostProcessor(opp)
when: "Adding an AuthenticationProvider"
builder.authenticationProvider(provider)
builder.build()
then: "AuthenticationProvider is not passed into LifecycleManager (it should be managed externally)"
0 * opp._(_ as AuthenticationProvider)
}
// https://github.com/SpringSource/spring-security-javaconfig/issues/132
def "#132 Custom AuthenticationEventPublisher with Web registerAuthentication"() {
setup:
AuthenticationEventPublisher aep = Mock()
when:
AuthenticationManager am = new AuthenticationManagerBuilder()
.authenticationEventPublisher(aep)
.inMemoryAuthentication()
.and()
.build()
then:
am.eventPublisher == aep
}
def "authentication-manager support multiple DaoAuthenticationProvider's"() {
setup:
loadConfig(MultiAuthenticationProvidersConfig)
when:
Authentication auth = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken("user","password"))
then:
auth.name == "user"
auth.authorities*.authority == ['ROLE_USER']
when:
auth = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken("admin","password"))
then:
auth.name == "admin"
auth.authorities*.authority.sort() == ['ROLE_ADMIN','ROLE_USER']
}
@EnableWebSecurity
@Configuration
static class MultiAuthenticationProvidersConfig extends WebSecurityConfigurerAdapter {
protected void registerAuthentication(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser("user").password("password").roles("USER").and()
.and()
.inMemoryAuthentication()
.withUser("admin").password("password").roles("USER","ADMIN")
}
}
}

View File

@ -0,0 +1,50 @@
/*
* Copyright 2002-2013 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.annotation.authentication
import java.rmi.registry.Registry;
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.authentication.AuthenticationManager
import org.springframework.security.config.annotation.authentication.AuthenticationManagerBuilder
import org.springframework.security.config.annotation.authentication.configurers.userdetails.UserDetailsServiceConfigurer;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.authentication.configurers.provisioning.InMemoryUserDetailsManagerConfigurer;
import org.springframework.security.core.userdetails.UserDetailsService;
/**
*
* @author Rob Winch
*/
@Configuration
class BaseAuthenticationConfig {
protected void registerAuthentication(
AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser("user").password("password").roles("USER").and()
.withUser("admin").password("password").roles("USER", "ADMIN").and()
}
@Bean
public AuthenticationManager authenticationManager() {
AuthenticationManagerBuilder registry = new AuthenticationManagerBuilder();
registerAuthentication(registry);
return registry.build();
}
}

View File

@ -0,0 +1,92 @@
/*
* Copyright 2002-2013 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.annotation.authentication
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.authentication.AuthenticationManager
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
import org.springframework.security.config.annotation.BaseSpringSpec
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.Authentication
/**
*
* @author Rob Winch
*
*/
class NamespaceAuthenticationManagerTests extends BaseSpringSpec {
def "authentication-manager@erase-credentials=true (default)"() {
when:
loadConfig(EraseCredentialsTrueDefaultConfig)
Authentication auth = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken("user","password"))
then:
auth.principal.password == null
auth.credentials == null
when: "authenticate the same user"
auth = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken("user","password"))
then: "successfully authenticate again"
noExceptionThrown()
}
@EnableWebSecurity
@Configuration
static class EraseCredentialsTrueDefaultConfig extends WebSecurityConfigurerAdapter {
protected void registerAuthentication(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser("user").password("password").roles("USER")
}
// Only necessary to have access to verify the AuthenticationManager
@Bean
@Override
public AuthenticationManager authenticationManagerBean()
throws Exception {
return super.authenticationManagerBean();
}
}
def "authentication-manager@erase-credentials=false"() {
when:
loadConfig(EraseCredentialsFalseConfig)
Authentication auth = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken("user","password"))
then:
auth.credentials == "password"
auth.principal.password == "password"
}
@EnableWebSecurity
@Configuration
static class EraseCredentialsFalseConfig extends WebSecurityConfigurerAdapter {
protected void registerAuthentication(AuthenticationManagerBuilder auth) throws Exception {
auth
.eraseCredentials(false)
.inMemoryAuthentication()
.withUser("user").password("password").roles("USER")
}
// Only necessary to have access to verify the AuthenticationManager
@Bean
@Override
public AuthenticationManager authenticationManagerBean()
throws Exception {
return super.authenticationManagerBean();
}
}
}

View File

@ -0,0 +1,83 @@
/*
* Copyright 2002-2013 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.annotation.authentication
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.authentication.AuthenticationManager
import org.springframework.security.authentication.dao.DaoAuthenticationProvider
import org.springframework.security.config.annotation.BaseSpringSpec
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.provisioning.InMemoryUserDetailsManager
/**
*
* @author Rob Winch
*
*/
class NamespaceAuthenticationProviderTests extends BaseSpringSpec {
def "authentication-provider@ref"() {
when:
loadConfig(AuthenticationProviderRefConfig)
then:
authenticationProviders()[1] == AuthenticationProviderRefConfig.expected
}
@EnableWebSecurity
@Configuration
static class AuthenticationProviderRefConfig extends WebSecurityConfigurerAdapter {
static DaoAuthenticationProvider expected = new DaoAuthenticationProvider()
protected void registerAuthentication(AuthenticationManagerBuilder auth) throws Exception {
auth
.authenticationProvider(expected)
}
// Only necessary to have access to verify the AuthenticationManager
@Bean
@Override
public AuthenticationManager authenticationManagerBean()
throws Exception {
return super.authenticationManagerBean();
}
}
def "authentication-provider@user-service-ref"() {
when:
loadConfig(UserServiceRefConfig)
then:
findAuthenticationProvider(DaoAuthenticationProvider).userDetailsService == UserServiceRefConfig.expected
}
@EnableWebSecurity
@Configuration
static class UserServiceRefConfig extends WebSecurityConfigurerAdapter {
static InMemoryUserDetailsManager expected = new InMemoryUserDetailsManager([] as Collection)
protected void registerAuthentication(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(expected)
}
// Only necessary to have access to verify the AuthenticationManager
@Bean
@Override
public AuthenticationManager authenticationManagerBean()
throws Exception {
return super.authenticationManagerBean();
}
}
}

View File

@ -0,0 +1,188 @@
/*
* Copyright 2002-2013 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.annotation.authentication
import javax.sql.DataSource
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType
import org.springframework.security.authentication.AuthenticationManager
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
import org.springframework.security.authentication.dao.DaoAuthenticationProvider
import org.springframework.security.config.annotation.BaseSpringSpec
import org.springframework.security.config.annotation.authentication.NamespaceJdbcUserServiceTests.CustomJdbcUserServiceSampleConfig.CustomUserCache;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.Authentication
import org.springframework.security.core.userdetails.UserCache
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.provisioning.JdbcUserDetailsManager
/**
*
* @author Rob Winch
*
*/
class NamespaceJdbcUserServiceTests extends BaseSpringSpec {
def "jdbc-user-service"() {
when:
loadConfig(DataSourceConfig,JdbcUserServiceConfig)
then:
findAuthenticationProvider(DaoAuthenticationProvider).userDetailsService instanceof JdbcUserDetailsManager
}
@EnableWebSecurity
@Configuration
static class JdbcUserServiceConfig extends WebSecurityConfigurerAdapter {
@Autowired
private DataSource dataSource;
protected void registerAuthentication(AuthenticationManagerBuilder auth) throws Exception {
auth
.jdbcAuthentication()
.dataSource(dataSource) // jdbc-user-service@data-source-ref
}
// Only necessary to have access to verify the AuthenticationManager
@Bean
@Override
public AuthenticationManager authenticationManagerBean()
throws Exception {
return super.authenticationManagerBean();
}
}
def "jdbc-user-service in memory testing sample"() {
when:
loadConfig(DataSourceConfig,JdbcUserServiceInMemorySampleConfig)
then:
Authentication auth = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken("user", "password"))
auth.authorities.collect {it.authority} == ['ROLE_USER']
auth.name == "user"
}
@EnableWebSecurity
@Configuration
static class JdbcUserServiceInMemorySampleConfig extends WebSecurityConfigurerAdapter {
@Autowired
private DataSource dataSource;
protected void registerAuthentication(AuthenticationManagerBuilder auth) throws Exception {
auth
.jdbcAuthentication()
.dataSource(dataSource)
// imports the default schema (will fail if already exists)
.withDefaultSchema()
// adds this user automatically (will fail if already exists)
.withUser("user")
.password("password")
.roles("USER")
}
// Only necessary to have access to verify the AuthenticationManager
@Bean
@Override
public AuthenticationManager authenticationManagerBean()
throws Exception {
return super.authenticationManagerBean();
}
}
@Configuration
static class DataSourceConfig {
@Bean
public DataSource dataSource() {
EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder()
return builder.setType(EmbeddedDatabaseType.HSQL).build();
}
}
def "jdbc-user-service custom"() {
when:
loadConfig(CustomDataSourceConfig,CustomJdbcUserServiceSampleConfig)
then:
findAuthenticationProvider(DaoAuthenticationProvider).userDetailsService.userCache instanceof CustomUserCache
when:
Authentication auth = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken("user", "password"))
then:
auth.authorities.collect {it.authority}.sort() == ['ROLE_DBA','ROLE_USER']
auth.name == 'user'
}
@EnableWebSecurity
@Configuration
static class CustomJdbcUserServiceSampleConfig extends WebSecurityConfigurerAdapter {
@Autowired
private DataSource dataSource;
protected void registerAuthentication(AuthenticationManagerBuilder auth) throws Exception {
auth
.jdbcAuthentication()
// jdbc-user-service@dataSource
.dataSource(dataSource)
// jdbc-user-service@cache-ref
.userCache(new CustomUserCache())
// jdbc-user-service@users-byusername-query
.usersByUsernameQuery("select principal,credentials,true from users where principal = ?")
// jdbc-user-service@authorities-by-username-query
.authoritiesByUsernameQuery("select principal,role from roles where principal = ?")
// jdbc-user-service@group-authorities-by-username-query
.groupAuthoritiesByUsername(JdbcUserDetailsManager.DEF_GROUP_AUTHORITIES_BY_USERNAME_QUERY)
// jdbc-user-service@role-prefix
.rolePrefix("ROLE_")
}
// Only necessary to have access to verify the AuthenticationManager
@Bean
@Override
public AuthenticationManager authenticationManagerBean()
throws Exception {
return super.authenticationManagerBean();
}
static class CustomUserCache implements UserCache {
@Override
public UserDetails getUserFromCache(String username) {
return null;
}
@Override
public void putUserInCache(UserDetails user) {
}
@Override
public void removeUserFromCache(String username) {
}
}
}
@Configuration
static class CustomDataSourceConfig {
@Bean
public DataSource dataSource() {
EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder()
// simulate that the DB already has the schema loaded and users in it
.addScript("CustomJdbcUserServiceSampleConfig.sql")
return builder.setType(EmbeddedDatabaseType.HSQL).build();
}
}
}

View File

@ -0,0 +1,126 @@
/*
* Copyright 2002-2013 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.annotation.authentication
import static org.springframework.security.config.annotation.authentication.PasswordEncoderConfigurerConfigs.*
import javax.sql.DataSource
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType
import org.springframework.ldap.core.support.BaseLdapPathContextSource;
import org.springframework.security.authentication.AuthenticationManager
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
import org.springframework.security.config.annotation.BaseSpringSpec
import org.springframework.security.config.annotation.authentication.configurers.ldap.LdapAuthenticationProviderConfigurer;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.authority.AuthorityUtils
import org.springframework.security.core.userdetails.User
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
import org.springframework.security.ldap.authentication.LdapAuthenticationProvider;
import org.springframework.security.provisioning.InMemoryUserDetailsManager
/**
*
* @author Rob Winch
*
*/
class NamespacePasswordEncoderTests extends BaseSpringSpec {
def "password-encoder@ref with in memory"() {
when:
loadConfig(PasswordEncoderWithInMemoryConfig)
then:
authenticationManager.authenticate(new UsernamePasswordAuthenticationToken("user", "password"))
}
@EnableWebSecurity
@Configuration
static class PasswordEncoderWithInMemoryConfig extends WebSecurityConfigurerAdapter {
@Override
protected void registerAuthentication(AuthenticationManagerBuilder auth)
throws Exception {
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder()
auth
.inMemoryAuthentication()
.withUser("user").password(encoder.encode("password")).roles("USER").and()
.passwordEncoder(encoder)
}
}
def "password-encoder@ref with jdbc"() {
when:
loadConfig(PasswordEncoderWithJdbcConfig)
then:
authenticationManager.authenticate(new UsernamePasswordAuthenticationToken("user", "password"))
}
@EnableWebSecurity
@Configuration
static class PasswordEncoderWithJdbcConfig extends WebSecurityConfigurerAdapter {
@Override
protected void registerAuthentication(AuthenticationManagerBuilder auth)
throws Exception {
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder()
auth
.jdbcAuthentication()
.withDefaultSchema()
.dataSource(dataSource())
.withUser("user").password(encoder.encode("password")).roles("USER").and()
.passwordEncoder(encoder)
}
@Bean
public DataSource dataSource() {
EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder()
return builder.setType(EmbeddedDatabaseType.HSQL).build();
}
}
def "password-encoder@ref with userdetailsservice"() {
when:
loadConfig(PasswordEncoderWithUserDetailsServiceConfig)
then:
authenticationManager.authenticate(new UsernamePasswordAuthenticationToken("user", "password"))
}
@EnableWebSecurity
@Configuration
static class PasswordEncoderWithUserDetailsServiceConfig extends WebSecurityConfigurerAdapter {
@Override
protected void registerAuthentication(AuthenticationManagerBuilder auth)
throws Exception {
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder()
User user = new User("user",encoder.encode("password"), AuthorityUtils.createAuthorityList("ROLE_USER"))
InMemoryUserDetailsManager uds = new InMemoryUserDetailsManager([user])
auth
.userDetailsService(uds)
.passwordEncoder(encoder)
}
@Bean
public DataSource dataSource() {
EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder()
return builder.setType(EmbeddedDatabaseType.HSQL).build();
}
}
}

View File

@ -0,0 +1,83 @@
/*
* Copyright 2002-2013 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.annotation.authentication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
/**
* Class Containing the {@link Configuration} for
* {@link PasswordEncoderConfigurerTests}. Separate to ensure the configuration
* compiles in Java (i.e. we are not using hidden methods).
*
* @author Rob Winch
* @since 3.2
*/
public class PasswordEncoderConfigurerConfigs {
@EnableWebSecurity
@Configuration
static class PasswordEncoderConfig extends WebSecurityConfigurerAdapter {
protected void registerAuthentication(
AuthenticationManagerBuilder auth) throws Exception {
BCryptPasswordEncoder encoder = passwordEncoder();
auth
.inMemoryAuthentication()
.withUser("user").password(encoder.encode("password")).roles("USER").and()
.passwordEncoder(encoder);
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean()
throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
}
@Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
@EnableWebSecurity
@Configuration
static class PasswordEncoderNoAuthManagerLoadsConfig extends WebSecurityConfigurerAdapter {
protected void registerAuthentication(
AuthenticationManagerBuilder auth) throws Exception {
BCryptPasswordEncoder encoder = passwordEncoder();
auth
.inMemoryAuthentication()
.withUser("user").password(encoder.encode("password")).roles("USER").and()
.passwordEncoder(encoder);
}
@Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
}

View File

@ -0,0 +1,61 @@
/*
* Copyright 2002-2013 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.annotation.authentication
import static org.springframework.security.config.annotation.authentication.PasswordEncoderConfigurerConfigs.*
import org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.ldap.core.ContextSource;
import org.springframework.ldap.core.support.BaseLdapPathContextSource;
import org.springframework.security.authentication.AuthenticationManager
import org.springframework.security.authentication.AuthenticationProvider
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
import org.springframework.security.config.annotation.BaseSpringSpec
import org.springframework.security.config.annotation.SecurityBuilder;
import org.springframework.security.config.annotation.authentication.AuthenticationManagerBuilder
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.test.util.ReflectionTestUtils;
/**
*
* @author Rob Winch
*
*/
class PasswordEncoderConfigurerTests extends BaseSpringSpec {
def "password-encoder@ref with No AuthenticationManager Bean"() {
when:
loadConfig(PasswordEncoderNoAuthManagerLoadsConfig)
then:
noExceptionThrown()
}
def "password-encoder@ref with AuthenticationManagerBuilder"() {
when:
loadConfig(PasswordEncoderConfig)
AuthenticationManager authMgr = authenticationManager()
then:
authMgr.authenticate(new UsernamePasswordAuthenticationToken("user", "password"))
}
}

View File

@ -0,0 +1,120 @@
/*
* Copyright 2002-2013 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.annotation.configuration
import javax.servlet.ServletConfig
import javax.servlet.ServletContext
import org.springframework.beans.factory.BeanClassLoaderAware
import org.springframework.beans.factory.BeanFactoryAware
import org.springframework.beans.factory.BeanNameAware
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.context.ApplicationContextAware
import org.springframework.context.ApplicationEventPublisherAware
import org.springframework.context.EnvironmentAware
import org.springframework.context.MessageSourceAware
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.mock.web.MockServletConfig
import org.springframework.mock.web.MockServletContext
import org.springframework.security.config.annotation.BaseSpringSpec
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.configuration.AutowireBeanFactoryObjectPostProcessor;
import org.springframework.web.context.ServletConfigAware
import org.springframework.web.context.ServletContextAware
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext
/**
*
* @author Rob Winch
*/
class AutowireBeanFactoryObjectPostProcessorTests extends BaseSpringSpec {
def "Verify All Aware methods are invoked"() {
setup:
ApplicationContextAware contextAware = Mock(ApplicationContextAware)
ApplicationEventPublisherAware publisher = Mock(ApplicationEventPublisherAware)
BeanClassLoaderAware classloader = Mock(BeanClassLoaderAware)
BeanFactoryAware beanFactory = Mock(BeanFactoryAware)
EnvironmentAware environment = Mock(EnvironmentAware)
MessageSourceAware messageSource = Mock(MessageSourceAware)
ServletConfigAware servletConfig = Mock(ServletConfigAware)
ServletContextAware servletContext = Mock(ServletContextAware)
DisposableBean disposable = Mock(DisposableBean)
context = new AnnotationConfigWebApplicationContext([servletConfig:new MockServletConfig(),servletContext:new MockServletContext()])
context.register(Config)
context.refresh()
context.start()
ObjectPostProcessor opp = context.getBean(ObjectPostProcessor)
when:
opp.postProcess(contextAware)
then:
1 * contextAware.setApplicationContext(!null)
when:
opp.postProcess(publisher)
then:
1 * publisher.setApplicationEventPublisher(!null)
when:
opp.postProcess(classloader)
then:
1 * classloader.setBeanClassLoader(!null)
when:
opp.postProcess(beanFactory)
then:
1 * beanFactory.setBeanFactory(!null)
when:
opp.postProcess(environment)
then:
1 * environment.setEnvironment(!null)
when:
opp.postProcess(messageSource)
then:
1 * messageSource.setMessageSource(!null)
when:
opp.postProcess(servletConfig)
then:
1 * servletConfig.setServletConfig(!null)
when:
opp.postProcess(servletContext)
then:
1 * servletContext.setServletContext(!null)
when:
opp.postProcess(disposable)
context.close()
context = null
then:
1 * disposable.destroy()
}
@Configuration
static class Config {
@Bean
public ObjectPostProcessor objectPostProcessor(AutowireCapableBeanFactory beanFactory) {
return new AutowireBeanFactoryObjectPostProcessor(beanFactory);
}
}
}

View File

@ -0,0 +1,85 @@
/*
* Copyright 2002-2013 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.annotation.method.configuration
import static org.fest.assertions.Assertions.assertThat
import static org.junit.Assert.fail
import org.aopalliance.intercept.MethodInterceptor;
import org.springframework.context.ApplicationContext
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.AccessDecisionManager
import org.springframework.security.access.AccessDeniedException
import org.springframework.security.access.ConfigAttribute
import org.springframework.security.authentication.AuthenticationManager
import org.springframework.security.authentication.DefaultAuthenticationEventPublisher
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
import org.springframework.security.authentication.event.AuthenticationSuccessEvent;
import org.springframework.security.config.annotation.BaseSpringSpec
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration;
import org.springframework.security.config.annotation.web.WebSecurityConfigurerAdapterTests.InMemoryAuthWithWebSecurityConfigurerAdapter
import org.springframework.security.core.Authentication
import org.springframework.security.core.authority.AuthorityUtils
/**
*
* @author Rob Winch
*/
public class GlobalMethodSecurityConfigurationTests extends BaseSpringSpec {
def "messages set when using GlobalMethodSecurityConfiguration"() {
when:
loadConfig(InMemoryAuthWithGlobalMethodSecurityConfig)
then:
authenticationManager.messages.messageSource instanceof ApplicationContext
}
def "AuthenticationEventPublisher is registered GlobalMethodSecurityConfiguration"() {
when:
loadConfig(InMemoryAuthWithGlobalMethodSecurityConfig)
then:
authenticationManager.eventPublisher instanceof DefaultAuthenticationEventPublisher
when:
Authentication auth = new UsernamePasswordAuthenticationToken("user",null,AuthorityUtils.createAuthorityList("ROLE_USER"))
authenticationManager.eventPublisher.publishAuthenticationSuccess(auth)
then:
InMemoryAuthWithGlobalMethodSecurityConfig.EVENT.authentication == auth
}
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public static class InMemoryAuthWithGlobalMethodSecurityConfig extends GlobalMethodSecurityConfiguration implements ApplicationListener<AuthenticationSuccessEvent> {
static AuthenticationSuccessEvent EVENT
@Override
protected void registerAuthentication(AuthenticationManagerBuilder auth)
throws Exception {
auth
.inMemoryAuthentication()
}
@Override
public void onApplicationEvent(AuthenticationSuccessEvent e) {
EVENT = e
}
}
AuthenticationManager getAuthenticationManager() {
context.getBean(MethodInterceptor).authenticationManager
}
}

View File

@ -0,0 +1,51 @@
/*
* Copyright 2002-2013 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.annotation.method.configuration;
import javax.annotation.security.DenyAll
import org.springframework.security.access.annotation.Secured
import org.springframework.security.access.prepost.PostAuthorize;
import org.springframework.security.access.prepost.PreAuthorize
import org.springframework.security.core.Authentication
/**
*
* @author Rob Winch
*/
public interface MethodSecurityService {
@PreAuthorize("denyAll")
public String preAuthorize();
@Secured("ROLE_ADMIN")
public String secured();
@DenyAll
public String jsr250();
@Secured(["ROLE_USER","RUN_AS_SUPER"])
public Authentication runAs();
@PreAuthorize("permitAll")
public String preAuthorizePermitAll();
@PreAuthorize("hasPermission(#object,'read')")
public String hasPermission(String object);
@PostAuthorize("hasPermission(#object,'read')")
public String postHasPermission(String object);
}

View File

@ -0,0 +1,62 @@
/*
* Copyright 2002-2013 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.annotation.method.configuration;
import org.springframework.security.core.Authentication
import org.springframework.security.core.context.SecurityContextHolder
/**
*
* @author Rob Winch
*
*/
public class MethodSecurityServiceImpl implements MethodSecurityService {
@Override
public String preAuthorize() {
return null;
}
@Override
public String secured() {
return null;
}
@Override
public String jsr250() {
return null;
}
@Override
public Authentication runAs() {
return SecurityContextHolder.getContext().getAuthentication();
}
@Override
public String preAuthorizePermitAll() {
return null;
}
@Override
public String hasPermission(String object) {
return null;
}
@Override
public String postHasPermission(String object) {
return null;
}
}

View File

@ -0,0 +1,93 @@
/*
* Copyright 2002-2013 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.annotation.method.configuration
import static org.fest.assertions.Assertions.assertThat
import static org.junit.Assert.fail
import java.io.Serializable;
import org.springframework.context.ConfigurableApplicationContext
import org.springframework.context.annotation.AnnotationConfigApplicationContext
import org.springframework.context.annotation.Configuration
import org.springframework.security.access.AccessDeniedException
import org.springframework.security.access.PermissionEvaluator;
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler
import org.springframework.security.authentication.TestingAuthenticationToken
import org.springframework.security.config.annotation.BaseSpringSpec
import org.springframework.security.config.annotation.method.configuration.NamespaceGlobalMethodSecurityTests.BaseMethodConfig;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder
/**
*
* @author Rob Winch
*/
public class NamespaceGlobalMethodSecurityExpressionHandlerTests extends BaseSpringSpec {
def setup() {
SecurityContextHolder.getContext().setAuthentication(
new TestingAuthenticationToken("user", "password","ROLE_USER"))
}
def "global-method-security/expression-handler @PreAuthorize"() {
setup:
context = new AnnotationConfigApplicationContext(BaseMethodConfig,CustomAccessDecisionManagerConfig)
MethodSecurityService service = context.getBean(MethodSecurityService)
when:
service.hasPermission("granted")
then:
noExceptionThrown()
when:
service.hasPermission("denied")
then:
thrown(AccessDeniedException)
}
def "global-method-security/expression-handler @PostAuthorize"() {
setup:
context = new AnnotationConfigApplicationContext(BaseMethodConfig,CustomAccessDecisionManagerConfig)
MethodSecurityService service = context.getBean(MethodSecurityService)
when:
service.postHasPermission("granted")
then:
noExceptionThrown()
when:
service.postHasPermission("denied")
then:
thrown(AccessDeniedException)
}
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public static class CustomAccessDecisionManagerConfig extends GlobalMethodSecurityConfiguration {
@Override
protected MethodSecurityExpressionHandler expressionHandler() {
DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler()
expressionHandler.permissionEvaluator = new PermissionEvaluator() {
boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) {
"granted" == targetDomainObject
}
boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) {
throw new UnsupportedOperationException()
}
}
return expressionHandler
}
}
}

View File

@ -0,0 +1,443 @@
/*
* Copyright 2002-2013 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.annotation.method.configuration
import static org.fest.assertions.Assertions.assertThat
import static org.junit.Assert.fail
import java.lang.reflect.Method
import org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator
import org.springframework.beans.factory.BeanCreationException
import org.springframework.context.ConfigurableApplicationContext
import org.springframework.context.annotation.AdviceMode
import org.springframework.context.annotation.AnnotationConfigApplicationContext
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.context.annotation.Import
import org.springframework.core.Ordered
import org.springframework.security.access.AccessDecisionManager
import org.springframework.security.access.AccessDeniedException
import org.springframework.security.access.ConfigAttribute
import org.springframework.security.access.SecurityConfig
import org.springframework.security.access.intercept.AfterInvocationManager
import org.springframework.security.access.intercept.RunAsManager
import org.springframework.security.access.intercept.RunAsManagerImpl
import org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor
import org.springframework.security.access.intercept.aopalliance.MethodSecurityMetadataSourceAdvisor
import org.springframework.security.access.method.AbstractMethodSecurityMetadataSource
import org.springframework.security.access.method.MethodSecurityMetadataSource
import org.springframework.security.authentication.AuthenticationManager
import org.springframework.security.authentication.TestingAuthenticationToken
import org.springframework.security.config.annotation.BaseSpringSpec
import org.springframework.security.config.annotation.authentication.BaseAuthenticationConfig;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration;
import org.springframework.security.core.Authentication
import org.springframework.security.core.context.SecurityContextHolder
/**
*
* @author Rob Winch
*/
public class NamespaceGlobalMethodSecurityTests extends BaseSpringSpec {
def setup() {
SecurityContextHolder.getContext().setAuthentication(
new TestingAuthenticationToken("user", "password","ROLE_USER"))
}
// --- access-decision-manager-ref ---
def "custom AccessDecisionManager can be used"() {
setup: "Create an instance with an AccessDecisionManager that always denies access"
context = new AnnotationConfigApplicationContext(BaseMethodConfig,CustomAccessDecisionManagerConfig)
MethodSecurityService service = context.getBean(MethodSecurityService)
when:
service.preAuthorize()
then:
thrown(AccessDeniedException)
when:
service.secured()
then:
thrown(AccessDeniedException)
}
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public static class CustomAccessDecisionManagerConfig extends GlobalMethodSecurityConfiguration {
@Override
protected AccessDecisionManager accessDecisionManager() {
return new DenyAllAccessDecisionManager()
}
public static class DenyAllAccessDecisionManager implements AccessDecisionManager {
public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) {
throw new AccessDeniedException("Always Denied")
}
public boolean supports(ConfigAttribute attribute) {
return true
}
public boolean supports(Class<?> clazz) {
return true
}
}
}
// --- authentication-manager-ref ---
def "custom AuthenticationManager can be used"() {
when:
context = new AnnotationConfigApplicationContext(CustomAuthenticationConfig)
MethodSecurityInterceptor interceptor = context.getBean(MethodSecurityInterceptor)
interceptor.authenticationManager.authenticate(SecurityContextHolder.context.authentication)
then:
thrown(UnsupportedOperationException)
}
@Configuration
@EnableGlobalMethodSecurity
public static class CustomAuthenticationConfig extends GlobalMethodSecurityConfiguration {
@Override
protected AuthenticationManager authenticationManager() {
return new AuthenticationManager() {
Authentication authenticate(Authentication authentication) {
throw new UnsupportedOperationException()
}
}
}
}
// --- jsr250-annotations ---
def "enable jsr250"() {
when:
context = new AnnotationConfigApplicationContext(Jsr250Config)
MethodSecurityService service = context.getBean(MethodSecurityService)
then: "@Secured and @PreAuthorize are ignored"
service.secured() == null
service.preAuthorize() == null
when: "@DenyAll method invoked"
service.jsr250()
then: "access is denied"
thrown(AccessDeniedException)
}
@EnableGlobalMethodSecurity(jsr250Enabled = true)
@Configuration
public static class Jsr250Config extends BaseMethodConfig {
}
// --- metadata-source-ref ---
def "custom MethodSecurityMetadataSource can be used with higher priority than other sources"() {
setup:
context = new AnnotationConfigApplicationContext(BaseMethodConfig,CustomMethodSecurityMetadataSourceConfig)
MethodSecurityService service = context.getBean(MethodSecurityService)
when:
service.preAuthorize()
then:
thrown(AccessDeniedException)
when:
service.secured()
then:
thrown(AccessDeniedException)
when:
service.jsr250()
then:
thrown(AccessDeniedException)
}
@Configuration
@EnableGlobalMethodSecurity
public static class CustomMethodSecurityMetadataSourceConfig extends GlobalMethodSecurityConfiguration {
@Override
protected MethodSecurityMetadataSource customMethodSecurityMetadataSource() {
return new AbstractMethodSecurityMetadataSource() {
public Collection<ConfigAttribute> getAttributes(Method method, Class<?> targetClass) {
// require ROLE_NOBODY for any method on MethodSecurityService class
return MethodSecurityService.isAssignableFrom(targetClass) ? [new SecurityConfig("ROLE_NOBODY")] : []
}
public Collection<ConfigAttribute> getAllConfigAttributes() {
return null
}
}
}
}
// --- mode ---
def "aspectj mode works"() {
when:
context = new AnnotationConfigApplicationContext(AspectJModeConfig)
then:
AnnotationAwareAspectJAutoProxyCreator autoProxyCreator = context.getBean(AnnotationAwareAspectJAutoProxyCreator)
autoProxyCreator.proxyTargetClass == true
}
@Configuration
@EnableGlobalMethodSecurity(mode = AdviceMode.ASPECTJ, proxyTargetClass = true)
public static class AspectJModeConfig extends BaseMethodConfig {
}
def "aspectj mode works extending GlobalMethodSecurityConfiguration"() {
when:
context = new AnnotationConfigApplicationContext(BaseMethodConfig,AspectJModeExtendsGMSCConfig)
then:
AnnotationAwareAspectJAutoProxyCreator autoProxyCreator = context.getBean(AnnotationAwareAspectJAutoProxyCreator)
autoProxyCreator.proxyTargetClass == false
}
@Configuration
@EnableGlobalMethodSecurity(mode = AdviceMode.ASPECTJ)
public static class AspectJModeExtendsGMSCConfig extends GlobalMethodSecurityConfiguration {
}
// --- order ---
def order() {
when:
context = new AnnotationConfigApplicationContext(CustomOrderConfig)
MethodSecurityMetadataSourceAdvisor advisor = context.getBean(MethodSecurityMetadataSourceAdvisor)
then:
advisor.order == 135
}
@Configuration
@EnableGlobalMethodSecurity(order = 135)
public static class CustomOrderConfig extends BaseMethodConfig {
}
def "order is defaulted to Ordered.LOWEST_PRECEDENCE when using @EnableGlobalMethodSecurity"() {
when:
context = new AnnotationConfigApplicationContext(DefaultOrderConfig)
MethodSecurityMetadataSourceAdvisor advisor = context.getBean(MethodSecurityMetadataSourceAdvisor)
then:
advisor.order == Ordered.LOWEST_PRECEDENCE
}
@Configuration
@EnableGlobalMethodSecurity
public static class DefaultOrderConfig extends BaseMethodConfig {
}
def "order is defaulted to Ordered.LOWEST_PRECEDENCE when extending GlobalMethodSecurityConfiguration"() {
when:
context = new AnnotationConfigApplicationContext(BaseMethodConfig,DefaultOrderExtendsMethodSecurityConfig)
MethodSecurityMetadataSourceAdvisor advisor = context.getBean(MethodSecurityMetadataSourceAdvisor)
then:
advisor.order == Ordered.LOWEST_PRECEDENCE
}
@Configuration
@EnableGlobalMethodSecurity
public static class DefaultOrderExtendsMethodSecurityConfig extends GlobalMethodSecurityConfiguration {
}
// --- pre-post-annotations ---
def preAuthorize() {
when:
context = new AnnotationConfigApplicationContext(PreAuthorizeConfig)
MethodSecurityService service = context.getBean(MethodSecurityService)
then:
service.secured() == null
service.jsr250() == null
when:
service.preAuthorize()
then:
thrown(AccessDeniedException)
}
@EnableGlobalMethodSecurity(prePostEnabled = true)
@Configuration
public static class PreAuthorizeConfig extends BaseMethodConfig {
}
def "prePostEnabled extends GlobalMethodSecurityConfiguration"() {
when:
context = new AnnotationConfigApplicationContext(BaseMethodConfig,PreAuthorizeExtendsGMSCConfig)
MethodSecurityService service = context.getBean(MethodSecurityService)
then:
service.secured() == null
service.jsr250() == null
when:
service.preAuthorize()
then:
thrown(AccessDeniedException)
}
@EnableGlobalMethodSecurity(prePostEnabled = true)
@Configuration
public static class PreAuthorizeExtendsGMSCConfig extends GlobalMethodSecurityConfiguration {
}
// --- proxy-target-class ---
def "proxying classes works"() {
when:
context = new AnnotationConfigApplicationContext(ProxyTargetClass)
MethodSecurityServiceImpl service = context.getBean(MethodSecurityServiceImpl)
then:
noExceptionThrown()
}
@EnableGlobalMethodSecurity(proxyTargetClass = true)
@Configuration
public static class ProxyTargetClass extends BaseMethodConfig {
}
def "proxying interfaces works"() {
when:
context = new AnnotationConfigApplicationContext(PreAuthorizeConfig)
MethodSecurityService service = context.getBean(MethodSecurityService)
then: "we get an instance of the interface"
noExceptionThrown()
when: "try to cast to the class"
MethodSecurityServiceImpl serviceImpl = service
then: "we get a class cast exception"
thrown(ClassCastException)
}
// --- run-as-manager-ref ---
def "custom RunAsManager"() {
when:
context = new AnnotationConfigApplicationContext(BaseMethodConfig,CustomRunAsManagerConfig)
MethodSecurityService service = context.getBean(MethodSecurityService)
then:
service.runAs().authorities.find { it.authority == "ROLE_RUN_AS_SUPER"}
}
@Configuration
@EnableGlobalMethodSecurity(securedEnabled = true)
public static class CustomRunAsManagerConfig extends GlobalMethodSecurityConfiguration {
@Override
protected RunAsManager runAsManager() {
RunAsManagerImpl runAsManager = new RunAsManagerImpl()
runAsManager.setKey("some key")
return runAsManager
}
}
// --- secured-annotation ---
def "secured enabled"() {
setup:
context = new AnnotationConfigApplicationContext(SecuredConfig)
MethodSecurityService service = context.getBean(MethodSecurityService)
when:
service.secured()
then:
thrown(AccessDeniedException)
service.preAuthorize() == null
service.jsr250() == null
}
@EnableGlobalMethodSecurity(securedEnabled = true)
@Configuration
public static class SecuredConfig extends BaseMethodConfig {
}
// --- after-invocation-provider
def "custom AfterInvocationManager"() {
setup:
context = new AnnotationConfigApplicationContext(BaseMethodConfig,CustomAfterInvocationManagerConfig)
MethodSecurityService service = context.getBean(MethodSecurityService)
when:
service.preAuthorizePermitAll()
then:
AccessDeniedException e = thrown()
e.message == "custom AfterInvocationManager"
}
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public static class CustomAfterInvocationManagerConfig extends GlobalMethodSecurityConfiguration {
@Override
protected AfterInvocationManager afterInvocationManager() {
return new AfterInvocationManagerStub()
}
public static class AfterInvocationManagerStub implements AfterInvocationManager {
Object decide(Authentication authentication, Object object, Collection<ConfigAttribute> attributes,
Object returnedObject) throws AccessDeniedException {
throw new AccessDeniedException("custom AfterInvocationManager")
}
boolean supports(ConfigAttribute attribute) {
return true
}
boolean supports(Class<?> clazz) {
return true
}
}
}
// --- misc ---
def "good error message when no Enable annotation"() {
when:
context = new AnnotationConfigApplicationContext(ExtendsNoEnableAnntotationConfig)
MethodSecurityInterceptor interceptor = context.getBean(MethodSecurityInterceptor)
interceptor.authenticationManager.authenticate(SecurityContextHolder.context.authentication)
then:
BeanCreationException e = thrown()
e.message.contains(EnableGlobalMethodSecurity.class.getName() + " is required")
}
@Configuration
public static class ExtendsNoEnableAnntotationConfig extends GlobalMethodSecurityConfiguration {
@Override
protected AuthenticationManager authenticationManager() {
return new AuthenticationManager() {
Authentication authenticate(Authentication authentication) {
throw new UnsupportedOperationException()
}
}
}
}
def "import subclass of GlobalMethodSecurityConfiguration"() {
when:
context = new AnnotationConfigApplicationContext(ImportSubclassGMSCConfig)
MethodSecurityService service = context.getBean(MethodSecurityService)
then:
service.secured() == null
service.jsr250() == null
when:
service.preAuthorize()
then:
thrown(AccessDeniedException)
}
@Configuration
@Import(PreAuthorizeExtendsGMSCConfig)
public static class ImportSubclassGMSCConfig extends BaseMethodConfig {
}
@Configuration
public static class BaseMethodConfig extends BaseAuthenticationConfig {
@Bean
public MethodSecurityService methodSecurityService() {
return new MethodSecurityServiceImpl()
}
}
}

View File

@ -0,0 +1,129 @@
/*
* Copyright 2002-2013 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.annotation.method.configuration
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.access.AccessDeniedException
import org.springframework.security.access.PermissionEvaluator;
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler
import org.springframework.security.authentication.AuthenticationManager
import org.springframework.security.authentication.TestingAuthenticationToken
import org.springframework.security.config.annotation.BaseSpringSpec
import org.springframework.security.config.annotation.authentication.AuthenticationManagerBuilder
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder
/**
* Demonstrate the samples
*
* @author Rob Winch
*
*/
public class SampleEnableGlobalMethodSecurityTests extends BaseSpringSpec {
def setup() {
SecurityContextHolder.getContext().setAuthentication(
new TestingAuthenticationToken("user", "password","ROLE_USER"))
}
def preAuthorize() {
when:
loadConfig(SampleWebSecurityConfig)
MethodSecurityService service = context.getBean(MethodSecurityService)
then:
service.secured() == null
service.jsr250() == null
when:
service.preAuthorize()
then:
thrown(AccessDeniedException)
}
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled=true)
public static class SampleWebSecurityConfig {
@Bean
public MethodSecurityService methodSecurityService() {
return new MethodSecurityServiceImpl()
}
@Bean
public AuthenticationManager authenticationManager() throws Exception {
return new AuthenticationManagerBuilder()
.inMemoryAuthentication()
.withUser("user").password("password").roles("USER").and()
.withUser("admin").password("password").roles("USER", "ADMIN").and()
.and()
.build();
}
}
def 'custom permission handler'() {
when:
loadConfig(CustomPermissionEvaluatorWebSecurityConfig)
MethodSecurityService service = context.getBean(MethodSecurityService)
then:
service.hasPermission("allowed") == null
when:
service.hasPermission("denied") == null
then:
thrown(AccessDeniedException)
}
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled=true)
public static class CustomPermissionEvaluatorWebSecurityConfig extends GlobalMethodSecurityConfiguration {
@Bean
public MethodSecurityService methodSecurityService() {
return new MethodSecurityServiceImpl()
}
@Override
protected MethodSecurityExpressionHandler expressionHandler() {
DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
expressionHandler.setPermissionEvaluator(new CustomPermissionEvaluator());
return expressionHandler;
}
@Override
protected void registerAuthentication(AuthenticationManagerBuilder auth)
throws Exception {
auth
.inMemoryAuthentication()
.withUser("user").password("password").roles("USER").and()
.withUser("admin").password("password").roles("USER", "ADMIN");
}
}
static class CustomPermissionEvaluator implements PermissionEvaluator {
public boolean hasPermission(Authentication authentication,
Object targetDomainObject, Object permission) {
return !"denied".equals(targetDomainObject);
}
public boolean hasPermission(Authentication authentication,
Serializable targetId, String targetType, Object permission) {
return !"denied".equals(targetId);
}
}
}

View File

@ -0,0 +1,97 @@
/*
* Copyright 2002-2013 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.annotation.provisioning
import org.springframework.security.config.annotation.authentication.configurers.provisioning.UserDetailsManagerConfigurer;
import org.springframework.security.core.authority.SimpleGrantedAuthority
import org.springframework.security.core.userdetails.UserDetails
import org.springframework.security.provisioning.InMemoryUserDetailsManager
import spock.lang.Specification
/**
*
* @author Rob Winch
*
*/
class UserDetailsManagerConfigurerTests extends Specification {
def "all attributes supported"() {
setup:
InMemoryUserDetailsManager userDetailsManager = new InMemoryUserDetailsManager([])
when:
UserDetails userDetails = new UserDetailsManagerConfigurer<UserDetailsManagerConfigurer<InMemoryUserDetailsManager>>(userDetailsManager)
.withUser("user")
.password("password")
.roles("USER")
.disabled(true)
.accountExpired(true)
.accountLocked(true)
.credentialsExpired(true)
.build()
then:
userDetails.username == 'user'
userDetails.password == 'password'
userDetails.authorities.collect { it.authority } == ["ROLE_USER"]
!userDetails.accountNonExpired
!userDetails.accountNonLocked
!userDetails.credentialsNonExpired
!userDetails.enabled
}
def "authorities(GrantedAuthorities...) works"() {
setup:
InMemoryUserDetailsManager userDetailsManager = new InMemoryUserDetailsManager([])
SimpleGrantedAuthority authority = new SimpleGrantedAuthority("ROLE_USER")
when:
UserDetails userDetails = new UserDetailsManagerConfigurer<UserDetailsManagerConfigurer<InMemoryUserDetailsManager>>(userDetailsManager)
.withUser("user")
.password("password")
.authorities(authority)
.build()
then:
userDetails.authorities == [authority] as Set
}
def "authorities(String...) works"() {
setup:
InMemoryUserDetailsManager userDetailsManager = new InMemoryUserDetailsManager([])
String authority = "ROLE_USER"
when:
UserDetails userDetails = new UserDetailsManagerConfigurer<UserDetailsManagerConfigurer<InMemoryUserDetailsManager>>(userDetailsManager)
.withUser("user")
.password("password")
.authorities(authority)
.build()
then:
userDetails.authorities.collect { it.authority } == [authority]
}
def "authorities(List) works"() {
setup:
InMemoryUserDetailsManager userDetailsManager = new InMemoryUserDetailsManager([])
SimpleGrantedAuthority authority = new SimpleGrantedAuthority("ROLE_USER")
when:
UserDetails userDetails = new UserDetailsManagerConfigurer<UserDetailsManagerConfigurer<InMemoryUserDetailsManager>>(userDetailsManager)
.withUser("user")
.password("password")
.authorities([authority])
.build()
then:
userDetails.authorities == [authority] as Set
}
}

View File

@ -0,0 +1,171 @@
/*
* Copyright 2002-2013 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.annotation.web
import org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.SecurityConfigurer
import org.springframework.security.config.annotation.SecurityConfigurerAdapter
import org.springframework.test.util.ReflectionTestUtils
import spock.lang.Specification
/**
* @author Rob Winch
*
*/
class AbstractConfiguredSecurityBuilderTests extends Specification {
ConcreteAbstractConfiguredBuilder builder = new ConcreteAbstractConfiguredBuilder()
def "Null ObjectPostProcessor rejected"() {
when:
new ConcreteAbstractConfiguredBuilder(null)
then:
thrown(IllegalArgumentException)
when:
builder.objectPostProcessor(null);
then:
thrown(IllegalArgumentException)
}
def "apply null is rejected"() {
when:
builder.apply(null)
then:
thrown(IllegalArgumentException)
}
def "Duplicate configurer is removed"() {
when:
builder.apply(new ConcreteConfigurer())
builder.apply(new ConcreteConfigurer())
then:
ReflectionTestUtils.getField(builder,"configurers").size() == 1
}
def "build twice fails"() {
setup:
builder.build()
when:
builder.build()
then:
thrown(IllegalStateException)
}
def "getObject before build fails"() {
when:
builder.getObject()
then:
thrown(IllegalStateException)
}
def "Configurer.init can apply another configurer"() {
setup:
DelegateConfigurer.CONF = Mock(SecurityConfigurerAdapter)
when:
builder.apply(new DelegateConfigurer())
builder.build()
then:
1 * DelegateConfigurer.CONF.init(builder)
1 * DelegateConfigurer.CONF.configure(builder)
}
def "getConfigurer with multi fails"() {
setup:
ConcreteAbstractConfiguredBuilder builder = new ConcreteAbstractConfiguredBuilder(ObjectPostProcessor.QUIESCENT_POSTPROCESSOR, true)
builder.apply(new DelegateConfigurer())
builder.apply(new DelegateConfigurer())
when:
builder.getConfigurer(DelegateConfigurer)
then: "Fail due to trying to obtain a single DelegateConfigurer and multiple are provided"
thrown(IllegalStateException)
}
def "removeConfigurer with multi fails"() {
setup:
ConcreteAbstractConfiguredBuilder builder = new ConcreteAbstractConfiguredBuilder(ObjectPostProcessor.QUIESCENT_POSTPROCESSOR, true)
builder.apply(new DelegateConfigurer())
builder.apply(new DelegateConfigurer())
when:
builder.removeConfigurer(DelegateConfigurer)
then: "Fail due to trying to remove and obtain a single DelegateConfigurer and multiple are provided"
thrown(IllegalStateException)
}
def "removeConfigurers with multi"() {
setup:
DelegateConfigurer c1 = new DelegateConfigurer()
DelegateConfigurer c2 = new DelegateConfigurer()
ConcreteAbstractConfiguredBuilder builder = new ConcreteAbstractConfiguredBuilder(ObjectPostProcessor.QUIESCENT_POSTPROCESSOR, true)
builder.apply(c1)
builder.apply(c2)
when:
def result = builder.removeConfigurers(DelegateConfigurer)
then:
result.size() == 2
result.contains(c1)
result.contains(c2)
builder.getConfigurers(DelegateConfigurer).empty
}
def "getConfigurers with multi"() {
setup:
DelegateConfigurer c1 = new DelegateConfigurer()
DelegateConfigurer c2 = new DelegateConfigurer()
ConcreteAbstractConfiguredBuilder builder = new ConcreteAbstractConfiguredBuilder(ObjectPostProcessor.QUIESCENT_POSTPROCESSOR, true)
builder.apply(c1)
builder.apply(c2)
when:
def result = builder.getConfigurers(DelegateConfigurer)
then:
result.size() == 2
result.contains(c1)
result.contains(c2)
builder.getConfigurers(DelegateConfigurer).size() == 2
}
private static class DelegateConfigurer extends SecurityConfigurerAdapter<Object, ConcreteAbstractConfiguredBuilder> {
private static SecurityConfigurer<Object, ConcreteAbstractConfiguredBuilder> CONF;
@Override
public void init(ConcreteAbstractConfiguredBuilder builder)
throws Exception {
builder.apply(CONF);
}
}
private static class ConcreteConfigurer extends SecurityConfigurerAdapter<Object, ConcreteAbstractConfiguredBuilder> { }
private static class ConcreteAbstractConfiguredBuilder extends AbstractConfiguredSecurityBuilder<Object, ConcreteAbstractConfiguredBuilder> {
public ConcreteAbstractConfiguredBuilder() {
}
public ConcreteAbstractConfiguredBuilder(ObjectPostProcessor<Object> objectPostProcessor) {
super(objectPostProcessor);
}
public ConcreteAbstractConfiguredBuilder(ObjectPostProcessor<Object> objectPostProcessor, boolean allowMulti) {
super(objectPostProcessor,allowMulti);
}
public Object performBuild() throws Exception {
return "success";
}
}
}

View File

@ -0,0 +1,59 @@
/*
* Copyright 2002-2013 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.annotation.web;
import static org.springframework.security.config.annotation.web.AbstractRequestMatcherConfigurer.RequestMatchers.*
import org.springframework.http.HttpMethod;
import org.springframework.security.web.util.AntPathRequestMatcher;
import org.springframework.security.web.util.RegexRequestMatcher;
import spock.lang.Specification;
/**
* @author Rob Winch
*
*/
class RequestMatchersTests extends Specification {
def "regexMatchers(GET,'/a.*') uses RegexRequestMatcher"() {
when:
def matchers = regexMatchers(HttpMethod.GET, "/a.*")
then: 'matcher is a RegexRequestMatcher'
matchers.collect {it.class } == [RegexRequestMatcher]
}
def "regexMatchers('/a.*') uses RegexRequestMatcher"() {
when:
def matchers = regexMatchers("/a.*")
then: 'matcher is a RegexRequestMatcher'
matchers.collect {it.class } == [RegexRequestMatcher]
}
def "antMatchers(GET,'/a.*') uses AntPathRequestMatcher"() {
when:
def matchers = antMatchers(HttpMethod.GET, "/a.*")
then: 'matcher is a RegexRequestMatcher'
matchers.collect {it.class } == [AntPathRequestMatcher]
}
def "antMatchers('/a.*') uses AntPathRequestMatcher"() {
when:
def matchers = antMatchers("/a.*")
then: 'matcher is a AntPathRequestMatcher'
matchers.collect {it.class } == [AntPathRequestMatcher]
}
}

View File

@ -0,0 +1,321 @@
/*
* Copyright 2002-2013 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.annotation.web
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.core.annotation.Order
import org.springframework.security.authentication.AuthenticationManager
import org.springframework.security.config.annotation.BaseWebSpecuritySpec
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.builders.WebSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
/**
* Demonstrate the samples
*
* @author Rob Winch
*
*/
public class SampleWebSecurityConfigurerAdapterTests extends BaseWebSpecuritySpec {
def "README HelloWorld Sample works"() {
setup: "Sample Config is loaded"
loadConfig(HelloWorldWebSecurityConfigurerAdapter)
when:
springSecurityFilterChain.doFilter(request,response,chain)
then:
response.getRedirectedUrl() == "http://localhost/login"
when: "fail to log in"
super.setup()
request.requestURI = "/login"
request.method = "POST"
springSecurityFilterChain.doFilter(request,response,chain)
then: "sent to login error page"
response.getRedirectedUrl() == "/login?error"
when: "login success"
super.setup()
request.requestURI = "/login"
request.method = "POST"
request.parameters.username = ["user"] as String[]
request.parameters.password = ["password"] as String[]
springSecurityFilterChain.doFilter(request,response,chain)
then: "sent to default succes page"
response.getRedirectedUrl() == "/"
}
/**
* <code>
* <http use-expressions="true">
* <intercept-url pattern="/resources/**" access="permitAll"/>
* <intercept-url pattern="/**" access="authenticated"/>
* <logout
* logout-success-url="/login?logout"
* logout-url="/logout"
* <form-login
* authentication-failure-url="/login?error"
* login-page="/login" <!-- Except Spring Security renders the login page -->
* login-processing-url="/login" <!-- but only POST -->
* password-parameter="password"
* username-parameter="username"
* />
* </http>
* <authentication-manager>
* <authentication-provider>
* <user-service>
* <user username="user" password="password" authorities="ROLE_USER"/>
* </user-service>
* </authentication-provider>
* </authentication-manager>
* </code>
* @author Rob Winch
*/
@Configuration
@EnableWebSecurity
public static class HelloWorldWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
@Override
protected void registerAuthentication(AuthenticationManagerBuilder auth) {
auth
.inMemoryAuthentication()
.withUser("user").password("password").roles("USER");
}
}
def "README Sample works"() {
setup: "Sample Config is loaded"
loadConfig(SampleWebSecurityConfigurerAdapter)
when:
springSecurityFilterChain.doFilter(request,response,chain)
then:
response.getRedirectedUrl() == "http://localhost/login"
when: "fail to log in"
super.setup()
request.requestURI = "/login"
request.method = "POST"
springSecurityFilterChain.doFilter(request,response,chain)
then: "sent to login error page"
response.getRedirectedUrl() == "/login?error"
when: "login success"
super.setup()
request.requestURI = "/login"
request.method = "POST"
request.parameters.username = ["user"] as String[]
request.parameters.password = ["password"] as String[]
springSecurityFilterChain.doFilter(request,response,chain)
then: "sent to default succes page"
response.getRedirectedUrl() == "/"
}
/**
* <code>
* <http security="none" pattern="/resources/**"/>
* <http use-expressions="true">
* <intercept-url pattern="/logout" access="permitAll"/>
* <intercept-url pattern="/login" access="permitAll"/>
* <intercept-url pattern="/signup" access="permitAll"/>
* <intercept-url pattern="/about" access="permitAll"/>
* <intercept-url pattern="/**" access="hasRole('ROLE_USER')"/>
* <logout
* logout-success-url="/login?logout"
* logout-url="/logout"
* <form-login
* authentication-failure-url="/login?error"
* login-page="/login"
* login-processing-url="/login" <!-- but only POST -->
* password-parameter="password"
* username-parameter="username"
* />
* </http>
* <authentication-manager>
* <authentication-provider>
* <user-service>
* <user username="user" password="password" authorities="ROLE_USER"/>
* <user username="admin" password="password" authorities="ROLE_USER,ROLE_ADMIN"/>
* </user-service>
* </authentication-provider>
* </authentication-manager>
* </code>
* @author Rob Winch
*/
@Configuration
@EnableWebSecurity
public static class SampleWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
@Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/resources/**");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeUrls()
.antMatchers("/signup","/about").permitAll()
.anyRequest().hasRole("USER")
.and()
.formLogin()
.loginUrl("/login")
// set permitAll for all URLs associated with Form Login
.permitAll();
}
@Override
protected void registerAuthentication(AuthenticationManagerBuilder auth) {
auth
.inMemoryAuthentication()
.withUser("user").password("password").roles("USER").and()
.withUser("admin").password("password").roles("USER", "ADMIN");
}
}
def "README Multi http Sample works"() {
setup:
loadConfig(SampleMultiHttpSecurityConfig)
when:
springSecurityFilterChain.doFilter(request,response,chain)
then:
response.getRedirectedUrl() == "http://localhost/login"
when: "fail to log in"
super.setup()
request.requestURI = "/login"
request.method = "POST"
springSecurityFilterChain.doFilter(request,response,chain)
then: "sent to login error page"
response.getRedirectedUrl() == "/login?error"
when: "login success"
super.setup()
request.requestURI = "/login"
request.method = "POST"
request.parameters.username = ["user"] as String[]
request.parameters.password = ["password"] as String[]
springSecurityFilterChain.doFilter(request,response,chain)
then: "sent to default succes page"
response.getRedirectedUrl() == "/"
when: "request protected API URL"
super.setup()
request.servletPath = "/api/admin/test"
springSecurityFilterChain.doFilter(request,response,chain)
then: "get 403"
response.getStatus() == 403
when: "request API for admins with user"
super.setup()
request.servletPath = "/api/admin/test"
request.addHeader("Authorization", "Basic " + "user:password".bytes.encodeBase64().toString())
springSecurityFilterChain.doFilter(request,response,chain)
then: "get 403"
response.getStatus() == 403
when: "request API for admins with admin"
super.setup()
request.servletPath = "/api/admin/test"
request.addHeader("Authorization", "Basic " + "admin:password".bytes.encodeBase64().toString())
springSecurityFilterChain.doFilter(request,response,chain)
then: "get 200"
response.getStatus() == 200
}
/**
* <code>
* <http security="none" pattern="/resources/**"/>
* <http use-expressions="true" pattern="/api/**">
* <intercept-url pattern="/api/admin/**" access="hasRole('ROLE_ADMIN')"/>
* <intercept-url pattern="/api/**" access="hasRole('ROLE_USER')"/>
* <http-basic />
* </http>
* <http use-expressions="true">
* <intercept-url pattern="/logout" access="permitAll"/>
* <intercept-url pattern="/login" access="permitAll"/>
* <intercept-url pattern="/signup" access="permitAll"/>
* <intercept-url pattern="/about" access="permitAll"/>
* <intercept-url pattern="/**" access="hasRole('ROLE_USER')"/>
* <logout
* logout-success-url="/login?logout"
* logout-url="/logout"
* <form-login
* authentication-failure-url="/login?error"
* login-page="/login"
* login-processing-url="/login" <!-- but only POST -->
* password-parameter="password"
* username-parameter="username"
* />
* </http>
* <authentication-manager>
* <authentication-provider>
* <user-service>
* <user username="user" password="password" authorities="ROLE_USER"/>
* <user username="admin" password="password" authorities="ROLE_USER,ROLE_ADMIN"/>
* </user-service>
* </authentication-provider>
* </authentication-manager>
* </code>
* @author Rob Winch
*/
@Configuration
@EnableWebSecurity
public static class SampleMultiHttpSecurityConfig {
@Bean
public AuthenticationManager authenticationManager() {
return new AuthenticationManagerBuilder()
.inMemoryAuthentication()
.withUser("user").password("password").roles("USER").and()
.withUser("admin").password("password").roles("USER", "ADMIN").and()
.and()
.build();
}
@Configuration
@Order(1)
public static class ApiWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/api/**")
.authorizeUrls()
.antMatchers("/api/admin/**").hasRole("ADMIN")
.antMatchers("/api/**").hasRole("USER")
.and()
.httpBasic();
}
}
@Configuration
public static class FormLoginWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
@Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/resources/**");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeUrls()
.antMatchers("/signup","/about").permitAll()
.anyRequest().hasRole("USER")
.and()
.formLogin()
.loginUrl("/login")
.permitAll();
}
}
}
}

View File

@ -0,0 +1,102 @@
/*
* Copyright 2002-2013 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.annotation.web;
import static org.springframework.security.config.annotation.web.WebSecurityConfigurerAdapterTestsConfigs.*
import static org.junit.Assert.*
import javax.sql.DataSource
import org.springframework.context.ApplicationContext
import org.springframework.context.ApplicationListener
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType
import org.springframework.ldap.core.support.BaseLdapPathContextSource
import org.springframework.security.authentication.AuthenticationManager
import org.springframework.security.authentication.AuthenticationProvider
import org.springframework.security.authentication.DefaultAuthenticationEventPublisher
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
import org.springframework.security.authentication.event.AuthenticationSuccessEvent
import org.springframework.security.config.annotation.BaseSpringSpec
import org.springframework.security.config.annotation.authentication.configurers.ldap.LdapAuthenticationProviderConfigurer;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.Authentication
import org.springframework.security.core.authority.AuthorityUtils
import org.springframework.security.ldap.DefaultSpringSecurityContextSource
/**
* @author Rob Winch
*
*/
class WebSecurityConfigurerAdapterTests extends BaseSpringSpec {
def "MessageSources populated on AuthenticationProviders"() {
when:
loadConfig(MessageSourcesPopulatedConfig)
List<AuthenticationProvider> providers = authenticationProviders()
then:
providers*.messages*.messageSource == [context,context,context,context]
}
def "messages set when using WebSecurityConfigurerAdapter"() {
when:
loadConfig(InMemoryAuthWithWebSecurityConfigurerAdapter)
then:
authenticationManager.messages.messageSource instanceof ApplicationContext
}
def "AuthenticationEventPublisher is registered for Web registerAuthentication"() {
when:
loadConfig(InMemoryAuthWithWebSecurityConfigurerAdapter)
then:
authenticationManager.parent.eventPublisher instanceof DefaultAuthenticationEventPublisher
when:
Authentication token = new UsernamePasswordAuthenticationToken("user","password")
authenticationManager.authenticate(token)
then: "We only receive the AuthenticationSuccessEvent once"
InMemoryAuthWithWebSecurityConfigurerAdapter.EVENTS.size() == 1
InMemoryAuthWithWebSecurityConfigurerAdapter.EVENTS[0].authentication.name == token.principal
}
@EnableWebSecurity
@Configuration
static class InMemoryAuthWithWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter implements ApplicationListener<AuthenticationSuccessEvent> {
static List<AuthenticationSuccessEvent> EVENTS = []
@Bean
@Override
public AuthenticationManager authenticationManagerBean()
throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void registerAuthentication(AuthenticationManagerBuilder auth)
throws Exception {
auth
.inMemoryAuthentication()
.withUser("user").password("password").roles("USER")
}
@Override
public void onApplicationEvent(AuthenticationSuccessEvent e) {
EVENTS.add(e)
}
}
}

View File

@ -0,0 +1,76 @@
/*
* Copyright 2002-2013 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.annotation.web;
import javax.sql.DataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import org.springframework.ldap.core.support.BaseLdapPathContextSource;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.ldap.DefaultSpringSecurityContextSource;
/**
* @author Rob Winch
*
*/
public class WebSecurityConfigurerAdapterTestsConfigs {
// necessary because groovy resolves incorrect method when using generics
@Configuration
@EnableWebSecurity
static class MessageSourcesPopulatedConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/role1/**")
.authorizeUrls()
.anyRequest().hasRole("1");
}
@Bean
public BaseLdapPathContextSource contextSource() throws Exception {
DefaultSpringSecurityContextSource contextSource = new DefaultSpringSecurityContextSource("ldap://127.0.0.1:33389/dc=springframework,dc=org");
contextSource.setUserDn("uid=admin,ou=system");
contextSource.setPassword("secret");
return contextSource;
}
@Bean
public DataSource dataSource() {
EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder();
return builder.setType(EmbeddedDatabaseType.HSQL).build();
}
@Override
protected void registerAuthentication(AuthenticationManagerBuilder auth)
throws Exception {
auth
.inMemoryAuthentication().and()
.jdbcAuthentication()
.dataSource(dataSource())
.and()
.ldapAuthentication()
.userDnPatterns("uid={0},ou=people")
.contextSource(contextSource());
}
}
}

View File

@ -0,0 +1,120 @@
/*
* Copyright 2002-2013 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.annotation.web.builders
import javax.servlet.Filter
import javax.servlet.FilterChain
import javax.servlet.ServletException
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse
import org.springframework.beans.factory.BeanCreationException
import org.springframework.context.annotation.Configuration
import org.springframework.mock.web.MockFilterChain
import org.springframework.mock.web.MockHttpServletRequest
import org.springframework.mock.web.MockHttpServletResponse
import org.springframework.security.cas.web.CasAuthenticationFilter
import org.springframework.security.config.annotation.BaseSpringSpec
import org.springframework.security.config.annotation.web.configuration.BaseWebConfig
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter
import org.springframework.web.filter.OncePerRequestFilter
/**
* HttpSecurity tests
*
* @author Rob Winch
*
*/
public class HttpSecurityTests extends BaseSpringSpec {
def "addFilter with unregistered Filter"() {
when:
loadConfig(UnregisteredFilterConfig)
then:
BeanCreationException success = thrown()
success.message.contains "The Filter class ${UnregisteredFilter.name} does not have a registered order and cannot be added without a specified order. Consider using addFilterBefore or addFilterAfter instead."
}
@Configuration
static class UnregisteredFilterConfig extends BaseWebConfig {
protected void configure(HttpSecurity http) throws Exception {
http
.addFilter(new UnregisteredFilter())
}
}
static class UnregisteredFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
filterChain.doFilter(request, response);
}
}
// https://github.com/SpringSource/spring-security-javaconfig/issues/104
def "#104 addFilter CasAuthenticationFilter"() {
when:
loadConfig(CasAuthenticationFilterConfig)
then:
findFilter(CasAuthenticationFilter)
}
@Configuration
static class CasAuthenticationFilterConfig extends BaseWebConfig {
protected void configure(HttpSecurity http) throws Exception {
http
.addFilter(new CasAuthenticationFilter())
}
}
def "requestMatchers() javadoc"() {
setup: "load configuration like the config on the requestMatchers() javadoc"
loadConfig(RequestMatcherRegistryConfigs)
when:
super.setup()
request.servletPath = "/oauth/a"
springSecurityFilterChain.doFilter(request, response, chain)
then:
response.status == 403
where:
servletPath | status
"/oauth/a" | 403
"/oauth/b" | 403
"/api/a" | 403
"/api/b" | 403
"/oauth2/b" | 200
"/api2/b" | 200
}
@EnableWebSecurity
@Configuration
static class RequestMatcherRegistryConfigs extends BaseWebConfig {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.requestMatchers()
.antMatchers("/api/**")
.antMatchers("/oauth/**")
.and()
.authorizeUrls()
.antMatchers("/**").hasRole("USER")
.and()
.httpBasic()
}
}
}

View File

@ -0,0 +1,511 @@
/*
* Copyright 2002-2013 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.annotation.web.builders
import javax.servlet.http.HttpServletRequest
import org.springframework.context.annotation.Configuration
import org.springframework.security.access.AccessDecisionManager
import org.springframework.security.access.ConfigAttribute
import org.springframework.security.access.vote.AuthenticatedVoter
import org.springframework.security.access.vote.RoleVoter
import org.springframework.security.authentication.AuthenticationManager
import org.springframework.security.authentication.BadCredentialsException
import org.springframework.security.config.annotation.BaseSpringSpec
import org.springframework.security.config.annotation.web.builders.NamespaceHttpTests.AuthenticationManagerRefConfig.CustomAuthenticationManager
import org.springframework.security.config.annotation.web.builders.NamespaceHttpTests.RequestMatcherRefConfig.MyRequestMatcher
import org.springframework.security.config.annotation.web.configuration.BaseWebConfig
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.annotation.web.configurers.SessionCreationPolicy
import org.springframework.security.config.annotation.web.configurers.UrlAuthorizationConfigurer
import org.springframework.security.core.Authentication
import org.springframework.security.core.AuthenticationException
import org.springframework.security.web.FilterInvocation
import org.springframework.security.web.access.ExceptionTranslationFilter
import org.springframework.security.web.access.expression.ExpressionBasedFilterInvocationSecurityMetadataSource
import org.springframework.security.web.access.expression.WebExpressionVoter
import org.springframework.security.web.access.intercept.DefaultFilterInvocationSecurityMetadataSource
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter
import org.springframework.security.web.context.HttpSessionSecurityContextRepository
import org.springframework.security.web.context.NullSecurityContextRepository
import org.springframework.security.web.context.SecurityContextPersistenceFilter
import org.springframework.security.web.jaasapi.JaasApiIntegrationFilter
import org.springframework.security.web.savedrequest.HttpSessionRequestCache
import org.springframework.security.web.savedrequest.NullRequestCache
import org.springframework.security.web.savedrequest.RequestCacheAwareFilter
import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter
import org.springframework.security.web.session.SessionManagementFilter
import org.springframework.security.web.util.RegexRequestMatcher
import org.springframework.security.web.util.RequestMatcher
/**
* Tests to verify that all the functionality of <http> attributes is present
*
* @author Rob Winch
*
*/
public class NamespaceHttpTests extends BaseSpringSpec {
def "http@access-decision-manager-ref"() {
setup:
AccessDecisionManagerRefConfig.ACCESS_DECISION_MGR = Mock(AccessDecisionManager)
AccessDecisionManagerRefConfig.ACCESS_DECISION_MGR.supports(FilterInvocation) >> true
AccessDecisionManagerRefConfig.ACCESS_DECISION_MGR.supports(_ as ConfigAttribute) >> true
when:
loadConfig(AccessDecisionManagerRefConfig)
then:
findFilter(FilterSecurityInterceptor).accessDecisionManager == AccessDecisionManagerRefConfig.ACCESS_DECISION_MGR
}
@Configuration
static class AccessDecisionManagerRefConfig extends BaseWebConfig {
static AccessDecisionManager ACCESS_DECISION_MGR
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeUrls()
.anyRequest().permitAll()
.accessDecisionManager(ACCESS_DECISION_MGR)
}
}
def "http@access-denied-page"() {
when:
loadConfig(AccessDeniedPageConfig)
then:
findFilter(ExceptionTranslationFilter).accessDeniedHandler.errorPage == "/AccessDeniedPageConfig"
}
@Configuration
static class AccessDeniedPageConfig extends BaseWebConfig {
protected void configure(HttpSecurity http) throws Exception {
http
.exceptionHandling()
.accessDeniedPage("/AccessDeniedPageConfig")
}
}
def "http@authentication-manager-ref"() {
when: "Specify AuthenticationManager"
loadConfig(AuthenticationManagerRefConfig)
then: "Populates the AuthenticationManager"
findFilter(FilterSecurityInterceptor).authenticationManager.parent.class == CustomAuthenticationManager
}
@Configuration
static class AuthenticationManagerRefConfig extends BaseWebConfig {
// demo authentication-manager-ref (could be any value)
@Override
protected AuthenticationManager authenticationManager() throws Exception {
return new CustomAuthenticationManager();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeUrls()
.anyRequest().hasRole("USER");
}
static class CustomAuthenticationManager implements AuthenticationManager {
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
throw new BadCredentialsException("This always fails");
}
}
}
// Note: There is no http@auto-config equivalent in Java Config
def "http@create-session=always"() {
when:
loadConfig(IfRequiredConfig)
then:
findFilter(SecurityContextPersistenceFilter).forceEagerSessionCreation == false
findFilter(SecurityContextPersistenceFilter).repo.allowSessionCreation == true
findFilter(SessionManagementFilter).securityContextRepository.allowSessionCreation == true
findFilter(ExceptionTranslationFilter).requestCache.class == HttpSessionRequestCache
}
@Configuration
static class CreateSessionAlwaysConfig extends BaseWebConfig {
protected void configure(HttpSecurity http) throws Exception {
http
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.always);
}
}
def "http@create-session=stateless"() {
when:
loadConfig(CreateSessionStatelessConfig)
then:
findFilter(SecurityContextPersistenceFilter).forceEagerSessionCreation == false
findFilter(SecurityContextPersistenceFilter).repo.class == NullSecurityContextRepository
findFilter(SessionManagementFilter).securityContextRepository.class == NullSecurityContextRepository
findFilter(ExceptionTranslationFilter).requestCache.class == NullRequestCache
findFilter(RequestCacheAwareFilter).requestCache.class == NullRequestCache
}
@Configuration
static class CreateSessionStatelessConfig extends BaseWebConfig {
protected void configure(HttpSecurity http) throws Exception {
http
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.stateless);
}
}
def "http@create-session=ifRequired"() {
when:
loadConfig(IfRequiredConfig)
then:
findFilter(SecurityContextPersistenceFilter).forceEagerSessionCreation == false
findFilter(SecurityContextPersistenceFilter).repo.allowSessionCreation == true
findFilter(SessionManagementFilter).securityContextRepository.allowSessionCreation == true
}
@Configuration
static class IfRequiredConfig extends BaseWebConfig {
protected void configure(HttpSecurity http) throws Exception {
http
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.ifRequired);
}
}
def "http@create-session defaults to ifRequired"() {
when:
loadConfig(IfRequiredConfig)
then:
findFilter(SecurityContextPersistenceFilter).forceEagerSessionCreation == false
findFilter(SecurityContextPersistenceFilter).repo.allowSessionCreation == true
findFilter(SessionManagementFilter).securityContextRepository.allowSessionCreation == true
}
def "http@create-session=never"() {
when:
loadConfig(CreateSessionNeverConfig)
then:
findFilter(SecurityContextPersistenceFilter).forceEagerSessionCreation == false
findFilter(SecurityContextPersistenceFilter).repo.allowSessionCreation == false
findFilter(SessionManagementFilter).securityContextRepository.allowSessionCreation == false
}
@Configuration
static class CreateSessionNeverConfig extends BaseWebConfig {
protected void configure(HttpSecurity http) throws Exception {
http
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.never);
}
}
@Configuration
static class DefaultCreateSessionConfig extends BaseWebConfig {
protected void configure(HttpSecurity http) throws Exception {
}
}
def "http@disable-url-rewriting = true (default for Java Config)"() {
when:
loadConfig(DefaultUrlRewritingConfig)
then:
findFilter(SecurityContextPersistenceFilter).repo.disableUrlRewriting
}
@Configuration
static class DefaultUrlRewritingConfig extends BaseWebConfig {
protected void configure(HttpSecurity http) throws Exception {
}
}
// http@disable-url-rewriting is on by default to disable it create a custom HttpSecurityContextRepository and use security-context-repository-ref
def "http@disable-url-rewriting = false"() {
when:
loadConfig(EnableUrlRewritingConfig)
then:
findFilter(SecurityContextPersistenceFilter).repo.disableUrlRewriting == false
}
@Configuration
static class EnableUrlRewritingConfig extends BaseWebConfig {
protected void configure(HttpSecurity http) throws Exception {
HttpSessionSecurityContextRepository repository = new HttpSessionSecurityContextRepository()
repository.disableUrlRewriting = false // explicitly configured (not necessary due to default values)
http.
securityContext()
.securityContextRepository(repository)
}
}
def "http@entry-point-ref"() {
when:
loadConfig(EntryPointRefConfig)
then:
findFilter(ExceptionTranslationFilter).authenticationEntryPoint.loginFormUrl == "/EntryPointRefConfig"
}
@Configuration
static class EntryPointRefConfig extends BaseWebConfig {
protected void configure(HttpSecurity http) throws Exception {
http
.exceptionHandling()
.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/EntryPointRefConfig"))
}
}
def "http@jaas-api-provision"() {
when:
loadConfig(JaasApiProvisionConfig)
then:
findFilter(JaasApiIntegrationFilter)
}
@Configuration
static class JaasApiProvisionConfig extends BaseWebConfig {
protected void configure(HttpSecurity http) throws Exception {
http
.addFilter(new JaasApiIntegrationFilter())
}
}
// http@name is not available since it can be done w/ standard bean configuration easily
def "http@once-per-request=true"() {
when:
loadConfig(OncePerRequestConfig)
then:
findFilter(FilterSecurityInterceptor).observeOncePerRequest
}
@Configuration
static class OncePerRequestConfig extends BaseWebConfig {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeUrls()
.anyRequest().hasRole("USER");
}
}
def "http@once-per-request=false"() {
when:
loadConfig(OncePerRequestFalseConfig)
then:
!findFilter(FilterSecurityInterceptor).observeOncePerRequest
}
@Configuration
static class OncePerRequestFalseConfig extends BaseWebConfig {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.
authorizeUrls()
.filterSecurityInterceptorOncePerRequest(false)
.antMatchers("/users**","/sessions/**").hasRole("ADMIN")
.antMatchers("/signup").permitAll()
.anyRequest().hasRole("USER");
}
}
// http@path-type is not available (instead request matcher instances are used)
// http@pattern is not available (instead see the tests http@request-matcher-ref ant or http@request-matcher-ref regex)
def "http@realm"() {
when:
loadConfig(RealmConfig)
then:
findFilter(BasicAuthenticationFilter).authenticationEntryPoint.realmName == "RealmConfig"
}
@Configuration
static class RealmConfig extends BaseWebConfig {
protected void configure(HttpSecurity http) throws Exception {
http
.httpBasic().realmName("RealmConfig")
}
}
// http@request-matcher is not available (instead request matcher instances are used)
def "http@request-matcher-ref ant"() {
when:
loadConfig(RequestMatcherAntConfig)
then:
filterChain(0).requestMatcher.pattern == "/api/**"
}
@Configuration
static class RequestMatcherAntConfig extends BaseWebConfig {
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/api/**")
}
}
def "http@request-matcher-ref regex"() {
when:
loadConfig(RequestMatcherRegexConfig)
then:
filterChain(0).requestMatcher.class == RegexRequestMatcher
filterChain(0).requestMatcher.pattern.matcher("/regex/a")
filterChain(0).requestMatcher.pattern.matcher("/regex/b")
!filterChain(0).requestMatcher.pattern.matcher("/regex1/b")
}
@Configuration
static class RequestMatcherRegexConfig extends BaseWebConfig {
protected void configure(HttpSecurity http) throws Exception {
http
.regexMatcher("/regex/.*")
}
}
def "http@request-matcher-ref"() {
when:
loadConfig(RequestMatcherRefConfig)
then:
filterChain(0).requestMatcher.class == MyRequestMatcher
}
@Configuration
static class RequestMatcherRefConfig extends BaseWebConfig {
protected void configure(HttpSecurity http) throws Exception {
http
.requestMatcher(new MyRequestMatcher());
}
static class MyRequestMatcher implements RequestMatcher {
public boolean matches(HttpServletRequest request) {
return true;
}
}
}
def "http@security=none"() {
when:
loadConfig(SecurityNoneConfig)
then:
filterChain(0).requestMatcher.pattern == "/resources/**"
filterChain(0).filters.empty
filterChain(1).requestMatcher.pattern == "/public/**"
filterChain(1).filters.empty
}
@Configuration
static class SecurityNoneConfig extends BaseWebConfig {
@Override
public void configure(WebSecurity web)
throws Exception {
web
.ignoring()
.antMatchers("/resources/**","/public/**")
}
@Override
protected void configure(HttpSecurity http) throws Exception {}
}
def "http@security-context-repository-ref"() {
when:
loadConfig(SecurityContextRepoConfig)
then:
findFilter(SecurityContextPersistenceFilter).repo.class == NullSecurityContextRepository
}
@Configuration
static class SecurityContextRepoConfig extends BaseWebConfig {
protected void configure(HttpSecurity http) throws Exception {
http
.securityContext()
.securityContextRepository(new NullSecurityContextRepository()) // security-context-repository-ref
}
}
def "http@servlet-api-provision=false"() {
when:
loadConfig(ServletApiProvisionConfig)
then:
findFilter(SecurityContextHolderAwareRequestFilter) == null
}
@Configuration
static class ServletApiProvisionConfig extends BaseWebConfig {
protected void configure(HttpSecurity http) throws Exception {
http.servletApi().disable()
}
}
def "http@servlet-api-provision defaults to true"() {
when:
loadConfig(ServletApiProvisionDefaultsConfig)
then:
findFilter(SecurityContextHolderAwareRequestFilter) != null
}
@Configuration
static class ServletApiProvisionDefaultsConfig extends BaseWebConfig {
protected void configure(HttpSecurity http) throws Exception {
}
}
def "http@use-expressions=true"() {
when:
loadConfig(UseExpressionsConfig)
then:
findFilter(FilterSecurityInterceptor).securityMetadataSource.class == ExpressionBasedFilterInvocationSecurityMetadataSource
findFilter(FilterSecurityInterceptor).accessDecisionManager.decisionVoters.collect { it.class } == [WebExpressionVoter]
}
@Configuration
@EnableWebSecurity
static class UseExpressionsConfig extends BaseWebConfig {
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeUrls()
.antMatchers("/users**","/sessions/**").hasRole("USER")
.antMatchers("/signup").permitAll()
.anyRequest().hasRole("USER")
}
}
def "http@use-expressions=false"() {
when:
loadConfig(DisableUseExpressionsConfig)
then:
findFilter(FilterSecurityInterceptor).securityMetadataSource.class == DefaultFilterInvocationSecurityMetadataSource
findFilter(FilterSecurityInterceptor).accessDecisionManager.decisionVoters.collect { it.class } == [RoleVoter, AuthenticatedVoter]
}
@Configuration
@EnableWebSecurity
static class DisableUseExpressionsConfig extends BaseWebConfig {
protected void configure(HttpSecurity http) throws Exception {
http
.apply(new UrlAuthorizationConfigurer())
.antMatchers("/users**","/sessions/**").hasRole("USER")
.antMatchers("/signup").hasRole("ANONYMOUS")
.anyRequest().hasRole("USER")
}
}
}

View File

@ -0,0 +1,42 @@
/*
* Copyright 2002-2013 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.annotation.web.configuration;
import org.springframework.context.annotation.Configuration
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder
/**
*
* @author Rob Winch
*/
@Configuration
@EnableWebSecurity
public abstract class BaseWebConfig extends WebSecurityConfigurerAdapter {
BaseWebConfig(boolean disableDefaults) {
super(disableDefaults)
}
BaseWebConfig() {
}
protected void registerAuthentication(
AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser("user").password("password").roles("USER").and()
.withUser("admin").password("password").roles("USER", "ADMIN");
}
}

View File

@ -0,0 +1,88 @@
/*
* Copyright 2002-2013 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.annotation.web.configuration;
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.mock.web.MockHttpServletRequest
import org.springframework.security.authentication.AnonymousAuthenticationToken
import org.springframework.security.authentication.AuthenticationManager
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
import org.springframework.security.config.annotation.BaseSpringSpec
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder
import org.springframework.security.config.annotation.web.builders.DebugFilter;
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.web.authentication.AnonymousAuthenticationFilter
class EnableWebSecurityTests extends BaseSpringSpec {
def "@Bean(BeanIds.AUTHENTICATION_MANAGER) includes HttpSecurity's AuthenticationManagerBuilder"() {
when:
loadConfig(SecurityConfig)
AuthenticationManager authenticationManager = context.getBean(AuthenticationManager)
AnonymousAuthenticationToken anonymousAuthToken = findFilter(AnonymousAuthenticationFilter).createAuthentication(new MockHttpServletRequest())
then:
authenticationManager.authenticate(new UsernamePasswordAuthenticationToken("user", "password"))
authenticationManager.authenticate(anonymousAuthToken)
}
@EnableWebSecurity
@Configuration
static class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void registerAuthentication(AuthenticationManagerBuilder auth)
throws Exception {
auth
.inMemoryAuthentication()
.withUser("user").password("password").roles("USER");
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean()
throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeUrls()
.antMatchers("/*").hasRole("USER")
.and()
.formLogin();
}
}
def "@EnableWebSecurity on superclass"() {
when:
loadConfig(ChildSecurityConfig)
then:
context.getBean("springSecurityFilterChain", DebugFilter)
}
@Configuration
static class ChildSecurityConfig extends DebugSecurityConfig {
}
@Configuration
@EnableWebSecurity(debug=true)
static class DebugSecurityConfig extends WebSecurityConfigurerAdapter {
}
}

View File

@ -0,0 +1,217 @@
/*
* Copyright 2002-2013 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.annotation.web.configuration;
import static org.junit.Assert.*
import java.util.List;
import org.springframework.beans.factory.BeanCreationException
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration
import org.springframework.core.annotation.Order
import org.springframework.security.authentication.AuthenticationManager
import org.springframework.security.authentication.event.AuthenticationSuccessEvent;
import org.springframework.security.config.annotation.BaseSpringSpec
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.builders.WebSecurity
import org.springframework.security.web.FilterChainProxy
import org.springframework.security.web.SecurityFilterChain
import org.springframework.security.web.access.WebInvocationPrivilegeEvaluator;
import org.springframework.security.web.access.expression.WebSecurityExpressionHandler;
import org.springframework.security.web.util.AnyRequestMatcher
/**
* @author Rob Winch
*
*/
class WebSecurityConfigurationTests extends BaseSpringSpec {
def "WebSecurityConfigurers are sorted"() {
when:
loadConfig(SortedWebSecurityConfigurerAdaptersConfig);
List<SecurityFilterChain> filterChains = context.getBean(FilterChainProxy).filterChains
then:
filterChains[0].requestMatcher.pattern == "/ignore1"
filterChains[0].filters.empty
filterChains[1].requestMatcher.pattern == "/ignore2"
filterChains[1].filters.empty
filterChains[2].requestMatcher.pattern == "/role1/**"
filterChains[3].requestMatcher.pattern == "/role2/**"
filterChains[4].requestMatcher.pattern == "/role3/**"
filterChains[5].requestMatcher.class == AnyRequestMatcher
}
@Configuration
@EnableWebSecurity
static class SortedWebSecurityConfigurerAdaptersConfig {
public AuthenticationManager authenticationManager() throws Exception {
return new AuthenticationManagerBuilder()
.inMemoryAuthentication()
.withUser("marissa").password("koala").roles("USER").and()
.withUser("paul").password("emu").roles("USER").and()
.and()
.build();
}
@Configuration
@Order(1)
public static class WebConfigurer1 extends WebSecurityConfigurerAdapter {
@Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/ignore1","/ignore2");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/role1/**")
.authorizeUrls()
.anyRequest().hasRole("1");
}
}
@Configuration
@Order(2)
public static class WebConfigurer2 extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/role2/**")
.authorizeUrls()
.anyRequest().hasRole("2");
}
}
@Configuration
@Order(3)
public static class WebConfigurer3 extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/role3/**")
.authorizeUrls()
.anyRequest().hasRole("3");
}
}
@Configuration
public static class WebConfigurer4 extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeUrls()
.anyRequest().hasRole("4");
}
}
}
def "WebSecurityConfigurers fails with duplicate order"() {
when:
loadConfig(DuplicateOrderConfig);
then:
BeanCreationException e = thrown()
e.message.contains "@Order on WebSecurityConfigurers must be unique"
}
@Configuration
@EnableWebSecurity
static class DuplicateOrderConfig {
public AuthenticationManager authenticationManager() throws Exception {
return new AuthenticationManagerBuilder()
.inMemoryAuthentication()
.withUser("user").password("password").roles("USER").and()
.and()
.build();
}
@Configuration
public static class WebConfigurer1 extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/role1/**")
.authorizeUrls()
.anyRequest().hasRole("1");
}
}
@Configuration
public static class WebConfigurer2 extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/role2/**")
.authorizeUrls()
.anyRequest().hasRole("2");
}
}
}
def "Override privilegeEvaluator"() {
setup:
WebInvocationPrivilegeEvaluator privilegeEvaluator = Mock()
PrivilegeEvaluatorConfigurerAdapterConfig.PE = privilegeEvaluator
when:
loadConfig(PrivilegeEvaluatorConfigurerAdapterConfig)
then:
context.getBean(WebInvocationPrivilegeEvaluator) == privilegeEvaluator
}
@EnableWebSecurity
@Configuration
static class PrivilegeEvaluatorConfigurerAdapterConfig extends WebSecurityConfigurerAdapter {
static WebInvocationPrivilegeEvaluator PE
@Override
public void configure(WebSecurity web) throws Exception {
web
.privilegeEvaluator(PE)
}
}
def "Override webSecurityExpressionHandler"() {
setup:
WebSecurityExpressionHandler expressionHandler = Mock()
WebSecurityExpressionHandlerConfig.EH = expressionHandler
when:
loadConfig(WebSecurityExpressionHandlerConfig)
then:
context.getBean(WebSecurityExpressionHandler) == expressionHandler
}
@EnableWebSecurity
@Configuration
static class WebSecurityExpressionHandlerConfig extends WebSecurityConfigurerAdapter {
@SuppressWarnings("deprecation")
static WebSecurityExpressionHandler EH
@Override
public void configure(WebSecurity web) throws Exception {
web
.expressionHandler(EH)
}
}
}

View File

@ -0,0 +1,76 @@
/*
* Copyright 2002-2013 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.annotation.web.configurers;
import java.util.List;
import org.springframework.http.HttpMethod;
import org.springframework.security.access.AccessDecisionVoter;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractRequestMatcherMappingConfigurer;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.security.web.util.AntPathRequestMatcher;
import org.springframework.security.web.util.RegexRequestMatcher;
import org.springframework.security.web.util.RequestMatcher;
import spock.lang.Specification;
/**
* @author Rob Winch
*
*/
class AbstractRequestMatcherMappingConfigurerTests extends Specification {
ConcreteAbstractRequestMatcherMappingConfigurer registry = new ConcreteAbstractRequestMatcherMappingConfigurer()
def "regexMatchers(GET,'/a.*') uses RegexRequestMatcher"() {
when:
def matchers = registry.regexMatchers(HttpMethod.GET,"/a.*")
then: 'matcher is a RegexRequestMatcher'
matchers.collect {it.class } == [RegexRequestMatcher]
}
def "regexMatchers('/a.*') uses RegexRequestMatcher"() {
when:
def matchers = registry.regexMatchers("/a.*")
then: 'matcher is a RegexRequestMatcher'
matchers.collect {it.class } == [RegexRequestMatcher]
}
def "antMatchers(GET,'/a.*') uses AntPathRequestMatcher"() {
when:
def matchers = registry.antMatchers(HttpMethod.GET, "/a.*")
then: 'matcher is a RegexRequestMatcher'
matchers.collect {it.class } == [AntPathRequestMatcher]
}
def "antMatchers('/a.*') uses AntPathRequestMatcher"() {
when:
def matchers = registry.antMatchers("/a.*")
then: 'matcher is a AntPathRequestMatcher'
matchers.collect {it.class } == [AntPathRequestMatcher]
}
static class ConcreteAbstractRequestMatcherMappingConfigurer extends AbstractRequestMatcherMappingConfigurer<HttpSecurity,List<RequestMatcher>,DefaultSecurityFilterChain> {
List<AccessDecisionVoter> decisionVoters() {
return null;
}
List<RequestMatcher> chainRequestMatchersInternal(List<RequestMatcher> requestMatchers) {
return requestMatchers;
}
}
}

View File

@ -0,0 +1,53 @@
/*
* Copyright 2002-2013 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.annotation.web.configurers
import org.springframework.security.config.annotation.AnyObjectPostProcessor
import org.springframework.security.config.annotation.BaseSpringSpec
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.access.channel.ChannelDecisionManagerImpl
import org.springframework.security.web.access.channel.ChannelProcessingFilter
import org.springframework.security.web.access.channel.InsecureChannelProcessor
import org.springframework.security.web.access.channel.SecureChannelProcessor
/**
*
* @author Rob Winch
*/
class ChannelSecurityConfigurerTests extends BaseSpringSpec {
def "requiresChannel ObjectPostProcessor"() {
setup: "initialize the AUTH_FILTER as a mock"
AnyObjectPostProcessor objectPostProcessor = Mock()
when:
HttpSecurity http = new HttpSecurity(objectPostProcessor, authenticationBldr, [:])
http
.requiresChannel()
.anyRequest().requiresSecure()
.and()
.build()
then: "InsecureChannelProcessor is registered with LifecycleManager"
1 * objectPostProcessor.postProcess(_ as InsecureChannelProcessor) >> {InsecureChannelProcessor o -> o}
and: "SecureChannelProcessor is registered with LifecycleManager"
1 * objectPostProcessor.postProcess(_ as SecureChannelProcessor) >> {SecureChannelProcessor o -> o}
and: "ChannelDecisionManagerImpl is registered with LifecycleManager"
1 * objectPostProcessor.postProcess(_ as ChannelDecisionManagerImpl) >> {ChannelDecisionManagerImpl o -> o}
and: "ChannelProcessingFilter is registered with LifecycleManager"
1 * objectPostProcessor.postProcess(_ as ChannelProcessingFilter) >> {ChannelProcessingFilter o -> o}
}
}

View File

@ -0,0 +1,159 @@
/*
* Copyright 2002-2013 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.annotation.web.configurers
import org.springframework.beans.factory.BeanCreationException
import org.springframework.context.annotation.AnnotationConfigApplicationContext
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.mock.web.MockFilterChain
import org.springframework.mock.web.MockHttpServletRequest
import org.springframework.mock.web.MockHttpServletResponse
import org.springframework.security.config.annotation.BaseSpringSpec
import org.springframework.security.config.annotation.web.WebSecurityConfigurer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.BaseWebConfig;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.DefaultSecurityFilterChain
import org.springframework.security.web.FilterChainProxy
import org.springframework.security.web.access.ExceptionTranslationFilter
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor
import org.springframework.security.web.authentication.AnonymousAuthenticationFilter
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter
import org.springframework.security.web.authentication.logout.LogoutFilter
import org.springframework.security.web.context.SecurityContextPersistenceFilter
import org.springframework.security.web.savedrequest.RequestCacheAwareFilter
import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter
import org.springframework.security.web.session.SessionManagementFilter;
import org.springframework.security.web.util.AnyRequestMatcher
/**
*
* @author Rob Winch
*/
class DefaultFiltersTests extends BaseSpringSpec {
def missingConfigMessage = "At least one non-null instance of "+ WebSecurityConfigurer.class.getSimpleName()+" must be exposed as a @Bean when using @EnableWebSecurity. Hint try extending "+ WebSecurityConfigurerAdapter.class.getSimpleName()
def "DefaultSecurityFilterChainBuilder cannot be null"() {
when:
context = new AnnotationConfigApplicationContext(FilterChainProxyBuilderMissingConfig)
then:
BeanCreationException e = thrown()
e.message.contains missingConfigMessage
}
@Configuration
@EnableWebSecurity
static class FilterChainProxyBuilderMissingConfig { }
def "FilterChainProxyBuilder no DefaultSecurityFilterChainBuilder specified"() {
when:
context = new AnnotationConfigApplicationContext(FilterChainProxyBuilderNoSecurityFilterBuildersConfig)
then:
BeanCreationException e = thrown()
e.message.contains missingConfigMessage
}
@Configuration
@EnableWebSecurity
static class FilterChainProxyBuilderNoSecurityFilterBuildersConfig {
@Bean
public WebSecurity filterChainProxyBuilder() {
new WebSecurity()
.ignoring()
.antMatchers("/resources/**")
}
}
def "null WebInvocationPrivilegeEvaluator"() {
when:
context = new AnnotationConfigApplicationContext(NullWebInvocationPrivilegeEvaluatorConfig)
then:
List<DefaultSecurityFilterChain> filterChains = context.getBean(FilterChainProxy).filterChains
filterChains.size() == 1
filterChains[0].requestMatcher instanceof AnyRequestMatcher
filterChains[0].filters.size() == 1
filterChains[0].filters.find { it instanceof UsernamePasswordAuthenticationFilter }
}
@Configuration
@EnableWebSecurity
static class NullWebInvocationPrivilegeEvaluatorConfig extends BaseWebConfig {
NullWebInvocationPrivilegeEvaluatorConfig() {
super(true)
}
protected void configure(HttpSecurity http) {
http.formLogin()
}
}
def "FilterChainProxyBuilder ignoring resources"() {
when:
context = new AnnotationConfigApplicationContext(FilterChainProxyBuilderIgnoringConfig)
then:
List<DefaultSecurityFilterChain> filterChains = context.getBean(FilterChainProxy).filterChains
filterChains.size() == 2
filterChains[0].requestMatcher.pattern == '/resources/**'
filterChains[0].filters.empty
filterChains[1].requestMatcher instanceof AnyRequestMatcher
filterChains[1].filters.collect { it.class } ==
[SecurityContextPersistenceFilter, LogoutFilter, RequestCacheAwareFilter,
SecurityContextHolderAwareRequestFilter, AnonymousAuthenticationFilter, SessionManagementFilter,
ExceptionTranslationFilter, FilterSecurityInterceptor ]
}
@Configuration
@EnableWebSecurity
static class FilterChainProxyBuilderIgnoringConfig extends BaseWebConfig {
@Override
public void configure(WebSecurity builder) throws Exception {
builder
.ignoring()
.antMatchers("/resources/**");
}
@Override
protected void configure(HttpSecurity http) {
http
.authorizeUrls()
.anyRequest().hasRole("USER");
}
}
def "DefaultFilters.permitAll()"() {
when:
context = new AnnotationConfigApplicationContext(DefaultFiltersConfigPermitAll)
then:
FilterChainProxy filterChain = context.getBean(FilterChainProxy)
expect:
MockHttpServletResponse response = new MockHttpServletResponse()
filterChain.doFilter(new MockHttpServletRequest(servletPath : uri, queryString: query), response, new MockFilterChain())
response.redirectedUrl == null
where:
uri | query
"/logout" | null
}
@Configuration
@EnableWebSecurity
static class DefaultFiltersConfigPermitAll extends BaseWebConfig {
protected void configure(HttpSecurity http) {
}
}
}

View File

@ -0,0 +1,343 @@
/*
* Copyright 2002-2013 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.annotation.web.configurers
import javax.servlet.http.HttpSession
import org.springframework.context.annotation.Configuration
import org.springframework.mock.web.MockFilterChain
import org.springframework.mock.web.MockHttpServletRequest
import org.springframework.mock.web.MockHttpServletResponse
import org.springframework.security.config.annotation.AnyObjectPostProcessor
import org.springframework.security.config.annotation.BaseSpringSpec
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.BaseWebConfig;
import org.springframework.security.config.annotation.web.configurers.DefaultLoginPageConfigurer;
import org.springframework.security.web.FilterChainProxy
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter
import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler
import org.springframework.security.web.authentication.ui.DefaultLoginPageViewFilter;
/**
* Tests to verify that {@link DefaultLoginPageConfigurer} works
*
* @author Rob Winch
*
*/
public class DefaultLoginPageConfigurerTests extends BaseSpringSpec {
FilterChainProxy springSecurityFilterChain
MockHttpServletRequest request
MockHttpServletResponse response
MockFilterChain chain
def setup() {
request = new MockHttpServletRequest(method:"GET")
response = new MockHttpServletResponse()
chain = new MockFilterChain()
}
def "http/form-login default login generating page"() {
setup:
loadConfig(DefaultLoginPageConfig)
springSecurityFilterChain = context.getBean(FilterChainProxy)
when:
springSecurityFilterChain.doFilter(request,response,chain)
then:
findFilter(DefaultLoginPageViewFilter)
response.getRedirectedUrl() == "http://localhost/login"
when: "request the login page"
setup()
request.requestURI = "/login"
springSecurityFilterChain.doFilter(request,response,chain)
then:
response.getContentAsString() == """<html><head><title>Login Page</title></head><body onload='document.f.username.focus();'>
<h3>Login with Username and Password</h3><form name='f' action='/login' method='POST'>
<table>
<tr><td>User:</td><td><input type='text' name='username' value=''></td></tr>
<tr><td>Password:</td><td><input type='password' name='password'/></td></tr>
<tr><td colspan='2'><input name="submit" type="submit" value="Login"/></td></tr>
</table>
</form></body></html>"""
when: "fail to log in"
setup()
request.requestURI = "/login"
request.method = "POST"
springSecurityFilterChain.doFilter(request,response,chain)
then: "sent to login error page"
response.getRedirectedUrl() == "/login?error"
when: "request the error page"
HttpSession session = request.session
setup()
request.session = session
request.requestURI = "/login"
request.queryString = "error"
springSecurityFilterChain.doFilter(request,response,chain)
then:
response.getContentAsString() == """<html><head><title>Login Page</title></head><body onload='document.f.username.focus();'>
<p><font color='red'>Your login attempt was not successful, try again.<br/><br/>Reason: Bad credentials</font></p><h3>Login with Username and Password</h3><form name='f' action='/login' method='POST'>
<table>
<tr><td>User:</td><td><input type='text' name='username' value=''></td></tr>
<tr><td>Password:</td><td><input type='password' name='password'/></td></tr>
<tr><td colspan='2'><input name="submit" type="submit" value="Login"/></td></tr>
</table>
</form></body></html>"""
when: "login success"
setup()
request.requestURI = "/login"
request.method = "POST"
request.parameters.username = ["user"] as String[]
request.parameters.password = ["password"] as String[]
springSecurityFilterChain.doFilter(request,response,chain)
then: "sent to default succes page"
response.getRedirectedUrl() == "/"
}
def "logout success renders"() {
setup:
loadConfig(DefaultLoginPageConfig)
springSecurityFilterChain = context.getBean(FilterChainProxy)
when: "logout success"
request.requestURI = "/login"
request.queryString = "logout"
request.method = "GET"
springSecurityFilterChain.doFilter(request,response,chain)
then: "sent to default success page"
response.getContentAsString() == """<html><head><title>Login Page</title></head><body onload='document.f.username.focus();'>
<p><font color='green'>You have been logged out</font></p><h3>Login with Username and Password</h3><form name='f' action='/login' method='POST'>
<table>
<tr><td>User:</td><td><input type='text' name='username' value=''></td></tr>
<tr><td>Password:</td><td><input type='password' name='password'/></td></tr>
<tr><td colspan='2'><input name="submit" type="submit" value="Login"/></td></tr>
</table>
</form></body></html>"""
}
@Configuration
static class DefaultLoginPageConfig extends BaseWebConfig {
@Override
protected void configure(HttpSecurity http) {
http
.authorizeUrls()
.anyRequest().hasRole("USER")
.and()
.formLogin()
}
}
def "custom logout success handler prevents rendering"() {
setup:
loadConfig(DefaultLoginPageCustomLogoutSuccessHandlerConfig)
springSecurityFilterChain = context.getBean(FilterChainProxy)
when: "logout success"
request.requestURI = "/login"
request.queryString = "logout"
request.method = "GET"
springSecurityFilterChain.doFilter(request,response,chain)
then: "default success page is NOT rendered (application is in charge of it)"
response.getContentAsString() == ""
}
@Configuration
static class DefaultLoginPageCustomLogoutSuccessHandlerConfig extends BaseWebConfig {
@Override
protected void configure(HttpSecurity http) {
http
.authorizeUrls()
.anyRequest().hasRole("USER")
.and()
.logout()
.logoutSuccessHandler(new SimpleUrlLogoutSuccessHandler())
.and()
.formLogin()
}
}
def "custom logout success url prevents rendering"() {
setup:
loadConfig(DefaultLoginPageCustomLogoutConfig)
springSecurityFilterChain = context.getBean(FilterChainProxy)
when: "logout success"
request.requestURI = "/login"
request.queryString = "logout"
request.method = "GET"
springSecurityFilterChain.doFilter(request,response,chain)
then: "default success page is NOT rendered (application is in charge of it)"
response.getContentAsString() == ""
}
@Configuration
static class DefaultLoginPageCustomLogoutConfig extends BaseWebConfig {
@Override
protected void configure(HttpSecurity http) {
http
.authorizeUrls()
.anyRequest().hasRole("USER")
.and()
.logout()
.logoutSuccessUrl("/login?logout")
.and()
.formLogin()
}
}
def "http/form-login default login with remember me"() {
setup:
loadConfig(DefaultLoginPageWithRememberMeConfig)
springSecurityFilterChain = context.getBean(FilterChainProxy)
when: "request the login page"
setup()
request.requestURI = "/login"
springSecurityFilterChain.doFilter(request,response,chain)
then:
response.getContentAsString() == """<html><head><title>Login Page</title></head><body onload='document.f.username.focus();'>
<h3>Login with Username and Password</h3><form name='f' action='/login' method='POST'>
<table>
<tr><td>User:</td><td><input type='text' name='username' value=''></td></tr>
<tr><td>Password:</td><td><input type='password' name='password'/></td></tr>
<tr><td><input type='checkbox' name='remember-me'/></td><td>Remember me on this computer.</td></tr>
<tr><td colspan='2'><input name="submit" type="submit" value="Login"/></td></tr>
</table>
</form></body></html>"""
}
@Configuration
static class DefaultLoginPageWithRememberMeConfig extends BaseWebConfig {
@Override
protected void configure(HttpSecurity http) {
http
.authorizeUrls()
.anyRequest().hasRole("USER")
.and()
.formLogin()
.and()
.rememberMe()
}
}
def "http/form-login default login with openid"() {
setup:
loadConfig(DefaultLoginPageWithOpenIDConfig)
springSecurityFilterChain = context.getBean(FilterChainProxy)
when: "request the login page"
request.requestURI = "/login"
springSecurityFilterChain.doFilter(request,response,chain)
then:
response.getContentAsString() == """<html><head><title>Login Page</title></head><h3>Login with OpenID Identity</h3><form name='oidf' action='/login/openid' method='POST'>
<table>
<tr><td>Identity:</td><td><input type='text' size='30' name='openid_identifier'/></td></tr>
<tr><td colspan='2'><input name="submit" type="submit" value="Login"/></td></tr>
</table>
</form></body></html>"""
}
@Configuration
static class DefaultLoginPageWithOpenIDConfig extends BaseWebConfig {
@Override
protected void configure(HttpSecurity http) {
http
.authorizeUrls()
.anyRequest().hasRole("USER")
.and()
.openidLogin()
}
}
def "http/form-login default login with openid, form login, and rememberme"() {
setup:
loadConfig(DefaultLoginPageWithFormLoginOpenIDRememberMeConfig)
springSecurityFilterChain = context.getBean(FilterChainProxy)
when: "request the login page"
request.requestURI = "/login"
springSecurityFilterChain.doFilter(request,response,chain)
then:
response.getContentAsString() == """<html><head><title>Login Page</title></head><body onload='document.f.username.focus();'>
<h3>Login with Username and Password</h3><form name='f' action='/login' method='POST'>
<table>
<tr><td>User:</td><td><input type='text' name='username' value=''></td></tr>
<tr><td>Password:</td><td><input type='password' name='password'/></td></tr>
<tr><td><input type='checkbox' name='remember-me'/></td><td>Remember me on this computer.</td></tr>
<tr><td colspan='2'><input name="submit" type="submit" value="Login"/></td></tr>
</table>
</form><h3>Login with OpenID Identity</h3><form name='oidf' action='/login/openid' method='POST'>
<table>
<tr><td>Identity:</td><td><input type='text' size='30' name='openid_identifier'/></td></tr>
<tr><td><input type='checkbox' name='remember-me'></td><td>Remember me on this computer.</td></tr>
<tr><td colspan='2'><input name="submit" type="submit" value="Login"/></td></tr>
</table>
</form></body></html>"""
}
@Configuration
static class DefaultLoginPageWithFormLoginOpenIDRememberMeConfig extends BaseWebConfig {
@Override
protected void configure(HttpSecurity http) {
http
.authorizeUrls()
.anyRequest().hasRole("USER")
.and()
.rememberMe()
.and()
.formLogin()
.and()
.openidLogin()
}
}
def "default login with custom AuthenticationEntryPoint"() {
when:
loadConfig(DefaultLoginWithCustomAuthenticationEntryPointConfig)
then:
!findFilter(DefaultLoginPageViewFilter)
}
@Configuration
static class DefaultLoginWithCustomAuthenticationEntryPointConfig extends BaseWebConfig {
@Override
protected void configure(HttpSecurity http) {
http
.exceptionHandling()
.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login"))
.and()
.authorizeUrls()
.anyRequest().hasRole("USER")
.and()
.formLogin()
}
}
def "DefaultLoginPage ObjectPostProcessor"() {
setup:
AnyObjectPostProcessor objectPostProcessor = Mock()
when:
HttpSecurity http = new HttpSecurity(objectPostProcessor, authenticationBldr, [:])
DefaultLoginPageConfigurer defaultLoginConfig = new DefaultLoginPageConfigurer([builder:http])
defaultLoginConfig.addObjectPostProcessor(objectPostProcessor)
http
// must set builder manually due to groovy not selecting correct method
.apply(defaultLoginConfig).and()
.formLogin()
.and()
.build()
then: "DefaultLoginPageGeneratingFilter is registered with LifecycleManager"
1 * objectPostProcessor.postProcess(_ as DefaultLoginPageViewFilter) >> {DefaultLoginPageViewFilter o -> o}
1 * objectPostProcessor.postProcess(_ as UsernamePasswordAuthenticationFilter) >> {UsernamePasswordAuthenticationFilter o -> o}
1 * objectPostProcessor.postProcess(_ as LoginUrlAuthenticationEntryPoint) >> {LoginUrlAuthenticationEntryPoint o -> o}
}
}

View File

@ -0,0 +1,43 @@
/*
* Copyright 2002-2013 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.annotation.web.configurers
import org.springframework.security.config.annotation.AnyObjectPostProcessor
import org.springframework.security.config.annotation.BaseSpringSpec
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.access.ExceptionTranslationFilter
/**
*
* @author Rob Winch
*/
class ExceptionHandlingConfigurerTests extends BaseSpringSpec {
def "exception ObjectPostProcessor"() {
setup: "initialize the AUTH_FILTER as a mock"
AnyObjectPostProcessor opp = Mock()
when:
HttpSecurity http = new HttpSecurity(opp, authenticationBldr, [:])
http
.exceptionHandling()
.and()
.build()
then: "ExceptionTranslationFilter is registered with LifecycleManager"
1 * opp.postProcess(_ as ExceptionTranslationFilter) >> {ExceptionTranslationFilter o -> o}
}
}

View File

@ -0,0 +1,413 @@
/*
* Copyright 2002-2013 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.annotation.web.configurers;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.vote.AffirmativeBased;
import org.springframework.security.authentication.RememberMeAuthenticationToken
import org.springframework.security.config.annotation.BaseSpringSpec
import org.springframework.security.config.annotation.SecurityExpressions.*
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
public class ExpressionUrlAuthorizationConfigurerTests extends BaseSpringSpec {
def "hasAnyAuthority('ROLE_USER')"() {
when:
def expression = ExpressionUrlAuthorizationConfigurer.hasAnyAuthority("ROLE_USER")
then:
expression == "hasAnyAuthority('ROLE_USER')"
}
def "hasAnyAuthority('ROLE_USER','ROLE_ADMIN')"() {
when:
def expression = ExpressionUrlAuthorizationConfigurer.hasAnyAuthority("ROLE_USER","ROLE_ADMIN")
then:
expression == "hasAnyAuthority('ROLE_USER','ROLE_ADMIN')"
}
def "hasRole('ROLE_USER') is rejected due to starting with ROLE_"() {
when:
def expression = ExpressionUrlAuthorizationConfigurer.hasRole("ROLE_USER")
then:
IllegalArgumentException e = thrown()
e.message == "role should not start with 'ROLE_' since it is automatically inserted. Got 'ROLE_USER'"
}
def "authorizeUrls() uses AffirmativeBased AccessDecisionManager"() {
when: "Load Config with no specific AccessDecisionManager"
loadConfig(NoSpecificAccessDecessionManagerConfig)
then: "AccessDecessionManager matches the HttpSecurityBuilder's default"
findFilter(FilterSecurityInterceptor).accessDecisionManager.class == AffirmativeBased
}
@EnableWebSecurity
@Configuration
static class NoSpecificAccessDecessionManagerConfig extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeUrls()
.anyRequest().hasRole("USER")
}
}
def "authorizeUrls() no requests"() {
when: "Load Config with no requests"
loadConfig(NoRequestsConfig)
then: "A meaningful exception is thrown"
BeanCreationException success = thrown()
success.message.contains "At least one mapping is required (i.e. authorizeUrls().anyRequest.authenticated())"
}
@EnableWebSecurity
@Configuration
static class NoRequestsConfig extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeUrls()
}
}
def "authorizeUrls() incomplete mapping"() {
when: "Load Config with incomplete mapping"
loadConfig(IncompleteMappingConfig)
then: "A meaningful exception is thrown"
BeanCreationException success = thrown()
success.message.contains "An incomplete mapping was found for "
}
@EnableWebSecurity
@Configuration
static class IncompleteMappingConfig extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeUrls()
.antMatchers("/a").authenticated()
.anyRequest()
}
}
def "authorizeUrls() hasAuthority"() {
setup:
loadConfig(HasAuthorityConfig)
when:
springSecurityFilterChain.doFilter(request,response,chain)
then:
response.status == 403
when:
super.setup()
login()
springSecurityFilterChain.doFilter(request,response,chain)
then:
response.status == 200
when:
super.setup()
login("user","ROLE_INVALID")
springSecurityFilterChain.doFilter(request,response,chain)
then:
response.status == 403
}
@EnableWebSecurity
@Configuration
static class HasAuthorityConfig extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http) throws Exception {
http
.httpBasic()
.and()
.authorizeUrls()
.anyRequest().hasAuthority("ROLE_USER")
}
}
def "authorizeUrls() hasAnyAuthority"() {
setup:
loadConfig(HasAnyAuthorityConfig)
when:
springSecurityFilterChain.doFilter(request,response,chain)
then:
response.status == 403
when:
super.setup()
login("user","ROLE_ADMIN")
springSecurityFilterChain.doFilter(request,response,chain)
then:
response.status == 200
when:
super.setup()
login("user","ROLE_DBA")
springSecurityFilterChain.doFilter(request,response,chain)
then:
response.status == 200
when:
super.setup()
login("user","ROLE_INVALID")
springSecurityFilterChain.doFilter(request,response,chain)
then:
response.status == 403
}
@EnableWebSecurity
@Configuration
static class HasAnyAuthorityConfig extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http) throws Exception {
http
.httpBasic()
.and()
.authorizeUrls()
.anyRequest().hasAnyAuthority("ROLE_ADMIN","ROLE_DBA")
}
}
def "authorizeUrls() hasIpAddress"() {
setup:
loadConfig(HasIpAddressConfig)
when:
request.remoteAddr = "192.168.1.1"
springSecurityFilterChain.doFilter(request,response,chain)
then:
response.status == 403
when:
super.setup()
request.remoteAddr = "192.168.1.0"
springSecurityFilterChain.doFilter(request,response,chain)
then:
response.status == 200
}
@EnableWebSecurity
@Configuration
static class HasIpAddressConfig extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http) throws Exception {
http
.httpBasic()
.and()
.authorizeUrls()
.anyRequest().hasIpAddress("192.168.1.0")
}
}
def "authorizeUrls() anonymous"() {
setup:
loadConfig(AnonymousConfig)
when:
springSecurityFilterChain.doFilter(request,response,chain)
then:
response.status == 200
when:
super.setup()
login()
springSecurityFilterChain.doFilter(request,response,chain)
then:
response.status == 403
}
@EnableWebSecurity
@Configuration
static class AnonymousConfig extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http) throws Exception {
http
.httpBasic()
.and()
.authorizeUrls()
.anyRequest().anonymous()
}
}
def "authorizeUrls() rememberMe"() {
setup:
loadConfig(RememberMeConfig)
when:
springSecurityFilterChain.doFilter(request,response,chain)
then:
response.status == 403
when:
super.setup()
login(new RememberMeAuthenticationToken("key", "user", AuthorityUtils.createAuthorityList("ROLE_USER")))
springSecurityFilterChain.doFilter(request,response,chain)
then:
response.status == 200
}
@EnableWebSecurity
@Configuration
static class RememberMeConfig extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http) throws Exception {
http
.rememberMe()
.and()
.httpBasic()
.and()
.authorizeUrls()
.anyRequest().rememberMe()
}
@Override
protected void registerAuthentication(AuthenticationManagerBuilder auth)
throws Exception {
auth
.inMemoryAuthentication()
.withUser("user").password("password").roles("USER")
}
}
def "authorizeUrls() denyAll"() {
setup:
loadConfig(DenyAllConfig)
when:
springSecurityFilterChain.doFilter(request,response,chain)
then:
response.status == 403
when:
super.setup()
login(new RememberMeAuthenticationToken("key", "user", AuthorityUtils.createAuthorityList("ROLE_USER")))
springSecurityFilterChain.doFilter(request,response,chain)
then:
response.status == 403
}
@EnableWebSecurity
@Configuration
static class DenyAllConfig extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http) throws Exception {
http
.httpBasic()
.and()
.authorizeUrls()
.anyRequest().denyAll()
}
}
def "authorizeUrls() not denyAll"() {
setup:
loadConfig(NotDenyAllConfig)
when:
springSecurityFilterChain.doFilter(request,response,chain)
then:
response.status == 200
when:
super.setup()
login(new RememberMeAuthenticationToken("key", "user", AuthorityUtils.createAuthorityList("ROLE_USER")))
springSecurityFilterChain.doFilter(request,response,chain)
then:
response.status == 200
}
@EnableWebSecurity
@Configuration
static class NotDenyAllConfig extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http) throws Exception {
http
.httpBasic()
.and()
.authorizeUrls()
.anyRequest().not().denyAll()
}
}
def "authorizeUrls() fullyAuthenticated"() {
setup:
loadConfig(FullyAuthenticatedConfig)
when:
springSecurityFilterChain.doFilter(request,response,chain)
then:
response.status == 403
when:
super.setup()
login(new RememberMeAuthenticationToken("key", "user", AuthorityUtils.createAuthorityList("ROLE_USER")))
springSecurityFilterChain.doFilter(request,response,chain)
then:
response.status == 403
when:
super.setup()
login()
springSecurityFilterChain.doFilter(request,response,chain)
then:
response.status == 200
}
@EnableWebSecurity
@Configuration
static class FullyAuthenticatedConfig extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http) throws Exception {
http
.rememberMe()
.and()
.httpBasic()
.and()
.authorizeUrls()
.anyRequest().fullyAuthenticated()
}
@Override
protected void registerAuthentication(AuthenticationManagerBuilder auth)
throws Exception {
auth
.inMemoryAuthentication()
.withUser("user").password("password").roles("USER")
}
}
def "authorizeUrls() access"() {
setup:
loadConfig(AccessConfig)
when:
springSecurityFilterChain.doFilter(request,response,chain)
then: "Access is granted due to GET"
response.status == 200
when:
super.setup()
login()
request.method = "POST"
springSecurityFilterChain.doFilter(request,response,chain)
then: "Access is granted due to role"
response.status == 200
when:
super.setup()
request.method = "POST"
springSecurityFilterChain.doFilter(request,response,chain)
then: "Access is denied"
response.status == 403
}
@EnableWebSecurity
@Configuration
static class AccessConfig extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http) throws Exception {
http
.rememberMe()
.and()
.httpBasic()
.and()
.authorizeUrls()
.anyRequest().access("hasRole('ROLE_USER') or request.method == 'GET'")
}
@Override
protected void registerAuthentication(AuthenticationManagerBuilder auth)
throws Exception {
auth
.inMemoryAuthentication()
.withUser("user").password("password").roles("USER")
}
}
}

View File

@ -0,0 +1,214 @@
/*
* Copyright 2002-2013 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.annotation.web.configurers
import org.springframework.context.annotation.AnnotationConfigApplicationContext
import org.springframework.context.annotation.Configuration
import org.springframework.mock.web.MockFilterChain
import org.springframework.mock.web.MockHttpServletRequest
import org.springframework.mock.web.MockHttpServletResponse
import org.springframework.security.authentication.BadCredentialsException
import org.springframework.security.config.annotation.AnyObjectPostProcessor
import org.springframework.security.config.annotation.BaseSpringSpec
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.builders.WebSecurity
import org.springframework.security.config.annotation.web.configuration.BaseWebConfig
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.web.AuthenticationEntryPoint
import org.springframework.security.web.FilterChainProxy
import org.springframework.security.web.PortMapper
import org.springframework.security.web.access.ExceptionTranslationFilter
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor
import org.springframework.security.web.authentication.AnonymousAuthenticationFilter
import org.springframework.security.web.authentication.AuthenticationFailureHandler
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter
import org.springframework.security.web.authentication.logout.LogoutFilter
import org.springframework.security.web.authentication.session.SessionFixationProtectionStrategy
import org.springframework.security.web.context.SecurityContextPersistenceFilter
import org.springframework.security.web.savedrequest.RequestCacheAwareFilter
import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter
import org.springframework.security.web.session.SessionManagementFilter
import org.springframework.security.web.util.AnyRequestMatcher
import org.springframework.test.util.ReflectionTestUtils
/**
*
* @author Rob Winch
*/
class FormLoginConfigurerTests extends BaseSpringSpec {
def "Form Login"() {
when: "load formLogin()"
context = new AnnotationConfigApplicationContext(FormLoginConfig)
then: "FilterChains configured correctly"
def filterChains = filterChains()
filterChains.size() == 2
filterChains[0].requestMatcher.pattern == '/resources/**'
filterChains[0].filters.empty
filterChains[1].requestMatcher instanceof AnyRequestMatcher
filterChains[1].filters.collect { it.class.name.contains('$') ? it.class.superclass : it.class } ==
[SecurityContextPersistenceFilter, LogoutFilter, UsernamePasswordAuthenticationFilter,
RequestCacheAwareFilter, SecurityContextHolderAwareRequestFilter,
AnonymousAuthenticationFilter, SessionManagementFilter, ExceptionTranslationFilter, FilterSecurityInterceptor ]
and: "UsernamePasswordAuthentictionFilter is configured correctly"
UsernamePasswordAuthenticationFilter authFilter = findFilter(UsernamePasswordAuthenticationFilter,1)
authFilter.usernameParameter == "username"
authFilter.passwordParameter == "password"
authFilter.failureHandler.defaultFailureUrl == "/login?error"
authFilter.successHandler.defaultTargetUrl == "/"
authFilter.requiresAuthentication(new MockHttpServletRequest(requestURI : "/login", method: "POST"), new MockHttpServletResponse())
!authFilter.requiresAuthentication(new MockHttpServletRequest(requestURI : "/login", method: "GET"), new MockHttpServletResponse())
and: "SessionFixationProtectionStrategy is configured correctly"
SessionFixationProtectionStrategy sessionStrategy = ReflectionTestUtils.getField(authFilter,"sessionStrategy")
sessionStrategy.migrateSessionAttributes
and: "Exception handling is configured correctly"
AuthenticationEntryPoint authEntryPoint = filterChains[1].filters.find { it instanceof ExceptionTranslationFilter}.authenticationEntryPoint
MockHttpServletResponse response = new MockHttpServletResponse()
authEntryPoint.commence(new MockHttpServletRequest(requestURI: "/private/"), response, new BadCredentialsException(""))
response.redirectedUrl == "http://localhost/login"
}
@Configuration
@EnableWebSecurity
static class FormLoginConfig extends BaseWebConfig {
@Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/resources/**");
}
@Override
protected void configure(HttpSecurity http) {
http
.authorizeUrls()
.anyRequest().hasRole("USER")
.and()
.formLogin()
.loginUrl("/login")
}
}
def "FormLogin.permitAll()"() {
when: "load formLogin() with permitAll"
context = new AnnotationConfigApplicationContext(FormLoginConfigPermitAll)
then: "the formLogin URLs are granted access"
FilterChainProxy filterChain = context.getBean(FilterChainProxy)
MockHttpServletResponse response = new MockHttpServletResponse()
filterChain.doFilter(new MockHttpServletRequest(servletPath : servletPath, requestURI: servletPath, queryString: query, method: method), response, new MockFilterChain())
response.redirectedUrl == redirectUrl
where:
servletPath | method | query | redirectUrl
"/login" | "GET" | null | null
"/login" | "POST" | null | "/login?error"
"/login" | "GET" | "error" | null
}
@Configuration
@EnableWebSecurity
static class FormLoginConfigPermitAll extends BaseWebConfig {
@Override
protected void configure(HttpSecurity http) {
http
.authorizeUrls()
.anyRequest().hasRole("USER")
.and()
.formLogin()
.permitAll()
}
}
def "FormLogin uses PortMapper"() {
when: "load formLogin() with permitAll"
FormLoginUsesPortMapperConfig.PORT_MAPPER = Mock(PortMapper)
loadConfig(FormLoginUsesPortMapperConfig)
then: "the formLogin URLs are granted access"
findFilter(ExceptionTranslationFilter).authenticationEntryPoint.portMapper == FormLoginUsesPortMapperConfig.PORT_MAPPER
}
@Configuration
@EnableWebSecurity
static class FormLoginUsesPortMapperConfig extends BaseWebConfig {
static PortMapper PORT_MAPPER
@Override
protected void configure(HttpSecurity http) {
http
.authorizeUrls()
.anyRequest().hasRole("USER")
.and()
.formLogin()
.permitAll()
.and()
.portMapper()
.portMapper(PORT_MAPPER)
}
}
def "FormLogin permitAll ignores failureUrl when failureHandler set"() {
setup:
PermitAllIgnoresFailureHandlerConfig.FAILURE_HANDLER = Mock(AuthenticationFailureHandler)
loadConfig(PermitAllIgnoresFailureHandlerConfig)
FilterChainProxy springSecurityFilterChain = context.getBean(FilterChainProxy)
when: "access default failureUrl and configured explicit FailureHandler"
MockHttpServletRequest request = new MockHttpServletRequest(requestURI:"/login",queryString:"error")
MockHttpServletResponse response = new MockHttpServletResponse()
springSecurityFilterChain.doFilter(request,response,new MockFilterChain())
then: "access is not granted to the failure handler (sent to login page)"
response.status == 302
}
@EnableWebSecurity
@Configuration
static class PermitAllIgnoresFailureHandlerConfig extends BaseWebConfig {
static AuthenticationFailureHandler FAILURE_HANDLER
@Override
protected void configure(HttpSecurity http) {
http
.authorizeUrls()
.anyRequest().hasRole("USER")
.and()
.formLogin()
.failureHandler(FAILURE_HANDLER)
.permitAll()
}
}
def "formLogin ObjectPostProcessor"() {
setup: "initialize the AUTH_FILTER as a mock"
AnyObjectPostProcessor opp = Mock()
HttpSecurity http = new HttpSecurity(opp, authenticationBldr, [:])
when:
http
.formLogin()
.and()
.build()
then: "UsernamePasswordAuthenticationFilter is registered with LifecycleManager"
1 * opp.postProcess(_ as UsernamePasswordAuthenticationFilter) >> {UsernamePasswordAuthenticationFilter o -> o}
and: "LoginUrlAuthenticationEntryPoint is registered with LifecycleManager"
1 * opp.postProcess(_ as LoginUrlAuthenticationEntryPoint) >> {LoginUrlAuthenticationEntryPoint o -> o}
}
}

Some files were not shown because too many files have changed in this diff Show More