Tuesday, October 27, 2009

Spring Security

Getting Started

I would suggest getting started with the applicationContext-security.xml that is found in the tutorial sample, and trimming it down a bit. Here’s what I got when I trimmed it down:
<?xml version="1.0" encoding="UTF-8"?> 
 
<!--
- Sample namespace-based configuration
-
- $Id: applicationContext-security.xml 3019 2008-05-01 17:51:48Z luke_t $
-->
 
<beans:beans xmlns="http://www.springframework.org/schema/security"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security-2.0.1.xsd">

 
<global-method-security secured-annotations="enabled">
</global-method-security>
 
<http auto-config="true">
<intercept-url pattern="/**" access="IS_AUTHENTICATED_ANONYMOUSLY" />
</http>
 
<!--
Usernames/Passwords are
rod/koala
dianne/emu
scott/wombat
peter/opal
-->
<authentication-provider>
<password-encoder hash="md5"/>
<user-service>
<user name="rod" password="a564de63c2d0da68cf47586ee05984d7" authorities="ROLE_SUPERVISOR, ROLE_USER, ROLE_TELLER" />
<user name="dianne" password="65d15fe9156f9c4bbffd98085992a44e" authorities="ROLE_USER,ROLE_TELLER" />
<user name="scott" password="2b58af6dddbd072ed27ffc86725d7d3a" authorities="ROLE_USER" />
<user name="peter" password="22b5c9accc6e1ba628cedc63a72d57f8" authorities="ROLE_USER" />
</user-service>
</authentication-provider>
</beans:beans>

This makes a good baseline for the modifications we’re going to make. But first…

Mapping XML Elements to Java Code

I found it very helpful at this point, before messing with the XML, to know where the Java code was that corresponded to the available XML elements. The basic class that Spring Security uses for mapping XML elements to beans is SecurityNamespaceHandler. The code in this class simply delegates XML elements to bean definition parsers. It’s easy to follow along and map XML elements to Java code in this way. Unfortunately, don’t expect extensive commenting in the Java code to help you

web.xml Changes

I agree with the Spring Security documentation and found it easier to extract the security-related stuff into its own XML configuration file. This allows you to play XML tricks and not require namespace-tagging for the security elements. First off, you have to include a reference to applicationContext-security.xml in your [Spring] initialization parameters in your web.xml file:
 <context-param> 
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/spring-app-servlet.xml
/WEB-INF/applicationContext-security.xml
</param-value>
</context-param>

Next, as instructed by the Spring Security getting started guide, you need to add the filter mapping. In my case, this went right after the <context-param> end tag, since I didn’t have any other filters:


    <filter> 
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
 
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>


This default mapping will run all requests to your application through Spring Security. Now we’re done with web.xml, and we move on to…


Database-Backed Authentication


In my case, my application was already configured to use a JDBC DataSource, so pointing Spring Security at my JDBC data source was as easy as modifying the authentication-provider element to reference my already configured Spring bean:


    <authentication-provider> 
<jdbc-user-service data-source-ref="dataSource"/>
</authentication-provider>


Now, the immediate question I asked is – OK, what does the convention over configuration assume my database tables look like? If you look at the documentation of the JDBC authentication provider, you would expect to see that information there, but you’d be wrong.


Instead, you have to look at the SQL queries that are hard-coded in the JdbcDaoImpl class and infer the schema structure for yourself. This article has a graphical depiction of the basic schema down in section 5.4.


If you want to configure the queries that are used, simply match the available attributes on the jdbc-user-service element to the SQL queries in the Java class I referenced above. In my example, I wanted to simplify my schema by adding the user’s role directly to the user table. So I modified the XML configuration slightly as follows:


  <jdbc-user-service data-source-ref="dataSource"  
authorities-by-username-query="select username,authority from users where username=?"/>


This allowed me to put values in the ‘authority’ column like ‘ROLE_ADMIN’ or ‘ROLE_USER’, which translate directly into Spring Security roles!


Configuring URL authorization


Mapping URLs to roles is really easy. In your http element, simply put successive elements like this:



        <intercept-url pattern="/admin/*.do" access="ROLE_ADMIN"  /> 
<intercept-url pattern="/**.do" access="ROLE_USER,ROLE_ADMIN" />


Note here that the ‘access’ attribute values directly correspond to the values returned by the second column of the authorities-by-username-query. The ‘.do’ mapping is what I arbitrarily chose for my application – you may have to adjust depending on what your application’s Spring-managed URLs look like.


Configuring and Branding Spring Security-managed Pages


Finally, I wanted to figure out where the pages related to Spring Security should be configured, so that I could modify them if I needed to. Somewhat oddly, Spring Security ships with a default login page whose HTML markup is located in a class file – DefaultLoginPageGeneratingFilter. We would (obviously) like to replace this with our own custom page. Since we are authenticating everything passing through the Spring servlet, we must use a JSP for this.


Add the following to the http tag in the security configuration file:


<form-login login-page="/login.jsp" />


Now you need to put the login.jsp page in your web application (generally in the WEB-INF directory). The basic structure of the page you’re creating will look like this:



<%@ page import="org.springframework.security.ui.webapp.AuthenticationProcessingFilter" %> 
<%@ page import="org.springframework.security.ui.AbstractProcessingFilter" %>
<%@ page import="org.springframework.security.AuthenticationException" %>
 
...
<form action="j_spring_security_check">
<label for="j_username">Username</label>
<input type="text" name="j_username" id="j_username" <c:if test="${not empty param.login_error}">value='<%= session.getAttribute(AuthenticationProcessingFilter.SPRING_SECURITY_LAST_USERNAME_KEY) %>'</c:if>/>
<br/>
<label for="j_password">Password</label>
<input type="password" name="j_password" id="j_password"/>
<br/>
<input type='checkbox' name='_spring_security_remember_me'/> Remember me on this computer.
<br/>
<input type="submit" value="Login"/>
</form>

The names of the form elements and form action must match what is shown here otherwise your login form will not work!


Note also that this is a plain ol’ JSP page, and not under Spring control. It is likely that you could play with the servlet filter patterns in web.xml to bring these pages under Spring control, but that is a topic outside the scope of this brief tutorial.


There are a couple other pages you will want to configure.


Access Denied: This is the page the user will see if they are denied access to the site due to lack of authorization (i.e. tried to hit a page that they didn’t have access to hit, even though they were authenticated properly). This is configured as follows:



    <http ... access-denied-page="/accessDenied.jsp"> 
...
</http>


Default Target URL: This is where the user will be redirected upon successful login. This can (and probably should) be a page located under Spring control. Configured as follows:



    <http ... > 
...
<form-login ... default-target-url="/home.do"/>
...
</http>


Logout URL: The page where the user is redirected upon a successful logout. This can be a page located under Spring control too (provided that it allows anonymous access):



    <http ... > 
...
<logout logout-success-url="/home.do"/>
...
</http>

Login Failure URL: Where the user will be sent if there was an authentication failure. Typically this is back to the login form, with a URL parameter, such as:



    <http ... > 
...
<form-login ... authentication-failure-url="/login.jsp?login_error=1"/>
...
</http>


Putting it Together


Here’s what my whole sample Spring security configuration looked like when I was done:



<beans:beans xmlns="http://www.springframework.org/schema/security" 
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-2.0.1.xsd">

 
<global-method-security secured-annotations="enabled">
<!-- AspectJ pointcut expression that locates our "post" method and applies security that way
<protect-pointcut expression="execution(* bigbank.*Service.post*(..))" access="ROLE_TELLER"/>
-->
</global-method-security>
 
<http auto-config="true" access-denied-page="/accessDenied.jsp">
<intercept-url pattern="/login.jsp*" filters="none"/>
<intercept-url pattern="/admin/editUser.do" access="ROLE_ADMIN" />
<intercept-url pattern="/admin/searchUsers.do" access="ROLE_ADMIN" />
<intercept-url pattern="/**.do" access="ROLE_USER,ROLE_ADMIN" />
<form-login authentication-failure-url="/login.jsp?login_error=1" default-target-url="/home.do"/>
<logout logout-success-url="/home.do"/>
</http>
 
<authentication-provider>
<jdbc-user-service data-source-ref="dataSource" authorities-by-username-query="select username,authority from users where username=?"/>
</authentication-provider>
 
</beans:beans>


Wrap-Up


Ironically, just as I was drafting this article, a smart colleague of mine happened to come to me telling me about all the problems he was having getting started with Spring Security. He complained about the lack of detailed documentation on the XML, and the fact that the getting started documentation really wasn’t comprehensive (both complains that I had as well). Note that this colleague also happened to be responsible for implementing Acegi Security with Spring in a prior project that we worked on together – so he was intimately familiar with the underlying technology. He ended up going back to the Java-based configuration mechanism in frustration!


Hope this helps you out and I always appreciate hearing your comments and questions.

No comments:

Post a Comment