Writing custom UserDetailsService for spring security

February 11th, 2009 | Tags: , , , ,

I wish spring security would work on their documentation and tell people how easy it is to implement a custom service for loading user details. You don’t HAVE to use JDBC to do that, you can write your very own hibernate, toplink or whatever DAO to do just that. It’s important to realise that spring-security does not send your password to the database ever. Instead it loads a user’s details and then compares it’s password internally before validating the user and granting it access to internal pages.

In my case I did not want to maintain a list of authorities because there were ever only going to be two kinds of user’s, admins and non-admins. Administrator access was to be determined by a boolean field in the table. So I needed to override the queries that the default UserDetailsService implementation, JdbcDaoImpl uses and also set an extra role for user’s who were admins. It sounds simple and it actually is simple only if you don’t dive into the documentation.

The table structure and some data (in postgresql) :

CREATE TABLE test.users
(
  id integer NOT NULL,
  name character varying(100) NOT NULL,
  email character varying(100) NOT NULL,
  "admin" boolean DEFAULT false,
  "password" character varying(20)
)
WITH (OIDS=FALSE);

INSERT INTO test.users VALUES (1, 'normal user 1', 'normal@email.com', 'f', 'pass1');
INSERT INTO test.users VALUES (2, 'normal user 2', 'normal2@email.com', 'f', 'pass2');
INSERT INTO test.users VALUES (3, 'admin user 1', 'admin@email.com', 't', 'pass3');

Remember that UserDetailsService is only used to lookup the user and not perform any sort of validation etc. Implementation of such a service would be :

package com.codercorp.dwr;

//imports 

public class MyUserDetailsService implements UserDetailsService {

	private DataSource	dataSource;

	@Override
	public UserDetails loadUserByUsername(String username)
			throws UsernameNotFoundException, DataAccessException {
		String sql = "select * from user where email like :username";
		MapSqlParameterSource source = new MapSqlParameterSource();
		source.addValue("username", username);

		SimpleJdbcTemplate sjt = new SimpleJdbcTemplate(getDataSource());
		User user = sjt.queryForObject(sql, new UserMapper(), source);
		return user;
	}

	private GrantedAuthority[] getAuthorities(boolean isAdmin) {
		List<GrantedAuthority> authList = new ArrayList<GrantedAuthority>(2);
		authList.add(new GrantedAuthorityImpl("ROLE_USER"));
		if (isAdmin) {
			authList.add(new GrantedAuthorityImpl("ROLE_ADMIN"));
		}
		return authList.toArray(new GrantedAuthority[] {});
	}

	private class UserMapper implements ParameterizedRowMapper<User> {

		@Override
		public User mapRow(ResultSet rs, int arg1) throws SQLException {
			return new User(rs.getString("email"), rs.getString("password"), true, true, true, true, getAuthorities(rs.getBoolean("admin")));
		}

	}
}

The User object that loadByUsername returns is of the class org.springframework.security.userdetails.User. We don’t really need extra functionality for this tutorial but if you do, you can simply extend this class or alternatively implement org.springframework.security.userdetails.UserDetails. As is evident from the code all we do is add a ROLE_USER to every user who’s details we obtain and another ROLE_ADMIN for every user that is marked as an admin. We also use the email address as the login credential and as the name of the user, nothing wrong with that and you can change that anytime you like by extending the org.springframework.security.userdetails.User class.

Now for the spring security configuration. This is the tricky part and if you haven’t done this before I strongly suggest you read my earlier article on configuring the basics. If you go looking on the web, you’ll see a lot of people talking about authentication providers and what not. Thats only neccessary when you plan to authenticate user’s using some other way instead of their input details. If you just want to use a database then the following two lines in your security-applicationContext.xml are more than enough :

<b:bean id="customUserDetailsService" class="com.codercorp.npdac.security.MyUserDetailsService">
	<b:property name="dataSource" ref="dataSource" />
</b:bean>

<s:authentication-provider user-service-ref="customUserDetailsService" />

Those are the ONLY lines that you need to add to your configuration file. It’s pretty easy to see that all we’re doing is instantiating our user details service and then telling our AuthenticationProvider, DaoAuthenticationProvider by default, to use our service to retrieve user details. I did run into a problem while configuring spring security and recieved a org.springframework.security.providers.ProviderNotFoundException but that was only because I had set auto-config attribute of the http element to false because of which the default authentication provider was not initialized.

Your webapp can now use custom tables to implement it’s security.

Share and Enjoy:
  • del.icio.us
  • Google Bookmarks
  • DZone
  • Reddit
  • Digg
  • Facebook
  • Netvibes
  • StumbleUpon
  • Technorati
  • LinkedIn
  • MySpace
  • Print
  • Slashdot
  • Share/Bookmark

No related posts.

  1. anton
    March 6th, 2009 at 20:47
    Reply | Quote | #1

    By just adding something like the following to the configuration file it works too (but I’m using an Oracle database)

  2. leithold
    April 22nd, 2009 at 14:42
    Reply | Quote | #2

    wow thanks so much for this man. ill try it out. hope it works for me.

  3. leithold
    April 22nd, 2009 at 15:43
    Reply | Quote | #3

    Hey its me again. i have a question. i have a user table similar to yours. the thing is, i need to pass the id of the user to the next page when the log-in is successfull. i was wondering how to do this.

    (i have a hunch that maybe it has something to do with the form-login
    default-target-url but i could be dead wrong)

    thanks so much.

    • Gaurav Arora
      April 22nd, 2009 at 15:50
      Reply | Quote | #4

      You can get the user object itself on the next page using SecurityContextHolder.getContext().getAuthentication().getPrincipal(). But you really shouldn’t have to access the user’s ID on the jsp page. All that should be done in the controller.

  4. manoj
    May 12th, 2009 at 13:20
    Reply | Quote | #5

    Hi,
    While I have been able to customize the JasperServer to use existing iBatis/Struts infrastructure and integrate authentication using existing app, there is one thorn. How can I change the login page to accept another field? Say I want user to enter Domain in addition to username and password. And use the three to authenticate and eventually show reports. I have been able to write my custom Dao that validates jasper user from my DB, but how do I get new attribute – domain to reach my Dao, so that it can be used to authenticate the user? While customizing the login page to accept additional fields is straightforward – you just need to modify login.jsp and make it your login page – making the new values reach \’some Java handler\’ is an issue.

    Appreciate inputs on this.

  5. Sajitha
    June 15th, 2009 at 12:05
    Reply | Quote | #6

    Hi,

    This was a very useful article :)
    I have a requirement, which is the user account has to be locked after 5 consecutive failed attempts. How can this be acheived. I have to store the details like “First Failed Try Time” and “Locked Flag” in the database.
    So basically, I have to customize something after the passwords mismatch. Iam not sure how this is done. Given that iam using Spring’s Form based authentication. Where should i write this Logic??
    If any one has got any idea PLEASE..E share.

    Regards,
    Sajitha

  6. Daniel
    September 14th, 2009 at 21:26
    Reply | Quote | #7

    Thanks for the tutorial, I’m new to Spring Security and it really helped me out!

    I want to return my own user Object (with extra parameters ), so I created a custom UserDetailsService and extended org.springframework.security.userdetails.User.

    However in my client side application (on successful login) an Object with two parameters is returned:
    – authorites
    – name (which is the username)

    Below is a snippet of my custom UserDetailsService:

    public MyUser loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException {

    String sql = “select * from user where emailAddress like :username”;
    MapSqlParameterSource source = new MapSqlParameterSource();
    source.addValue(”username”, username);

    SimpleJdbcTemplate sjt = new SimpleJdbcTemplate(getDataSource());
    MyUser user = sjt.queryForObject(sql, RowMapperUtil.getUserRowMapper(), source);
    return user;
    }

    I’m a bit lost here! I am returning an Object of type MyUser and mapping all the fields correctly, so I am not sure where I am going wrong.

    Your help is greatly appreciated,
    Daniel

  7. 1 trackbacks
TOP