While all the features shown in this chapter so far demonstrate a powerful security framework, arguably the most important feature is the ability to write a custom security extension that plugs into this architecture.
Earlier in the LDAP section, it was mentioned that it is possible to adjust where searches are conducted to find groups related to the users. In the SQL section, it was shown that custom queries can be injected. Both of these features allow flexible adjustments, however, they can only bend so far. If our security system is highly specialized, it may be easier to simply code our own extension, and plug it in.
This is also necessary if another security system is not yet supported by Spring Python, such as two-factor tokens, OpenID, or X.509 certificates. While support for these may appear in the future, our needs may not be able to wait.
For our example, let's write a custom authentication provider that is based on logging in to a database instead of doing a password comparison.
This example matches a real situation I had to deal with. I inherited an application that was based on logging the user into the database and then looking up roles. This confined passwords to whatever the database supported, and in turn not allowing us to have a unified policy for acceptable passwords. The first step to migrate that application off of this limited solution required a custom authentication provider just like this example. It allowed me to utilize this framework, which provided the ability to plug in another security provider pointed at our new user security data.
In order to code our custom security provider, we need to write an authenticate
method with the following signature:
def authenticate(self, authentication)
Authentication providers are expected to either succeed or fail with the following behavior:
UsernamePasswordAuthenticationToken
, copy in the username, password
, and granted_auths
, and then return it. This is the same class used by all of Spring Python Security's providers. We do not have to call setAuthenticated(true)
on the token. AuthenticationManager
handles this for us BadCredentialsException
For this provider, we can use DatabaseTemplate
to do most of our work. To do that, we need an injected SQL connection factory. If we set the username and password with the values supplied by our user, we can create a DatabaseTemplate
and attempt a query to retrieve the user's roles. Since our instance of DatabaseTemplate
will try to login to the database before doing the query, this should serve as our authentication check. If successful, we will receive the granted authorities in a result set. If it fails, we will get a SQL exception, which we can catch and replace with a BadCredentialsException
.
from springpython.security.providers import * from springpython.database.core import * class MySqlLoginAuthenticationProvider(AuthenticationProvider): def __init__(self, factory=None): self.factory = factory def authenticate(self, authentication): self.factory.username = authentication.username self.factory.password = authentication.password dt = DatabaseTemplate(self.factory) try: authorities = dt.query("select authority from user where username = ?", (authentication.username,), rowhandler=DictionaryRowMapper()) return UsernamePasswordAuthenticationToken( authentication.username, authentication.getCredentials(), [auth["authority"] for auth in authorities]) except: raise BadCredentialsException("Invalid credentials")
Because each connection factory is slightly different, we will assume this is a MySqlConnectionFactory
being passed in. Our new provider is ready for immediate use. It's that easy, meeting our requirement: 'users must be able to quickly write custom security extensions to handle legacy security solutions'.
One way to make this example more sophisticated would be to make it handle the current set of connection factories. But for now, it works as a suitable demonstration of how easy it is to authenticate against an existing system in a way not currently covered by Spring Python Security.