March 28, 2011

Spring Security by example: set up and form authentication

Spring Security (former Acegi) is a Java library that handles authorization and authentication in web applications. Documentation on the project web site is, as expected from Spring Source, easy to read and use. I have a feeling though, that most of us first search Google for a fast, technology tutorial, before reading the docs, so in this little article I'm going to show you a few things Spring Security can do, give you a few hints and code snippets I have, after using it a little bit here and there. I'm not going to explain everything throughly, that's what docs are for, but what is here should help to get you started (or decide whether you want to).

Since this is quite a lot of text for a blog post.

Here is the plan:

1. Set up and form authentication
2. User in the backend (getting logged user, authentication, testing)
3. Securing web resources
4. Securing methods
5. OpenID (login via gmail)
6. OAuth2 (login via Facebook)
7. Writing on Facebook wall with Spring Social
 
I'm not a Spring Security Master-Blaster, mind you. Hell, I didn't even had a chance to read the book. I'm just a guy, who had to implement all this stuff withing a few days, and would like to make it easier on you (no to mention keeping a record for later use), so there may be bugs & mistakes, but... well, so far it works and you are welcome to diss me in comments :]

1. How to set up Spring Security

First, let's add maven dependencies. At the time of writing, last stable version was 3.0.5. The exclusions are here because spring-security 3.0.5 uses spring framework (aop and so on) in version 3.0.3.RELEASE, and I'd rather have the latest. You may also want to try 3.1.0.RC1, which is available. Spring social is unfortunately in it's first milestone, but it works, so what the hell...

        <spring.security.version>3.0.5.RELEASE</spring.security.version>
        <spring.social.version>1.0.0.M1</spring.social.version>

            <dependency>
                <groupId>org.springframework.security</groupId>
                <artifactId>spring-security-core</artifactId>
                <version>${spring.security.version}</version>
                <exclusions>
                <exclusion>
                    <artifactId>spring-core</artifactId>
                    <groupId>org.springframework</groupId>
                </exclusion>
                <exclusion>
                    <artifactId>spring-expression</artifactId>
                    <groupId>org.springframework</groupId>
                </exclusion>
                <exclusion>
                    <artifactId>spring-context</artifactId>
                    <groupId>org.springframework</groupId>
                </exclusion>
                <exclusion>
                    <artifactId>spring-tx</artifactId>
                    <groupId>org.springframework</groupId>
                </exclusion>
                <exclusion>
                    <artifactId>spring-aop</artifactId>
                    <groupId>org.springframework</groupId>
                </exclusion>
            </exclusions>
            </dependency>
            <dependency>
                <groupId>org.springframework.security</groupId>
                <artifactId>spring-security-config</artifactId>
                <version>${spring.security.version}</version>
            </dependency>
            <dependency>
                <groupId>org.springframework.security</groupId>
                <artifactId>spring-security-openid</artifactId>
                <version>${spring.security.version}</version>
            </dependency>
            <dependency>
                <groupId>org.springframework.security</groupId>
                <artifactId>spring-security-web</artifactId>
                <version>${spring.security.version}</version>
            </dependency>
            <dependency>
                <groupId>org.openid4java</groupId>
                <artifactId>openid4java</artifactId>
                <version>0.9.5</version>
            </dependency>

Now, add a security filter to your web.xml. This is our entry point for authentication and web resource based authorization. This filter fires up a bunch of other Spring Security filters, which you can find here

    <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>
And I you aren't using Spring IoC container already, don't forget to also add Spring IoC listener: 
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:ioc/applicationContext.xml</param-value>
    </context-param>
    <listener>

How you want your applicationContext.xml (or any other IoC configuration) is up to you, in my example I have a security.xml file with all the spring-security configuration inside.
 
<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-3.0.xsd
           http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.0.xsd">

<http>
</http>
</beans:beans>

Not much, right? Let's do something useful.

2. Form authentication 

For your login/password authentication (called form-based authentication), Spring Security will listen and respond on two url's, one for login (default: j_spring_security_check), one for logout (default: /j_spring_security_logout). All we have to do is either POST to one or GET to the other. Let's start with an example, by modyfing security.xml file:

   <http>
        <session-management session-fixation-protection="migrateSession"/>

        <!-- Make sure you are not sending login/password in plain text on open channel. Use SSL (https) instead -->
        <intercept-url pattern="/login" requires-channel="https"/>
        <intercept-url pattern="/j_spring_security_check" requires-channel="https"/>
        <intercept-url pattern="/**" requires-channel="http"/>

        <!-- form login -->
        <form-login login-page="/login" login-processing-url="/j_spring_security_check"
                    always-use-default-target="true" default-target-url="/"/>

        <!-- logout -->
        <logout logout-url="/j_spring_security_logout"/>

        <!-- remember me -->
        <remember-me key="rememberMeKey" user-service-ref="userDetailsService"/>
    </http>

Session-fixation-protection allows us to “migrate”: create new session and rewrite all the data from the old one, after user logs in succesfully. In form-login tag, we can change defaults and configure what will happen after login (whether to use default target or redirect back).  Same with logout. Remember-me allows us to leave a cookie in the browser, so that the user doesn't have to login every time.

Our login-page can be in any technology we want, the only three important things are:
  • it should point to /j_spring_security_check
  • it should POST j_username, j_password and _spring_security_remember_me (checkbox)
  • it should POST over https

Intercept-url may be used for authorization, but here we just require the browser to use https, so that the password is not traveling as plain text over the wire. Just remember that it's not enough to secure the target URL (/j_spring_security_check), you need to make sure that the POST goes there over SSL. Securing only /j_spring_security_check will end in the browser sending one POST with login and password over http (plain text), getting redirected to https and then posting it again (using SSL). Not exactly secure, is it?

The easiest way to fix it, is to require the browser to use https on the login form page as well, but if you cannot do that (because for example you design requires a small login form on every page), and don't want to hardcode server domain in the form action (which would be inconvenient for the developer) you can change the protocol on the fly using javascript. Here's an example together with changing ports (tomcat defaults):
<head>
    <script language="javascript">
        function forceHttpsOnSubmit(objForm) {
            objForm.action = objForm.action.replace("http:", "https:").replace("localhost:8080","localhost:8443");
        }
    </script>
</head>
<body>
<form method="post" action="/j_spring_security_check" onsubmit="forceHttpsOnSubmit(this)">
        <div id="passwordLoginOption" class="form">
            <div class="row">
                <div class="label left">
                    <label for="j_username">login:</label>
                </div>
                <div class="right">
                    <div class="textWrapper">
                        <input type="text" name="j_username"/>
                    </div>
                </div>
                <div class="cl"></div>
            </div>
            <div class="row">
                <div class="label left">
                    <label for="j_password">password:</label>
                </div>
                <div class="right">
                    <div class="textWrapper">
                        <input type="password" name="j_password"/>
                    </div>
                </div>
                <div class="cl"></div>
            </div>
            <div class="row">
                <div class="right">
                    <label class="forCheckbox" for='_spring_security_remember_me'>
                        Remember me:
                        <input type='checkbox' name='_spring_security_remember_me'/>
                    </label>
                </div>
                <div class="cl"></div>
            </div>
            <div class="buttons">
                <input type="submit" value="Login"/>
            </div>
        </div>
    </form>
</body>

What we need now, is to tell Spring Security how to validate user login and password. So let's add this to security.xml:

    <!-- authentication manager and password hashing -->
    <authentication-manager alias="authenticationManager">
        <authentication-provider ref="daoAuthenticationProvider"/>
    </authentication-manager>

    <beans:bean id="daoAuthenticationProvider" class="org.springframework.security.authentication.dao.DaoAuthenticationProvider">
        <beans:property name="userDetailsService" ref="userDetailsService"/>
        <beans:property name="saltSource">
            <beans:bean class="org.springframework.security.authentication.dao.ReflectionSaltSource">
                <beans:property name="userPropertyToUse" value="username"/>
            </beans:bean>
        </beans:property>
        <beans:property name="passwordEncoder" ref="passwordEncoder"/>
    </beans:bean>

    <beans:bean id="userDetailsService"  name="userAuthenticationProvider" class="eu.solidcraft.backend.infrastructure.services.authentication.form.AuthenticationUserDetailsGetter">
        <beans:constructor-arg index="0" ref="userRepository"/>
    </beans:bean>

    <beans:bean id="passwordEncoder" class="org.springframework.security.authentication.encoding.ShaPasswordEncoder">
        <beans:constructor-arg index="0" value="256"/>
    </beans:bean>
Authentication Manager fires up all authentication providers against given login and password. Why? Because at some point you may want to change your providers or (more probable) hashing algorithm from, let's say SHA256 to SHA512, and since you cannot recalculate old passwords (you should keep the hash only), and you may not be able to tell which user uses which encoding, you will have to validate the password two times.

Our authentication provider (DaoAuthenticationProvider) is an out of the box spring implementation that allows us to give it a hashing algorithm, a salt source and the most important part:  userDetailsService, which is a class get you the user from a repository and hence I name it AuthenticationUserDetailsGetter.

The interface is that simple:

public interface UserDetailsService {
    UserDetails loadUserByUsername(String username)
        throws UsernameNotFoundException, DataAccessException;
}
And the implementation no more complicated

public class AuthenticationUserDetailsGetter implements UserDetailsService {
    private UserRepository userRepository;

    //required by cglib because I use class based aspect weaving
    protected AuthenticationUserDetailsGetter() {
    }

    public AuthenticationUserDetailsGetter(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Override
    @Transactional
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException {
        User user = userRepository.findByLogin(username);
        throwExceptionIfNotFound(user, username);
        return new AuthenticationUserDetails(user);
    }

    private void throwExceptionIfNotFound(User user, String login) {
        if (user == null) {
            throw new UsernameNotFoundException("User with login " + login + "  has not been found.");
        }
    }
}

Spring framework will use this class to get the user by login (called username, may as well be an email – you decide how to find the user in this class), and then hash the password  using passwordEncoder and check whether it's the same as what UserDetails object has. UserDetails is an interface representing our user from the security perspective.


public interface UserDetails extends Serializable {
    Collection<GrantedAuthority> getAuthorities();
    String getPassword();
    String getUsername();
    boolean isAccountNonExpired();
    boolean isAccountNonLocked();
    boolean isCredentialsNonExpired();
    boolean isEnabled();
}
The interface is self explanatory. “Authorities” will be useful later for authorization. For now let's implement this in a very simple way. Note, that though it may be tempting to just implement this interface in you User class, and pass it on, do not do it, as your User entity may be quite heavy and not serializable (hibernate proxy, lot of properties, some left for lazy loading, etc.) and UserDetails is supposed to be light.


public class AuthenticationUserDetails implements UserDetails {
    private Long id;
    private final String login;
    private final String passwordHash;
    private final boolean enabled;
    private HashSet<GrantedAuthority> grantedAuthorities = new HashSet<GrantedAuthority>();

    public AuthenticationUserDetails(User user) {
        this.login = user.getUsername();
        this.passwordHash = user.getPassword();
        this.enabled = user.isEnabled();
        this.grantedAuthorities.addAll(user.getAuthorities());
    }

    @Override
    public Collection<GrantedAuthority> getAuthorities() {
        return grantedAuthorities;
    }

    @Override
    public String getPassword() {
        return passwordHash;
    }

    @Override
    public String getUsername() {
        return login;
    }

    @Override
    public boolean isEnabled() {
        return enabled;
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    public String getLogin() {
        return login;
    }
}

Next: User in the backend (getting logged user, authentication, testing)

19 comments:

  1. Can you please add imports to your classes and some details on creating your own User and Role entities. Without role the reading is confusing. For example, on AuthenticatedUserDetails you call getId().... there is no getId()?

    ReplyDelete
  2. @Ali Muzaffar: there's no getId call in this post, so I assume you are refering to the whole set of my posts about Spring Security. You are correct, AuthenticationUserDetails in my project does have a private Long id, that I've not shown in this post, because I wanted to keep it as simple as possible. I've just fixed that.

    As for the User entity: the point in Spring Security is that it can be you own class (no interface to implement), and you only need to have a lightweight (think: Data Transfer Object) UserDetails implementation, which I've shown here.

    As for the Roles, there is an interface GrantedAuthority, and an implementation (GrantedAuthorityImpl) out of the box, working on simple strings, as I've described here.

    To keep it simple: when talking about Roles you can think about a list of strings, and have no other implementation whatsoever.

    For more complex situations (roles that have priviladges, users having many roles), there is an open spource GWT implementation of those entities, with a GUI panel to manage users, roles and privilages here: https://github.com/TouK/gxt-tools/tree/master/gxt-tools-lib/src/main/java/pl/touk/wonderfulsecurity

    A user implementation is here: https://github.com/TouK/gxt-tools/tree/master/gxt-tools-lib/src/main/java/pl/touk/wonderfulsecurity/beans

    A role implementation is here: https://github.com/TouK/gxt-tools/blob/master/gxt-tools-lib/src/main/java/pl/touk/wonderfulsecurity/beans/WsecRole.java

    I wouldn't recommend this project as a reference (it's ugly, not TDD) but it works, so it may be fine for learning.

    ReplyDelete
  3. Quickfix: user implementation is in WsecUser, here

    ReplyDelete
  4. Please can you check is remember-me service really works? I have look like configuration of spring security 3.0.5 with spring mvc and I spend 1 day trying to enable it( I try to user own entry point, own remember me service) I don't won't to make some filter that check if user is authorize and create some cookie than. Any ideas?

    ReplyDelete
  5. my bad ... or not my ...
    me or my friend make method getUsername in User class(User implements UserDetails) and write into method only 1 line: return null;
    ... oh my brain is boiling :) but remember me finally works.

    ReplyDelete
  6. Excellent post! I am new in Spring and I could not find out why userRepository was always null. saved my project!

    ReplyDelete
  7. Hi,

    I have a question regarding Spring Security, is it possible to pass username to Spring Security from an application? i.e. We have a custom made application which returns Windows username(NTLM), how can I pass this to our new Spring Web application?, so that we can leverage the use of Spring Security features.

    Any help on this is highly appreciable?

    Thanks

    ReplyDelete
  8. @Jacob
    Do you have an application, where you'd like to add Spring Security, but right now you are authorizing using NTLM?
    Or do you have an application, which integrates with NTLM, and which will redirect the user to another application with Spring Security and you want him to be authorized?

    If you have just one java application, with NTLM already handled, and want to add Spring Security to it, login the user in the backend, like shown here http://blog.solidcraft.eu/2011/03/spring-security-by-example-user-in.html [login(Long userId)].

    If you have two separate applications, and you want to authorize the user in the second, based on what the first says, you should create your own Filter in the second (the one with Spring Security), extending AbstractPreAuthenticatedProcessingFilter, and plugging it into spring web filters.
    AbstractPreAuthenticatedProcessingFilter is a base for filters, where user is already authorized (NTLM in your situation) and the filter just needs to take the user data somehow and feed it into Spring.

    This is very easy. The hard part, is how to pass the data from the first application to the second, in a way which will be impossible to break. One way to do it, is like OpenID works.

    In the first application, you create a random token after authorization. You save the token, and redirect the user to the second the user the second application with this token in POST/GET. The second application, in your custom AbstractPreAuthenticatedProcessingFilter, will take the token, ask the first application via REST/SOAP/Whatever, what the correct user is for this token, and Authenticate that user.

    So in essence, this second approach means, that the user talks with the first application, which redirects him to the second application, and then the second application calls the first application directly, to confirm who the user is.

    If your situation doesn't fit into this picture, please provide some more information. www.websequencediagrams.com/ is a great tool to explain how you want this stuff to work.

    ReplyDelete
    Replies
    1. Hi Jakub,

      Thanks for your reply.

      We do have one application where we are getting Windows username by plain NTLM method, it is basically calling Servlet and gets the domain username.

      As Spring 3 doesn't support NTLM, we are thinking of passing username from one application to our new Spring web application where we would like to have Spring Security.

      As you have mentioned that passing data would be a problem, I have a question here.
      We authenticate using first application and username can be passed to second application in URL?

      Second question I have is is it possible in one application use plain NTLM authentication using servlets and use that in Spring Security rather than using Spring's authentication? Is this the same as http://blog.solidcraft.eu/2011/03/spring-security-by-example-user-in.html?

      Thanks again

      Delete
    2. @Jacob

      It is possible to integrate NTLM with Spring Security directly. Take a look at those:
      https://github.com/dblock/waffle/blob/master/Docs/spring/SpringSecuritySingleSignOnFilter.md
      http://aloiscochard.blogspot.com/2010/03/spring-security-ntlm-3.html

      Back to your questions:

      1. Yes, it can be passed in the URL, but do not do that. It will be trivial to hack.
      Send a random token instead, and confirm the token between servers, like I've described above.

      2. Yes. It is possible. Yes, you can login the user "in the backend" without any filters (probably the easiest method).

      Delete
    3. Jakub

      https://github.com/dblock/waffle/blob/master/Docs/spring/SpringSecuritySingleSignOnFilter.md
      http://aloiscochard.blogspot.com/2010/03/spring-security-ntlm-3.html

      These two options are not possible at the moment because our management already have an plain NTLM authentication (http://stackoverflow.com/questions/11830957/ntlm-authentication-in-a-web-application-java). NTLM Authentication using JCIFS is not allowed because they do not want to provide domain controller username and password which is required for successful implementation.

      So I have stick with your suggested approaches
      1) Passing token from first application to second.
      2) Login the user in backend.

      Are there any doc or sample exists for passing token from one app to another?

      Thanks

      Delete
    4. Well, it's as simple as generating long, random string, and adding it to redirect (could be in url, but POSTing it via https would be safer).

      Delete
    5. Jakub

      When I receive the token in second application, how I do use that token? should I do anything like decryption or something like that?

      Thanks

      Delete
    6. Ok, so you've generated a random token on the first application, saved it together with the username, and sent the token to the user with redirection to the second application, right?

      Now your second application receives this token. It doesn't know what the username is, and there is nothing to decrypt from the token, because the token should be just a random string.

      The second application needs to ask the first application, for the username saved with this token. You can do this via REST or SOAP or whatever you like. If you are playing in an intranet (and since you use NTLM I suppose you do), there is not much to do here, chances for a man-in-the-middle attack are low, because your client probably controls this network. I'd use SSL/TSL (HTTPS), though, just because it's cheap and easy. If you want to be very secure, you can of course make a direct network connection between those two applications (either via ACLs on a router, VLNs or a direct fiber link).

      I see no point in hashing/encrypting the token, because it's single use only random string. I'd make sure that the token is sent via HTTPS, both from the first application to the user, and from the user to the second application. And from the second application back to the first.

      The only thing left is to get rid of the token on the first application, if the user's HTTP session dies. And delete the token on the first application, right after the first question from the second application.

      When the second application receives the username connected with the token, from the first application, just log the user in (via your custom filter), and you are all set.

      That is how I see it right now.

      Delete
    7. Jakub,

      I will try your suggested approaches and will come back to you.

      Thanks

      Delete
  9. This comment has been removed by the author.

    ReplyDelete
  10. This comment has been removed by the author.

    ReplyDelete
  11. HI Jacob,
    Can you please pick an example of integrating a Login form with Spring Security using Authentication and Authorization against Active Directory and LDAP.

    ReplyDelete
    Replies
    1. Not really. It's been ages since I had to use Active Directory, and I don't have much experience with LDAP. Sorry.

      Delete