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. External dependencies --------------------- *springpython.security.cherrypy3* package depends on `CherryPy 3 `_ being installed prior to using it. Other than that, there are no specific external libraries required by Spring Python's security system, however the IoC configuration format that you'll be using may need some, check IoC documentation for more details. 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. 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 *AuthenticationProviders*. 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 *AccessDecisionManagers* when making authorization decisions. These are chained together inside an *AuthenticationManager*. AuthenticationProviders +++++++++++++++++++++++ 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 :doc:`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 .. highlight:: xml XML configuration using :doc:`XMLConfig `:: user1 password1 role1blue True user2 password2 role1orange True adminuser password3 role1admin True disableduser password4 role1blue False emptyuser True 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. LDAP Authentication Provider >>>>>>>>>>>>>>>>>>>>>>>>>>>> Spring Python has an *LdapAuthenticationProvider* that is able to authenticate users against an LDAP server using either binding or password comparison. It will also search the LDAP server for groups in order to identify roles. .. note:: Spring Python's LDAP only works with CPython Currently, Spring Python only provides LDAP support for CPython. There is on-going effort to extend support to Jython as well. It is possible to the customize the query parameters, as well as inject an alternative version of authentication as well as role identification. There are two ways to verify a password in ldap: binding to the server using the password, or fetching the password from ldap and comparing outside the server. Spring Python supports both. You can choose which mechanism by injecting either a *BindAuthenticator* or a *PasswordComparisonAuthenticator* into *LdapAuthenticationProvider*. .. highlight:: xml The following XML fragment demonstrates how to configure Spring Python's *LdapAuthenticationProvider* using a *BindAuthenticator* combined with a *DefaultLdapAuthoritiesPopulator*:: * *context_source* - points to an ldap server, defining the base DN to start searching for users and groups. * *bindAuthenticator* - configured to use the context_source, and does a user search based on sub-entry *uid={0},ou=people*. *{0}* is the variable where an entered username will be substituted before executing the ldap search. * *authoritiesPopulator* - assuming the user is found, it uses the group_search_filter to find groups containing this attribute pointed at the user's DN. * *ldapAuthenticationProvider* - combines together the bindAuthenticator and the authoritiesPopulator, in order to process a *UsernamePasswordAuthenticationToken*. * *ldapAuthenticationManager* - just like the other examples, this *AuthenticationManager* iterates over the list of providers, giving them a chance to authenticate the user. .. highlight:: python The following shows the same configuration in pure Python, using :doc:`PythonConfig `:: class LdapContext(PythonConfig): def __init__(self): PythonConfig.__init__(self) @Object def context_source(self): return DefaultSpringSecurityContext(url="ldap://localhost:53389/dc=springframework,dc=org") @Object def bind_authenticator(self): return BindAuthenticator(self.context_source(), user_dn_patterns="uid={0},ou=people") @Object def authorities_populator(self): return DefaultLdapAuthoritiesPopulator(self.context_source(), group_search_filter="member={0}") @Object def provider(self): return LdapAuthenticationProvider(self.bind_authenticator(), self.authorities_populator()) @Object def manager(self): return AuthenticationManager(auth_providers=[self.provider()]) .. highlight:: xml To use the password comparison mechanism with :doc:`XMLConfig `, substitute PasswordComparisonAuthenticator for BindAuthenticator as follows:: .. highlight:: python The following block shows the same configuration using the pure Python container:: class LdapContext(PythonConfig): def __init__(self): PythonConfig.__init__(self) @Object def context_source(self): return DefaultSpringSecurityContext(url="ldap://localhost:53389/dc=springframework,dc=org") @Object def password_authenticator(self): return PasswordComparisonAuthenticator(self.context_source(), user_dn_patterns="uid={0},ou=people") @Object def authorities_populator(self): return DefaultLdapAuthoritiesPopulator(self.context_source(), group_search_filter="member={0}") @Object def provider(self): return LdapAuthenticationProvider(self.password_authenticator(), self.authorities_populator()) @Object def manager(self): return AuthenticationManager(auth_providers=[self.provider()]) By default, *PasswordComparisonAuthenticator* handles SHA encrypted passwords as well passwords stored in plain text. However, you can inject a custom *PasswordEncoder* to support alternative password encoding schemes. Future AuthenticationProviders >>>>>>>>>>>>>>>>>>>>>>>>>>>>>> So far, Spring Python has implemented a DaoAuthenticationProvider than can link with any database or use an in-memory user data structure, as well as an LdapAuthenticationProvider. Future releases should include: * *OpenIDAuthenticationProvider* * Anonymous authentication provider - allows you to tag anonymous users, and constrain what they can access, even if they don't provide a password 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 using :doc:`PythonConfig `:: class SampleContainer(PythonConfig): @Object def authenticationManager(self): return AuthenticationManager(auth_providers = [self.authenticationProvider()]) .. highlight:: xml XML-based configuration with :doc`XMLConfig `:: 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. Authorization ------------- .. highlight:: python 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 *AccessDecisionManagers* 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. 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 *RoleVoters* in its :doc:`Python-config based ` configuration:: class SampleContainer(PythonConfig): @Object def vetRoleVoter(self): return RoleVoter(role_prefix="VET") @Object def customerRoleVoter(self): return RoleVoter(role_prefix="CUSTOMER") .. highlight:: xml XML-based configuration with XMLConfig:: VET CUSTOMER 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. .. highlight:: python 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()) .. highlight:: xml XML-based configuration using :doc:`XMLConfig `:: 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. .. highlight:: python PetClinic wires together these *AccessDecisionVoters* 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 .. highlight:: xml XML-based configuration using :doc:`XMLConfig `:: False