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.

  1. anton
    March 6th, 2009 at 20:47
    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
    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
    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
      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
    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.

    • ssdga
      December 1st, 2010 at 04:04
      Quote | #6

      testing the test

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

    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
    Quote | #8

    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. Screamy
    June 16th, 2010 at 00:07
    Quote | #9

    Concise, to-the-point and _exactly_ what I was looking for. Thanks!

  8. Mr. Kudos
    June 23rd, 2010 at 08:26

    Kudos for the simplicity and objetctiviy. Saved me a lot of time.

  9. TUKsubaru
    July 19th, 2010 at 13:40

    Thank very much…
    Code is works.

  10. Alex
    August 12th, 2010 at 21:49

    Gaurav,

    Thank you so much for this blog post. I wasn’t able to really get how authorization worked in Spring Security until reading this. I left a message on the spring forums saying that they should improve their docs and put more details on how to customize the UserDetailsService.

  11. August 27th, 2010 at 15:34

    nice coding friends! please visit me back too!

  12. Harsha
    September 29th, 2010 at 14:37

    Dude….u’r examples are a saviour compared to the documentation provided.

    Above u have used a DB to get the user details.

    But could u guide me in finding the solution. I need client Website to be authenticated using spring-security with CAS and the user credentials are in Oracle bpm directory. trouble is neither spring security nor oracle bpm directory give access to password information.

    In above scenario hw to get a work around for the user authentication.

    Any kinda guidance appreciated.

  13. ilaiyaraja
    October 27th, 2010 at 19:09

    Hi… I am very new to this spring security. i followed the above steps. I have some doubts.
    1. From Where i need to call this service.
    2. Where to set the the return value user object.

    Please reply me…

    Thanks

  14. Steven
    November 16th, 2010 at 13:49

    Helpful, thanks

  15. December 1st, 2010 at 20:54

    Hi there,

    I tried using your code. I have a reference to UserDetailsService(customized) that i have written just like you. But, the authentication does not happen and everytime, it takes the role of anonymous user. What am I missing?

    I would appreciate if you could get back to me quickly
    Priya
    Java Blog

  16. Jorge
    March 12th, 2011 at 05:49

    Hey, this post is great!! I had a different DB structure than suggested by Spring Security and with this I can match my DB schema without creating new tables… Thanks a lot!!

  17. Shankha
    June 20th, 2011 at 11:35

    hi,
    i have 2 questions:
    1.
    private class UserMapper implements ParameterizedRowMapper {

    @Override
    public User mapRow(ResultSet rs, int arg1) throws SQLException {
    return new User(rs.getString(“id_user”), rs.getString(“password_2″), true, true, true, true, getAuthorities(rs.getBoolean(“admin”)));
    }

    }

    new User(rs.getString(“id_user”), rs.getString(“password_2″), true, true, true, true, getAuthorities(rs.getBoolean(“admin”)));

    this method is deprecated, what other way can i implement this.

    2. i have a separate table for users and roles both tables use a join table. how can i get the roles and implement it in this way?

  18. mdjn
    August 21st, 2011 at 22:39

    Thanks for this example. It saved my day.

    Im using JPA config in persistence.xml with eclipselink and jndi datasource.

    I lost all day trying to inject JPA EntityManager (with @PersistenceContext annotation) or DAO object (with @EJB annotation) but it failed – both were NULL.

    So I will go with your example with injecting “dataSource”.

  19. Vivek
    February 27th, 2012 at 18:49

    Thanks a ton mate, great post!

  20. April 16th, 2012 at 23:32

    information. I am social bookmarking and will be tweeting this precise to my own fans! Wonderful blog site and also excellent design and style.

  21. enkhchuluun
    June 14th, 2012 at 12:07

    pretty helpful tutorial. it saves my time.

  22. Aastha
    July 7th, 2012 at 19:22

    Hi I implemented in the same way as it was described. But got an error. My custom UserDeatilsService is nopt getting called. And the user is always getting redirected to authentication-failure-url. I dont know why it is happening please help.

  23. ajay sharma
    December 17th, 2012 at 11:16

    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.

  24. January 4th, 2013 at 05:19

    Hi there, I enjoy reading all of your article. I wanted
    to write a little comment to support you.

  25. January 14th, 2013 at 08:46

    I must thank you for the efforts you’ve put in writing this blog. I’m hoping to view
    the same high-grade content by you in the future as well.
    In truth, your creative writing abilities has inspired me to get my very own blog now ;)

  26. May 22nd, 2013 at 16:25

    Excellent article. Keep posting such kind of information on your site.

    Im really impressed by it.
    Hey there, You have done an excellent job. I will definitely digg it and individually recommend to my
    friends. I am sure they will be benefited from this site.

  27. May 27th, 2013 at 18:34

    Ginger can even cure a lot of ailments apart from its wonderful flavors.
    Then he stood up and, as best as I can describe it, hugged me.

    The string needs to be long enough to secure the bag and to be tied to the
    saucepan handle to reach the bottom of the pan.

  28. May 28th, 2013 at 18:15

    Attractive component to content. I simply stumbled upon your site and in accession capital
    to assert that I get in fact enjoyed account your blog posts.
    Anyway I’ll be subscribing on your feeds or even I fulfillment you get right of entry to constantly quickly.

  29. June 1st, 2013 at 17:11

    I really love your website.. Excellent colors & theme. Did you build this
    amazing site yourself? Please reply back as I’m trying to create my own personal website and would love to learn where you got this from or what the theme is called. Cheers!

  30. June 2nd, 2013 at 08:00

    Use finger and thumb pressure on the big areas,
    like the ball of the foot and heel. While many people would believe that coconut milk and coconut oil are unhealthy because of their high saturated fat content, this oil is actually an incredibly healthy food source.

    Olive oil is also good, but not after it has been heated.

  31. June 4th, 2013 at 02:13

    Pretty! This has been a really wonderful article. Thanks for providing this
    info.

  32. Asking questions are genuinely pleasant thing if
    you are not understanding something totally, however this piece of
    writing provides good understanding yet.

  33. June 14th, 2013 at 00:55

    Hi! Someone in my Facebook group shared this site with us so I came to look it over.
    I’m definitely enjoying the information. I’m bookmarking and will be tweeting this to my followers!
    Outstanding blog and wonderful style and design.

  34. June 20th, 2013 at 21:57

    Wow that was odd. I just wrote an incredibly long comment but after I clicked submit my
    comment didn’t appear. Grrrr… well I’m not writing all that
    over again. Anyway, just wanted to say wonderful blog!

  35. June 25th, 2013 at 03:53

    Hey! Do you use Twitter? I’d like to follow you if that would be okay. I’m definitely enjoying your blog and look forward to new
    posts.

  36. July 5th, 2013 at 12:44

    Right away I am going to do my breakfast, once having my breakfast coming
    yet again to read additional news.

Comments are closed.