Chris has posted 1 posts at DZone. View Full User Profile

Pathway from ACEGI to Spring Security 2.0

04.22.2008
| 106564 views |
  • submit to reddit
Formerly called ACEGI Security for Spring, the re-branded Spring Security 2.0 has delivered on its promises of making it simpler to use and improving developer productivity. Already considered as the Java platform's most widely used enterprise security framework with over 250,000 downloads from SourceForge, Spring Security 2.0 provides a host of new features.

This article outlines how to convert your existing ACEGI based Spring application to use Spring Security 2.0.

What is Spring Security 2.0

Spring Security 2.0 has recently been released as a replacement to ACEGI and it provides a host of new security features:

  • Substantially simplified configuration.
  • OpenID integration, single sign on standard.
  • Windows NTLM support, single sign on against Windows corporate networks.
  • Support for JSR 250 ("EJB 3") security annotations.
  • AspectJ pointcut expression language support.
  • Comprehensive support for RESTful web request authorization.
  • Long-requested support for groups, hierarchical roles and a user management API.
  • An improved, database-backed "remember me" implementation.
  • New support for web state and flow transition authorization through the Spring Web Flow 2.0 release.
  • Enhanced WSS (formerly WS-Security) support through the Spring Web Services 1.5 release.
  • A whole lot more...

Goal

Currently I work on a Spring web application that uses ACEGI to control access to the secure resources. Users are stored in a database and as such we have configured ACEGI to use a JDBC based UserDetails Service. Likewise, all of our web resources are stored in the database and ACEGI is configure to use a custom AbstractFilterInvocationDefinitionSource to check authorization details for each request.
With the release of Spring Security 2.0 I would like to see if I can replace ACEGI and keep the current ability to use the database as our source of authentication and authorization instead of the XML configuration files (as most examples demonstrate).

Here are the steps that I took...

Steps

  1. The first (and trickiest) step was to download the new Spring Security 2.0 Framework and make sure that the jar files are deployed to the correct location. (/WEB-INF/lib/)
    There are 22 jar files that come with the Spring Security 2.0 download. I did not need to use all of them (especially not the *sources packages). For this exercise I only had to include:
    • spring-security-acl-2.0.0.jar
    • spring-security-core-2.0.0.jar
    • spring-security-core-tiger-2.0.0.jar
    • spring-security-taglibs-2.0.0.jar
  2. Configure a DelegatingFilterProxy in the web.xml file.
    <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>
  3. Configuration of Spring Security 2.0 is far more concise than ACEGI, so instead of changing my current ACEGI based configuration file, I found it easier to start from a empty file. If you do want to change your existing configuration file, I am sure that you will be deleting more lines than adding.

    The first part of the configuration is to specifiy the details for the secure resource filter, this is to allow secure resources to be read from the database and not from the actual configuration file. This is an example of what you will see in most of the examples:
    <http auto-config="true" access-denied-page="/403.jsp">
    <intercept-url pattern="/index.jsp" access="ROLE_ADMINISTRATOR,ROLE_USER"/>
    <intercept-url pattern="/securePage.jsp" access="ROLE_ADMINISTRATOR"/>
    <intercept-url pattern="/**" access="ROLE_ANONYMOUS" />
    </http>
    Replace this with:
    <authentication-manager alias="authenticationManager"/>

    <beans:bean id="accessDecisionManager" class="org.springframework.security.vote.AffirmativeBased">
    <beans:property name="allowIfAllAbstainDecisions" value="false"/>
    <beans:property name="decisionVoters">
    <beans:list>
    <beans:bean class="org.springframework.security.vote.RoleVoter"/>
    <beans:bean class="org.springframework.security.vote.AuthenticatedVoter"/>
    </beans:list>
    </beans:property>
    </beans:bean>

    <beans:bean id="filterInvocationInterceptor" class="org.springframework.security.intercept.web.FilterSecurityInterceptor">
    <beans:property name="authenticationManager" ref="authenticationManager"/>
    <beans:property name="accessDecisionManager" ref="accessDecisionManager"/>
    <beans:property name="objectDefinitionSource" ref="secureResourceFilter" />
    </beans:bean>

    <beans:bean id="secureResourceFilter" class="org.security.SecureFilter.MySecureResourceFilter" />

    <http auto-config="true" access-denied-page="/403.jsp">
    <concurrent-session-control max-sessions="1" exception-if-maximum-exceeded="true" />
    <form-login login-page="/login.jsp" authentication-failure-url="/login.jsp" default-target-url="/index.jsp" />
    <logout logout-success-url="/login.jsp"/>
    </http>

    The main part of this piece of configuration is the secureResourceFilter, this is a class that implements FilterInvocationDefinitionSource and is called when Spring Security needs to check the Authorities for a requested page.
    Here is the code for MySecureResourceFilter:
    package org.security.SecureFilter;

    import java.util.Collection;
    import java.util.List;

    import org.springframework.security.ConfigAttributeDefinition;
    import org.springframework.security.ConfigAttributeEditor;
    import org.springframework.security.intercept.web.FilterInvocation;
    import org.springframework.security.intercept.web.FilterInvocationDefinitionSource;


    public class MySecureResourceFilter implements FilterInvocationDefinitionSource {

    public ConfigAttributeDefinition getAttributes(Object filter) throws IllegalArgumentException {

    FilterInvocation filterInvocation = (FilterInvocation) filter;

    String url = filterInvocation.getRequestUrl();

    // create a resource object that represents this Url object
    Resource resource = new Resource(url);

    if (resource == null) return null;
    else{
    ConfigAttributeEditor configAttrEditor = new ConfigAttributeEditor();
    // get the Roles that can access this Url
    List<Role> roles = resource.getRoles();
    StringBuffer rolesList = new StringBuffer();
    for (Role role : roles){
    rolesList.append(role.getName());
    rolesList.append(",");
    }
    // don't want to end with a "," so remove the last ","
    if (rolesList.length() > 0)
    rolesList.replace(rolesList.length()-1, rolesList.length()+1, "");
    configAttrEditor.setAsText(rolesList.toString());
    return (ConfigAttributeDefinition) configAttrEditor.getValue();
    }
    }

    public Collection getConfigAttributeDefinitions() {
    return null;
    }

    public boolean supports(Class arg0) {
    return true;
    }

    }
    This getAttributes() method above essentially returns the name of Authorities (which I call Roles) that are allowed access to the current Url.
  4. OK, so now we have setup the database based resources and now the next step is to get Spring Security to read the user details from the database. The examples that come with Spring Security 2.0 shows you how to keep a list of users and authorities in the configuration file like this:
    <authentication-provider>
    <user-service>
    <user name="rod" password="password" authorities="ROLE_SUPERVISOR, ROLE_USER" />
    <user name="dianne" password="password" authorities="ROLE_USER,ROLE_TELLER" />
    <user name="scott" password="password" authorities="ROLE_USER" />
    <user name="peter" password="password" authorities="ROLE_USER" />
    </user-service>
    </authentication-provider>
    You could replace these examples with this configuration so that you can read the user details straight from the database like this:
    <authentication-provider>
    <jdbc-user-service data-source-ref="dataSource" />
    </authentication-provider>
    While this is a very fast and easy way to configure database based security it does mean that you have to conform to a default databases schema. By default, the <jdbc-user-service> requires the following tables: user, authorities, groups, group_members and group_authorities.
    In my case this was not going to work as my security schema it not the same as what the <jdbc-user-service> requires, so I was forced to change the <authentication-provider>:
    <authentication-provider>
    <jdbc-user-service data-source-ref="dataSource"
    users-by-username-query="SELECT U.username, U.password, U.accountEnabled AS 'enabled' FROM User U where U.username=?"
    authorities-by-username-query="SELECT U.username, R.name as 'authority' FROM User U JOIN Authority A ON u.id = A.userId JOIN Role R ON R.id = A.roleId WHERE U.username=?"/>
    </authentication-provider>
    By adding the users-by-username-query and authorities-by-username-query properties you are able to override the default SQL statements with your own. As in ACEGI security you must make sure that the columns that your SQL statement returns is the same as what Spring Security expects. There is a another property group-authorities-by-username-query which I am not using and have therefore left it out of this example, but it works in exactly the same manner as the other two SQL statements.

    This feature of the <jdbc-user-service> has only been included in the past month or so and was not available in the pre-release versions of Spring Security. Luckily it has been added as it does make life a lot easier. You can read about this here and here.

    The dataSource bean instructs which database to connect to, it is not included in my configuration file as it's not specific to security. Here is an example of a dataSource bean for those who are not sure:
    	<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://localhost/db_name?useUnicode=true&characterEncoding=utf-8"/>
    <property name="username" value="root"/>
    <property name="password" value="pwd"/>
    </bean>
  5. And that is all for the configuration of Spring Security. My last task was to change my current logon screen. In ACEGI you could create your own logon <form> by making sure that you POSTED the correctly named HTML input elements to the correct URL. While you can still do this in Spring Security 2.0, some of the names have changed.
    You can still call your username field j_username and your password field j_password as before.
    <input type="text" name="j_username" id="j_username"/>
    <input type="password" name="j_password" id="j_password"/>
    However you must set the action property of your <form> to point to j_spring_security_check and not j_acegi_security_check.
    <form method="post" id="loginForm" action="<c:url value='j_spring_security_check'/>"
    There are a few places in our application where the user can logout, this is a link that redirects the logout request to the security framework so that it can be handled accordingly. This needs to be changed from j_acegi_logout to j_spring_security_logout.
    <a href='<c:url value="j_spring_security_logout"/>'>Logout</a>

Conclusion

This short guide on how to configure Spring Security 2.0 with access to resources stored in a database does not come close to illustrating the host of new features that are available in Spring Security 2.0, however I think that it does show some of the most commonly used abilities of the framework and I hope that you will find it useful.

One of the benefits of Spring Security 2.0 over ACEGI is the ability to write more consice configuration files, this is clearly shown when I compare my old ACEGI configration (172 lines) file to my new one (42 lines).
Here is my complete securityContext.xml file:

<?xml version="1.0" encoding="UTF-8"?>
<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.xsd"> <authentication-manager alias="authenticationManager"/>  <beans:bean id="accessDecisionManager" class="org.springframework.security.vote.AffirmativeBased"> <beans:property name="allowIfAllAbstainDecisions" value="false"/> <beans:property name="decisionVoters"> <beans:list> <beans:bean class="org.springframework.security.vote.RoleVoter"/> <beans:bean class="org.springframework.security.vote.AuthenticatedVoter"/> </beans:list> </beans:property> </beans:bean> <beans:bean id="filterInvocationInterceptor" class="org.springframework.security.intercept.web.FilterSecurityInterceptor"> <beans:property name="authenticationManager" ref="authenticationManager"/> <beans:property name="accessDecisionManager" ref="accessDecisionManager"/> <beans:property name="objectDefinitionSource" ref="secureResourceFilter" /> </beans:bean>  <beans:bean id="secureResourceFilter" class="org.security.SecureFilter.MySecureResourceFilter" /> <http auto-config="true" access-denied-page="/403.jsp">  <concurrent-session-control max-sessions="1" exception-if-maximum-exceeded="true" /> <form-login login-page="/login.jsp" authentication-failure-url="/login.jsp" default-target-url="/index.jsp" /> <logout logout-success-url="/login.jsp"/> </http>  <beans:bean id="loggerListener" class="org.springframework.security.event.authentication.LoggerListener"/>  <authentication-provider> <jdbc-user-service data-source-ref="dataSource" users-by-username-query="SELECT U.username, U.password, U.accountEnabled AS 'enabled' FROM User U where U.username=?" authorities-by-username-query="SELECT U.username, R.name as 'authority' FROM User U JOIN Authority A ON u.id = A.userId JOIN Role R ON R.id = A.roleId WHERE U.username=?" /> </authentication-provider> </beans:beans>

As I said in step 1, downloading Spring Security was the trickiest step of all. From there on it was plain sailing...

5
Your rating: None Average: 5 (2 votes)
Published at DZone with permission of its author, Chris Baker.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Comments

Roger Marin replied on Tue, 2008/04/22 - 11:20am

Nice Article i do have 1 question though, is it possible to create a custom <authentication-provider> in spring security like in acegi?

thanks.
 

Jonas Klingstedt replied on Tue, 2008/04/22 - 6:23pm

If I understand your question correctly - yes, using:

<authentication-provider user-service-ref="userDetailsService" />

Vinodh Lakshmin... replied on Tue, 2008/04/22 - 10:26pm

Nice article. I have a question though. How can we implement security in the case of Portal application. We have to develop an application on BEA Portal and would like to implement custom security through Acegi/Spring Security 2.0

 

replied on Wed, 2008/04/23 - 8:08pm

I hava no question because I have learned a little knowledge on java.

Muhammad Sibtain replied on Sun, 2008/05/04 - 2:26am

Can you kindly provide me a complete Example which authenticate from some database using Servlets insted of JSPs along with all configurations i have to implement Spring Security but i am getting failed every time i dont know what i am doing wrong. It will be highly appriciated if it would be provieded at earlier. You can send it to directly at my email muhammad_sibtain@hotmail.com

Thanks & Regards

abcdefgh Stéphane replied on Wed, 2008/05/07 - 2:31am

Hi,

I think the Resource classe import is missing.

Is it the org.springframework.core.io.Resource abstract class you are refering to ?

So in this case which implementation do you use ?

 Thanks

Sharaf Navas replied on Wed, 2008/05/21 - 8:23am

great work dude now i need a example to connect AD / or LDAP using spring security 2.. if any one has done it please post it...

Hung Nguywn replied on Fri, 2008/06/27 - 3:26pm

I observed that filterInvocationInterceptor is created but no where I found it hooked into the filter chain. I scanned the code of Spring Security (2.02) and see that when encountering <http> element the framework creates it own (default) interceptor. Is there any possibiblity you example using the default interceptor ? I hope I am wrong but I just don't see the connection

Shin leo replied on Fri, 2008/07/04 - 5:47pm in response to: abcdefgh Stéphane

[quote=steph38]

Hi,

I think the Resource classe import is missing.

Is it the org.springframework.core.io.Resource abstract class you are refering to ?

So in this case which implementation do you use ?

 Thanks

[/quote]

Yes, I am confusing too... Is there any guy can tell me? many thx!

Roger Marin replied on Mon, 2008/07/07 - 12:28pm

i think the author is using his own resource model for this tutorial, he is using his own implementation which represents a url in this case and each url(resource) has a list of roles that can access it, its basically an example of how you can secure your own resources in your app, when you implement SecureResourceFilter.

 

hope it helps.

abcdefgh Stéphane replied on Wed, 2008/07/16 - 7:56am in response to: Hung Nguywn

[quote=hnguyen]I observed that filterInvocationInterceptor is created but no where I found it hooked into the filter chain. I scanned the code of Spring Security (2.02) and see that when encountering <http> element the framework creates it own (default) interceptor. Is there any possibiblity you example using the default interceptor ? I hope I am wrong but I just don't see the connection[/quote]

 That's what I experimented too !

The <http> tag construct a simple interceptor behind the scene.

I have to declare the following bean with all it subsequents :

<bean id="springSecurityFilterChain" class="org.springframework.security.util.FilterChainProxy">
        <property name="filterInvocationDefinitionSource">
            <value><![CDATA[
       CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON

      PATTERN_TYPE_APACHE_ANT            

      /**=httpSessionContextIntegrationFilter,logoutFilter,authenticationProcessingFilter,securityContextHolderAwareRequestFilter,anonymousProcessingFilter,exceptionTranslationFilter,filterInvocationInterceptor
            ]]></value>
        </property>
</bean>

Then I can see call to the FilterInvocationDefinitionSource objet getAttributes() methods each time I browse a resource.

Regards

Stéphane

 ps : Roger was true, the resource is a custom object factory.

 

 

abcdefgh Stéphane replied on Thu, 2008/07/17 - 4:52am

Hello,

I finally managed to make the thing to work. Now some thought about :

 

It's fun to manage through the database but we can ask ourself, does it really helps ?

Yes of course the ease of management if you have an admin console to update the DB.

But what next ? Do roles needed to access an URL change so often ? Not sure.

Think about a file system. Do permission on the file system need to change so often ?

 

In a good design, you need to fix roles for an URL once. After that the only update, will be

new role assigned to an user. From that point role managment through DB will help.

Stéphane

 

 

Marcos Sousa replied on Mon, 2008/08/04 - 7:36am

I tried your example with no success.

I am using spring security 2.0.3

In old security: acegi I used succefully PathBasedFilterInvocationDefinitionMap

Any tip please?

Mike Mormando replied on Wed, 2008/08/27 - 4:51pm

Hi,

Great article! This is really a great improvement over doing things in Acegi IMHO, in all ways but one. Have you worked much with using Spring Security combined with Spring MVC, annotations based controllers? Basically I've gotten everything to work, but the my controller isn't called if I set the any of the form-login parameters to something I'd expect to be. For example, say the login page, I've got the dispatcher servlet mapped to intercept all calls to *.hmtl, so I set form-login login-page="/login.html", then set up a @RequestMapping("/index.html") to go to /WEB-INF/loginForm.jsp. Doesn't happen, get a 404. If I take the JSP and put it at the root of the web application and set login-page="/loginForm.jsp" everything is happy, but I kind of prefer being able to intercept things with Spring MVC, which worked fine with Acegi.

So basically I'm wondering...would this be a "bug" or a "feature" ;)

Thanks and keep up the great articles!!

 

Mike Mormando replied on Thu, 2008/08/28 - 10:41am in response to: Mike Mormando

Ok, nevermind....I take it all back. I think someone...(read that probably me ;).....changed the servlet mapping and I didn't notice it.

Please continue as you were. ;)

Mike

 

Ryan Wong replied on Tue, 2008/09/16 - 5:38am

Avoiding filter position conflicts

If you are inserting a custom filter which may occupy the same position as one of the standard filters created by the namespace then it's important that you don't include the namespace versions by mistake. Avoid using the auto-config attribute and remove any elements which create filters whose functionality you want to replace.

Note that you can't replace filters which are created by the use of the <http> element itself - HttpSessionContextIntegrationFilter, ExceptionTranslationFilter or FilterSecurityInterceptor.

 

Since that, should we raise a Issue to Spring Security Group? or just back to 1.0 configuration maner avoid using http?

marissa (not verified) replied on Mon, 2010/11/15 - 12:16pm

i think the author is using his own resource model for this tutorial, he is using his own implementation which represents a url in this case and each url(resource) has a list of roles that can access it, lose weight after pregnancy its basically an example of how you can secure your own resources in your app, when you implement SecureResourceFilter.

Tanya Huff replied on Thu, 2011/05/05 - 11:08pm in response to:

@marissa I am not sure you have a clue what you are talking about. Sewing Machine Foot

Sarah Wilson replied on Mon, 2011/08/29 - 10:02pm

Hi, Should I be removing the GraphDemoScene class altogether and add PhotoWidget? Where is the obj for PhotoWidget created? What are the args passed? I understand a scene, Image n name has to be passed. Where are we creating them?? 3. How to get the sources files that you said you have "hacked"? hospital plans

Sarah Wilson replied on Thu, 2011/11/03 - 9:48pm

Well, I think that clears up a few issues for myself. I like this one most houses for sale in centurion 1. 2. 3. 4. 5. Thanks furniture removals

Neo Nishat replied on Sun, 2014/03/30 - 1:32pm

 This pathetic old question again. There has been a lot of tomfoolery talked about Pathway from ACEGI to Spring Security 2.0. Spare us the pantomime for pity's sake! What a barmy notion, trust Glendale Electrician  to come up with this contentless concept. The ever-ineffectual What is Spring Security 2.0 has started hinting about "being well aware" of the problems and "very likely" to take some action, though obviously he "can't promise anything".

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.