Chapter 6. Security

Spring Python's Security module is based on Acegi Security's architecture. You can read Acegi's detailed reference manual for a background on this module.

[Note]Spring Security vs. Acegi Security

At the time this module was implemented, Spring Security was still Acegi Security. Links include reference documentation that was used at the time to implement this security module.

6.1. Shared Objects

The major building blocks of Spring Python Security are

  • SecurityContextHolder, to provide any type access to the SecurityContext.

  • SecurityContext, to hold the Authentication and possibly request-specific security information.

  • HttpSessionContextIntegrationFilter, to store the SecurityContext in the HTTP session between web requests.

  • Authentication, to represent the principal in an Acegi Security-specific manner.

  • GrantedAuthority, to reflect the application-wide permissions granted to a principal.

These objects are needed for both authentication and authorization.

6.2. Authentication

The first level of security involves verifying your credentials. Most systems today use some type of username/password check. To configure Spring Python, you will need to configure one or more AuthenticationProvider's. All Authentication implementations are required to store an array of GrantedAuthority objects. These represent the authorities that have been granted to the principal. The GrantedAuthority objects are inserted into the Authentication object by the AuthenticationManager and are later read by AccessDecisionManager's when making authorization decisions. These are chained together inside an AuthenticationManager.

6.2.1. AuthenticationProviders

6.2.1.1. DaoAuthenticationProvider

This AuthenticationProvider allows you to build a dictionary of user accounts, and is very handy for integration testing without resorting to complex configuration of 3rd party systems.

To configure this using a pythonic, decorator-based IoC container...

class SampleContainer(PythonConfig):
    ...
    @Object
    def inMemoryDaoAuthenticationProvider(self):
        provider = DaoAuthenticationProvider()
        provider.user_details_service = inMemoryUserDetailsService()
        return provider
        
	@Object
	def inMemoryUserDetailsService(self):
		user_details_service = InMemoryUserDetailsService()
        user_details_service.user_dict = {
           "vet1": ("password1", ["VET_ANY"], False),
           "bdavis": ("password2", ["CUSTOMER_ANY"], False),
           "jblack": ("password3", ["CUSTOMER_ANY"], False),
           "disableduser": ("password4", ["VET_ANY"], True),
           "emptyuser": ("", [], False) }
        return user_details_service

XML configuration using XMLConfig:

<?xml version="1.0" encoding="UTF-8"?>
<objects xmlns="http://www.springframework.org/springpython/schema/objects"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/springpython/schema/objects
       		http://springpython.webfactional.com/schema/context/spring-python-context-1.0.xsd">

	<object id="inMemoryUserDetailsService" class="springpython.security.userdetails.InMemoryUserDetailsService">
		<property name="user_dict">
			<dict>
				<entry>
					<key><value>user1</value></key>
					<value>
						<tuple>
							<value>password1</value>
							<list><value>role1</value><value>blue</value></list>
							<value>True</value>
						</tuple>
					</value>
				</entry>
				<entry>
					<key><value>user2</value></key>
					<value>
						<tuple>
							<value>password2</value>
							<list><value>role1</value><value>orange</value></list>
							<value>True</value>
						</tuple>
					</value>
				</entry>
				<entry>
					<key><value>adminuser</value></key>
					<value>
						<tuple>
							<value>password3</value>
							<list><value>role1</value><value>admin</value></list>
							<value>True</value>
						</tuple>
					</value>
				</entry>
				<entry>
					<key><value>disableduser</value></key>
					<value>
						<tuple>
							<value>password4</value>
							<list><value>role1</value><value>blue</value></list>
							<value>False</value>
						</tuple>
					</value>
				</entry>
				<entry>
					<key><value>emptyuser</value></key>
					<value>
						<tuple>
							<value/>
							<list/>
							<value>True</value>
						</tuple>
					</value>
				</entry>
			</dict>
		</property>
	</object>
      
	<object id="inMemoryDaoAuthenticationProvider" class="springpython.security.providers.dao.DaoAuthenticationProvider">
		<property name="user_details_service" ref="inMemoryUserDetailsService"/>
	</object>
	
</objects>

This is the user map defined for one of the test cases. The first user, user1, has a password of password1, a list of granted authorities ("role1", "blue"), and is enabled. The fourth user, "disableduser", has a password and a list of granted authorities, but is NOT enabled. The last user has no password, which will cause authentication to fail.

6.2.1.2. Future AuthenticationProviders

So far, Spring Python has implemented a DaoAuthenticationProvider than can link with any database or user an in-memory user data structure. Future releases should include:

  • LdapAuthenticationProvider

  • OpenIDAuthenticationProvider

  • Anonymous authentication provider - allows you to tag anonymous users, and constrain what they can access, even if they don't provide a password

6.2.2. AuthenticationManager

An AuthenticationManager holds a list of one or more AuthenticationProvider's, and will go through the list when attempting to authenticate. PetClinic configures it like this:

class SampleContainer(PythonConfig):
    ...
    @Object
    def authenticationManager(self):
        return AuthenticationManager(auth_providers = [self.authenticationProvider()])

XML-based configuration with XMLConfig:

<object id="authenticationManager" class="springpython.security.providers.AuthenticationManager">
    <property name="auth_providers">
    	<list><ref object="authenticationProvider"/></list>
   	</property>
</object>

This AuthenticationManager has a list referencing one object already defined in the ApplicationContext, authenticationProvider. The authentication manager is supplied as an argument to the security interceptor, so it can perform checks as needed.

6.3. Authorization

After successful authentication, a user is granted various roles. The next step of security is to determine if that user is authorized to conduct a given operation or access a particular web page. The AccessDecisionManager is called by the AbstractSecurityInterceptor and is responsible for making final access control decisions. The AccessDecisionManager interface contains two methods:

def decide(self, authentication, object, config)
def supports(self, attr)

As can be seen from the first method, the AccessDecisionManager is passed via method parameters all information that is likely to be of value in assessing an authorization decision. In particular, passing the secure object enables those arguments contained in the actual secure object invocation to be inspected. For example, let's assume the secure object was a MethodInvocation. It would be easy to query the MethodInvocation for any Customer argument, and then implement some sort of security logic in the AccessDecisionManager to ensure the principal is permitted to operate on that customer. Implementations are expected to throw an AccessDeniedException if access is denied.

Whilst users can implement their own AccessDecisionManager to control all aspects of authorization, Spring Python Security includes several AccessDecisionManager implementations that are based on voting. Using this approach, a series of AccessDecisionVoter implementations are polled on an authorization decision. The AccessDecisionManager then decides whether or not to throw an AccessDeniedException based on its assessment of the votes.

The AccessDecisionVoter interface has two methods:

def supports(self, attr)
def vote(self, authentication, object, config)

Concrete implementations return an integer, with possible values being reflected in the AccessDecisionVoter static fields ACCESS_ABSTAIN, ACCESS_DENIED and ACCESS_GRANTED. A voting implementation will return ACCESS_ABSTAIN if it has no opinion on an authorization decision. If it does have an opinion, it must return either ACCESS_DENIED or ACCESS_GRANTED.

There are three concrete AccessDecisionManager's provided with Spring Python Security that tally the votes. The ConsensusBased implementation will grant or deny access based on the consensus of non-abstain votes. Properties are provided to control behavior in the event of an equality of votes or if all votes are abstain. The AffirmativeBased implementation will grant access if one or more ACCESS_GRANTED votes were received (ie a deny vote will be ignored, provided there was at least one grant vote). Like the ConsensusBased implementation, there is a parameter that controls the behavior if all voters abstain. The UnanimousBased provider expects unanimous ACCESS_GRANTED votes in order to grant access, ignoring abstains. It will deny access if there is any ACCESS_DENIED vote. Like the other implementations, there is a parameter that controls the behavior if all voters abstain.

It is possible to implement a custom AccessDecisionManager that tallies votes differently. For example, votes from a particular AccessDecisionVoter might receive additional weighting, whilst a deny vote from a particular voter may have a veto effect.

There are two concrete AccessDecisionVoter implementations provided with Spring Python Security. The RoleVoter class will vote if any config attribute begins with ROLE_. It will vote to grant access if there is a GrantedAuthority which returns a String representation exactly equal to one or more config attributes starting with ROLE_. If there is no exact match of any config attribute starting with ROLE_, the RoleVoter will vote to deny access. If no config attribute begins with ROLE_, the voter will abstain. RoleVoter is case sensitive on comparisons as well as the ROLE_ prefix.

PetClinic has two RoleVoter's in its configuration:

class SampleContainer(PythonConfig):
    ...
    @Object
    def vetRoleVoter(self):
        return RoleVoter(role_prefix="VET")

    @Object
    def customerRoleVoter(self):
        return RoleVoter(role_prefix="CUSTOMER")

XML-based configuration with XMLConfig:

<object id="vetRoleVoter" class="springpython.security.vote.RoleVoter">
	<property name="role_prefix"><value>VET</value></property>
</object>

<object id="customerRoleVoter" class="springpython.security.vote.RoleVoter">
	<property name="role_prefix"><value>CUSTOMER</value></property>
</object>

The first one votes on VET authorities, and the second one votes on CUSTOMER authorities.

The other concrete AccessDecisionVoter is the LabelBasedAclVoter. It can be seen in the test cases. Maybe later it will be incorporated into a demo.

Petclinic has a custom AccessDecisionVoter, which votes on whether a user "owns" a record.

class SampleContainer(PythonConfig):
    ...
    @Object
    def ownerVoter(self):
        return OwnerVoter(controller = self.controller())

XML-based configuration using XMLConfig:

<object id="ownerVoter" class="controller.OwnerVoter">
	<property name="controller" ref="controller"/>
</object>

This class is wired in the PetClinic controller module as part of the sample, which demonstrates how easy it is to plugin your own custom security handler to this module.

PetClinic wires together these AccessDecisionVoter's into an AccessDecisionManager:

class SampleContainer(PythonConfig):
    ...
    @Object
    def accessDecisionManager(self):
        manager = AffirmativeBased()
        manager.allow_if_all_abstain = False
        manager.access_decision_voters = [self.vetRoleVoter(), self.customerRoleVoter(), self.ownerVoter()]
        return manager

XML-based configuration using XMLConfig:

<object id="accessDecisionManager" class="springpython.security.vote.AffirmativeBased">
	<property name="allow_if_all_abstain"><value>False</value></property>
	<property name="access_decision_voters">
		<list>
			<ref object="vetRoleVoter"/>
			<ref object="customerRoleVoter"/>
			<ref object="ownerVoter"/>
		</list>
	</property>
</object>