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.
No related posts.
Tags: dwr, Java, Spring, spring security, web security
[...] (or your own implementation of UserDetails) which contains your user details. I have written an article showing you how to do the [...]
By just adding something like the following to the configuration file it works too (but I’m using an Oracle database)
wow thanks so much for this man. ill try it out. hope it works for me.
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.
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.
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.
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
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
Concise, to-the-point and _exactly_ what I was looking for. Thanks!
Kudos for the simplicity and objetctiviy. Saved me a lot of time.
Thank very much…
Code is works.