Before we get going, let's go ahead and plug-in some security. In the security chapter, we discussed how security can be conveniently added after the fact. But it's even better if we start with it first. We will do so by adding a simple login page, and hard wiring three accounts: a customer, a bank manager, and a bank supervisor.
import cherrypy import os from springpython.context import ApplicationContext from springpython.security.context import * from ctx2 import * if __name__ == '__main__': cherrypy.config.update({'server.socket_port': 8009}) ctx = ApplicationContext(SpringBankAppContext()) SecurityContextHolder.setStrategy(SecurityContextHolder.MODE_GLOBAL) SecurityContextHolder.getContext() conf = {"/": {"tools.sessions.on":True, "tools.filterChainProxy.on":True}} cherrypy.tree.mount( ctx.get_object("view"), '/', config=conf) cherrypy.engine.start() cherrypy.engine.block()
First of all, we need to initialize the security context settings stored in SecurityContextHolder
. Another thing required by CherryPy is turning on HTTP sessions. We also must activate the filterChainProxy
. Spring Python's FilterChainProxy
, setup in our application context, automatically registers itself as a CherryPy tool.
from springpython.config import PythonConfig, Object from springpython.security.providers import * from springpython.security.providers.dao import * from springpython.security.userdetails import * from springpython.security.vote import * from springpython.security.web import * from springpython.security.cherrypy3 import * from app2 import * class SpringBankAppContext(PythonConfig): def __init__(self): PythonConfig.__init__(self) @Object banking applicationsecuringdef view(self): view = SpringBankView() view.auth_provider = self.auth_provider() view.filter = self.auth_processing_filter() view.http_context_filter = self.httpSessionContextIntegrationFilter() return view @Object def filterChainProxy(self): return CP3FilterChainProxy(filterInvocationDefinitionSource = [ ("/login.*", ["httpSessionContextIntegrationFilter"]), ("/.*", ["httpSessionContextIntegrationFilter", "exception_translation_filter", "auth_processing_filter", "filter_security_interceptor"]) ]) @Object def httpSessionContextIntegrationFilter(self): filter = HttpSessionContextIntegrationFilter() filter.sessionStrategy = self.session_strategy() return filter @Object def session_strategy(self): return CP3SessionStrategy() @Object def exception_translation_filter(self): filter = ExceptionTranslationFilter() filter.authenticationEntryPoint = self.auth_filter_entry_pt() filter.accessDeniedHandler = self.accessDeniedHandler() return filter @Object def auth_filter_entry_pt(self): filter = AuthenticationProcessingFilterEntryPoint() filter.loginFormUrl = "/login" filter.redirectStrategy = self.redirectStrategy() return filter @Object def accessDeniedHandler(self): handler = SimpleAccessDeniedHandler() handler.errorPage = "/accessDenied" handler.redirectStrategy = self.redirectStrategy() return handler @Object def redirectStrategy(self): return CP3RedirectStrategy() @Object def auth_processing_filter(self): filter = AuthenticationProcessingFilter() filter.auth_manager = self.auth_manager() filter.alwaysReauthenticate = False return filter @Object def auth_manager(self): auth_manager = AuthenticationManager() auth_manager.auth_providers = [self.auth_provider()] return auth_manager @Object def auth_provider(self): provider = DaoAuthenticationProvider() provider.user_details_service = self.user_details_service() provider.password_encoder = PlaintextPasswordEncoder() return provider @Object def user_details_service(self): user_details_service = InMemoryUserDetailsService() user_details_service.user_dict = { "alice": ("alicespassword",["ROLE_CUSTOMER"], True), "bob": ("bobspassword", ["ROLE_MGR"], True), "carol": ("carolspassword", ["ROLE_SUPERVISOR"], True) } return user_details_service @Object def filter_security_interceptor(self): filter = FilterSecurityInterceptor() filter.auth_manager = self.auth_manager() filter.access_decision_mgr = self.access_decision_mgr() filter.sessionStrategy = self.session_strategy() filter.obj_def_source = [ ("/.*", ["ROLE_CUSTOMER", "ROLE_MGR", "ROLE_SUPERVISOR"]) ] return filter @Object def access_decision_mgr(self): access_decision_mgr = AffirmativeBased() access_decision_mgr.allow_if_all_abstain = False access_decision_mgr.access_decision_voters = [RoleVoter()] return access_decision_mgr
This is admittedly a lot of code to add to our application context. Spring Python requires a lot of steps to add the security components, a fate shared by the Spring Security project. They spent a considerable amount of effort reducing the amount of code needed to configure security for typical configurations. Hopefully in the future Spring Python can be improved in a similar fashion.
import cherrypy from springpython.security import * from springpython.security.providers import * from springpython.security.context import * class SpringBankView(object): def __init__(self): self.filter = None self.auth_provider = None self.http_context_filter = None @cherrypy.expose def index(self): return """ Welcome to SpringBank! <p> <p> <a href="logout">Logout</a href> """ @cherrypy.expose def login(self, from_page="/", login="", password="", error_msg=""): if login != "" and password != "": try: self.attempt_auth(login, password) raise cherrypy.HTTPRedirect(from_page) except AuthenticationException, e: raise cherrypy.HTTPRedirect( "?login=%s&error_msg=Username/password failure" % login) return """ %s<p> <form method="POST" action=""> <table> <tr> <td>Login:</td> <td><input type="text" name="login" value="%s"/></td> </tr> <tr> <td>Password:</td> <td> <input type="password" name="password"/> </td> </tr> </table> <input type="hidden" name="from_page" value="%s"/><br/> <input type="submit"/> </form> """ % (error_msg, login, from_page) def attempt_auth(self, username, password): token = UsernamePasswordAuthenticationToken(username, password) SecurityContextHolder.getContext().authentication = self.auth_provider.authenticate(token) self.http_context_filter.saveContext() @cherrypy.expose def logout(self): self.filter.logout() self.http_context_filter.saveContext() raise cherrypy.HTTPRedirect("/") Here we have slightly altered index, so that it prints our 'welcome' but also offers a hyperlink to logout. We have also added a /login link that either attempts to log the user in, or displays an HTML form so the user can attempt to login.
Here we have slightly altered index, so that it prints our 'welcome' but also offers a hyperlink to logout. We have also added a /login
link that either attempts to log the user in, or displays an HTML form so the user can attempt to login.
As you can see, we are now looking at a very simple login screen. If we look at the shell in which our updated application is running, we can see what has happened.
We pointed our browser at the root URL, but Spring Python's security filter
noticed we had no credentials, so it redirected us to /login
.
Now we have logged in to our SpringBank web site. With the security in place, we should easily be able to start adding more features and control who can access what.