Spring Security - The new and improved ACEGI
For those attempting Spring Security for the first time, a piece of advice. Ignore all comparisons to ACEGI and move on. You will find your brain in better shape at the end of the excercise.
First of all to make my life easier I created a Dynamic Web Project in Eclipse 3.3.2. Next I have Tomcat configured to run within my IDE for quick testing. My goal is to simply protect a bunch of web pages using Spring Security. Its easy to extend this to larger web apps.
First lets see the web.xml below.
|
<?xml version="1.0"
encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
java.sun.com/xml/ns/javaee/web-app_2_5.xsd" style="font-size: 10pt; font-family: "Courier New";"> id="WebApp_ID" version="2.5"> <display-name>springsecurity</display-name> <context-param> <param-name>log4jConfigLocation</param-name> <param-value>/WEB-INF/log4j.properties</param-value> </context-param> <context-param> <param-name>contextConfigLocation</param-name> <param-value> /WEB-INF/application-security.xml /WEB-INF/application-service.xml </param-value> </context-param> <listener> <listener-class> org.springframework.web.context.ContextLoaderListener </listener-class> </listener> <listener> <listener-class> org.springframework.web.util.Log4jConfigListener </listener-class> </listener> <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> <welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list> </web-app> |
Only thing of interest is the listener org.springframework.web.filter.DelegatingFilterProxy. I would love to compare this to ACEGI configuration but I will resist the urge. Just forget any old stuff. Suffice it to say that all URLS with the configured pattern pass through this filter and Spring Security is "performed" on them.
Next let us see the login.jsp page:
|
<%@ include file="includes.jsp"%> <%@ page import="org.springframework.security.ui.AbstractProcessingFilter"
%> <%@ page import="org.springframework.security.ui.webapp.AuthenticationProcessingFilter"
%> <%@ page import="org.springframework.security.AuthenticationException"
%> <html> <head> <title>Login</title> </head> <body> <% if
(session.getAttribute(AbstractProcessingFilter.SPRING_SECURITY_LAST_EXCEPTION_KEY)
!= null) { %> <font color="red"> Your login
attempt was not successful, please try again.<BR> <br/> Reason: <%=((AuthenticationException)
session.getAttribute(AbstractProcessingFilter.SPRING_SECURITY_LAST_EXCEPTION_KEY)).getMessage()%> </font> <% } %> <form method="post"
id="loginForm" action="<c:url value='j_spring_security_check'/>">Username: <input type="text" name="j_username" id="j_username"
/> <br /> Password: <input type="password"
name="j_password"
id="j_password"
/><br /> <input type="submit"
value="Login"
/></form> </body> </html> |
Refer to the login form and the way the parameters are named. For Spring Security to pick up the attributes you must name it the same as above.
Most important ... here is the spring context file application-security.xml
|
<?xml version="1.0"
encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:security="http://www.springframework.org/schema/security" xsi:schemaLocation=" http://www.springframework.org/schema/beans
www.springframework.org/schema/beans/spring-beans-2.5.xsd style="font-size: 10pt; font-family: "Courier New";"> http://www.springframework.org/schema/security
www.springframework.org/schema/security/spring-security-2.0.xsd" style="font-size: 10pt; font-family: "Courier New"; color: teal;">> <security:authentication-manager
alias="authenticationManager"
/> <security:http auto-config="true" access-denied-page="/accessdenied.jsp"> <security:form-login
login-page="/login.jsp" authentication-failure-url="/login.jsp" default-target-url="/index.jsp" /> <security:logout
logout-success-url="/login.jsp"
/> <security:intercept-url
pattern="/index.jsp" access="ROLE_ADMIN,ROLE_USER" /> <security:intercept-url
pattern="/admin/**"
access="ROLE_ADMIN"
/> <security:intercept-url
pattern="/**"
access="ROLE_ANONYMOUS"
/> </security:http> <bean id="loggerListener" class="org.springframework.security.event.authentication.LoggerListener"
/> <security:authentication-provider> <security:password-encoder
hash="md5"/> <security:user-service> <security:user password="5f4dcc3b5aa765d61d8327deb882cf99"
name="thomasm" authorities="ROLE_USER,ROLE_ANONYMOUS"
/> <security:user password="5f4dcc3b5aa765d61d8327deb882cf99"
name="admin" authorities="ROLE_ADMIN,ROLE_USER,ROLE_ANONYMOUS"
/> </security:user-service> </security:authentication-provider> </beans> |
- Spring namespace is used to configure security.
- security:authentication-manager need not be listed. If not a default will be created. List it if you need to refer it from some other configuration. In my case I just did to make the point.
- security:http is very self explanatory. We configure form login page names and the roles-to-URL patterns to protect. You can choose to not have this mapping here and instead implement your own object definition source class and provide the info from there.
- Finally security:authentication-provider is used to configure a password encoder and in this case a in-memory user store. The password is 'password'. I have listed the MD5 values above to show the use of the encoder.
|
<%@ include file="includes.jsp"%> <html> <head> <title>Home</title> </head> <body> You are logged in. To log out
click <a href='<c:url value="j_spring_security_logout"/>'>log out</a> <br /> <a href="admin/admin.jsp">admin</a> <br /> <authz:authorize ifAllGranted="ROLE_ADMIN"> <p style="font-weight:
bold">This text is only visible to admin users.</p> </authz:authorize> </body> </html> |
- Note the use of the taglib authz to show/hide admin related content.



The bold text above is only displayed to users with admin role.
Now for a few tips. Obviously you will not be hardcoding the user name and password into the configuration. Right my friend! Now for this you can implement your own class that gets the credentials from wherever you choose. This class then hooks into Spring Security. Here is a sample class CustomUserService.java where I have mocked the credentials in code.
|
package com.test; import
org.springframework.dao.DataAccessException; import org.springframework.security.GrantedAuthority; import org.springframework.security.GrantedAuthorityImpl; import
org.springframework.security.userdetails.User; import
org.springframework.security.userdetails.UserDetails; import
org.springframework.security.userdetails.UserDetailsService; import
org.springframework.security.userdetails.UsernameNotFoundException; public class
CustomUserService implements UserDetailsService { public UserDetails
loadUserByUsername(String user) throws
UsernameNotFoundException, DataAccessException { User
ud = null; if ("admin".equals(user)) { GrantedAuthority[]
auths = new GrantedAuthority[] { new GrantedAuthorityImpl("ROLE_ADMIN"), new GrantedAuthorityImpl("ROLE_USER"), new GrantedAuthorityImpl("ROLE_ANONYMOUS") }; ud
= new User(user, "5f4dcc3b5aa765d61d8327deb882cf99", true, true, true, true, auths); }
else if ("thomasm1".equals(user)) { GrantedAuthority[]
auths = new GrantedAuthority[] { new GrantedAuthorityImpl("ROLE_USER"), new GrantedAuthorityImpl("ROLE_ANONYMOUS") }; ud
= new User(user, "5f4dcc3b5aa765d61d8327deb882cf99", true, true, true, true, auths); } return ud; } } |
In the application-security.xml file you need to make the following change.
|
<security:authentication-provider
user-service-ref="customUserService"> <security:password-encoder
hash="md5"
/> </security:authentication-provider> |
The rest of the code should work same. Finally one more useful feature that I have used with the good ol ACEGI. That is to provide security access protection to the service layer. Add the following to your configuration...
<global-method-security secured-annotations="enabled" jsr250-annotations="enabled"/>Next add the annotations @Secured( {"ROLE_SECRET_AGENT"} ) to your service methods.






Comments