Custom Jdbc Spring 4 Authentication

Spring Security makes available a good base customizable authentication layer to transform a plain web application into a secure one.

In this article I’ll show some of the behaviours that are customizable in a Spring solution. Obviously, I won’t describe all the possible parts but only a brief part of them but, I’m sure, once you got them, it’d be easy to understand how to customize even the deeper part of the code.

I’ve written a lot of articles about authentication and authorization process. Base technologies as “Base” or “Digest” Authentication sharing the same aim of more advanced technologies as Open Authentication (OAuth) or Central Authentication System (Cas).

Doesn’t matter what technology is adopted, the idea is the same:

Identify uniquely the user (authentication) and return the role assign to it (authorization).

Let’s talk about the Spring solution. Everything starts from the definition of the Url protected by the authentication:

<!-- Everyone has the access -->
<intercept-url pattern="/" access="permitAll"/>

<!-- Everyone has the access -->
<intercept-url pattern="/login" access="permitAll"/>

<!-- Both Customer and Admin can access -->
<intercept-url pattern="/customer" access="hasAnyRole('ROLE_CUSTOMER', 'ROLE_ADMIN')"/>

<!-- Only Customer user can access -->
<intercept-url pattern="/admin" access="hasRole('ROLE_ADMIN')"/>

So, only user with roles “Customer” or “Admin” could access protected pages. How can a user do the authentication? In this part are configured the login and logout pages:

<!-- Add the return Url -->
<custom-filter ref="MyCustomFilter" position="PRE_AUTH_FILTER" />
<!-- Login Page -->
<form-login login-page='/login' authentication-success-handler-ref="customAuthenticationSuccessHandler" 
authentication-failure-handler-ref="customAuthenticationFailureHandler" />

<!-- Logout Page -->
<logout  delete-cookies="JSESSIONID" success-handler-ref="customLogoutSuccessHandler" />

<remember-me remember-me-cookie="persistenceAuth" key="uniqueAndSecret" 
token-validity-seconds="86400" />

The “remember me” generates a cookie named “persistenceAuth” when the user checks the remember me box in the Login Page.
Briefly, from session based authentication, this check lets us get a cookie based authentication. Not Bad with only one checkBox.

The filter “MyCustomFilter” is applied to redirect the login success on a page that is defined at “return” parameter.
I put it in the session, otherwise, I should have overridden the method “buildRedirectUrlToLoginPage” of the class “LoginUrlAuthenticationEntryPoint” and put them as a filter.

Quite large topic, maybe next time..

You can see how success and failure authentication events are handled in the configuration with two beans:

<beans:bean class="it.blog.spring4security.handler.CustomLogoutSuccessHandler" 
name="customLogoutSuccessHandler"/>

<beans:bean class="it.blog.spring4security.handler.CustomAuthenticationSuccessHandler" 
name="customAuthenticationSuccessHandler"/>

<beans:bean 
class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler" 
name="customAuthenticationFailureHandler">
<beans:property name="defaultFailureUrl" value="/" />
</beans:bean>

The signature of the CustomLogoutSuccessHandler class is

import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler;

public class CustomLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler 
implements LogoutSuccessHandler 

And the signature for CustomAuthenticationSuccessHandler class is

import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;

public class CustomAuthenticationSuccessHandler 
extends SimpleUrlAuthenticationSuccessHandler

Where’re stored the user credentials?

<beans:bean id="dataSource" 
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<beans:property name="driverClassName" value="com.mysql.jdbc.Driver" />
<beans:property name="url" value="jdbc:mysql://127.0.0.1:3306/springsecurity" />
<beans:property name="username" value="root" />
<beans:property name="password" value="" />
</beans:bean>

<beans:bean name="JdbcDaoImpl" class="it.blog.spring4security.dao.MyJdbcDaoSupport">
<beans:property name="dataSource" ref="dataSource" />
<beans:property name="usersByUsernameQuery" 
value="select username, password, isActive as enabled from users where username=?" />
<beans:property name="authoritiesByUsernameQuery" 
value="select u.username, r.role from users u, roles r, user_roles ur 
where u.id=ur.user_id and r.id=ur.role_id and u.username=?" />
<beans:property name="userPersonalDataQuery" 
value="select p.firstname, p.surname from users_personaldata p, users u 
where p.id=u.id and u.username=?;" />
</beans:bean>

A Sql Database (it’s also possible adopting a NoSql solution) and a Dao service layer named MyJdbcDaoSupport with signature:

import org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl;

public class MyJdbcDaoSupport extends JdbcDaoImpl{

@Override
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException {
...
}

The Db schema is the following:

CREATE TABLE `roles` (
`id` bigint(50) NOT NULL AUTO_INCREMENT,
`role` varchar(45) NOT NULL,
PRIMARY KEY (`id`)
) 

CREATE TABLE `user_roles` (
`id` bigint(50) NOT NULL AUTO_INCREMENT,
`user_id` bigint(50) NOT NULL,
`role_id` bigint(50) NOT NULL,
PRIMARY KEY (`id`)
) 

CREATE TABLE `users` (
`id` bigint(50) NOT NULL AUTO_INCREMENT,
`username` varchar(25) NOT NULL,
`password` varchar(60) NOT NULL,
`isActive` enum('0','1') NOT NULL DEFAULT '0',
PRIMARY KEY (`id`)
) 
CREATE TABLE `users_personaldata` (
  `firstname` varchar(45) DEFAULT NULL,
  `surname` varchar(45) DEFAULT NULL,
  `id` bigint(50) NOT NULL,
  PRIMARY KEY (`id`)
)

The schema is not locked by any constrains of columns or table name. The queries specified in the usersByUsernameQuery and authoritiesByUsernameQuery values are used to enquiry the credential tables.

The test class it.blog.spring4security.test.EncodeUsrPwd generates the password for the user “admin” and “customer”.

Finally, the authentication manager which drive the authentication process chain.

<beans:bean id="bcrypt" 
class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder" />

<authentication-manager alias="authenticationManager">
	<authentication-provider user-service-ref="JdbcDaoImpl" >
		<password-encoder ref="bcrypt" />
	</authentication-provider>
</authentication-manager>

The provider type is our Dao Layer applying BCryptPasswordEncoder algorithm. Briefly, in this encryption mode the “salt” is contained in the password string, so, every password have (or can have) a different “salt”.

Another step is to get more information in the User Principal. In many cases, username, credentials and roles are not enough for a user bean in a Web Application.
It’s common to show the name of the user or other personal data such age, gender, etc…

To achieve that aim, I use the MyUser bean which extends the org.springframework.security.core.userdetails.User class.

public class MyUser extends User {
private static final long serialVersionUID = 3798122616154485067L;
private final String firstName;

public MyUser(String username, String password, String firstName, 
boolean enabled, boolean accountNonExpired, boolean credentialsNonExpired, 
boolean accountNonLocked,
Collection<? extends GrantedAuthority> authorities) {
super(username, password, enabled, accountNonExpired, 
credentialsNonExpired, accountNonLocked, authorities);
this.firstName = firstName;
}

public String getFirstName() {
return firstName;
}
}

The attributes “firstName” is used to store the user’s first name that I get from the users_personaldata table.

In the “loadUserByUsername” method I called the internal method “getUserDetails” as the highlight row below:


public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException {
List<UserDetails> users = loadUsersByUsername(username);

if (users.size() == 0) {
logger.debug("Query returned no results for user '" + username + "'");

throw new UsernameNotFoundException(messages.getMessage(
"JdbcDaoImpl.notFound", new Object[] { username },
"Username {0} not found"));
}

UserDetails user = users.get(0); // contains no GrantedAuthority[]

Set<GrantedAuthority> dbAuthsSet = new HashSet<GrantedAuthority>();

dbAuthsSet.addAll(loadUserAuthorities(user.getUsername()));

List<GrantedAuthority> dbAuths = new ArrayList<GrantedAuthority>(dbAuthsSet);

addCustomAuthorities(user.getUsername(), dbAuths);

if (dbAuths.size() == 0) {
logger.debug("User '" + username
+ "' has no authorities and will be treated as 'not found'");

throw new UsernameNotFoundException(messages.getMessage(
"JdbcDaoImpl.noAuthority", new Object[] { username },
"User {0} has no GrantedAuthority"));
}

MyUserPersonalData userPersonalData = getUserDetails(username);

MyUser myUser = new MyUser(username, user.getPassword(), 
userPersonalData.getFirstname(), user.isEnabled(), true, true, true, dbAuths);
return myUser;
}

And the method is the following:


protected MyUserPersonalData getUserDetails(String username)
{
return getJdbcTemplate().query(userPersonalDataQuery, new String[] { username },
new RowMapper<MyUserPersonalData>() {
public MyUserPersonalData mapRow(ResultSet rs, int rowNum)
throws SQLException {
return new MyUserPersonalData(rs.getString(1), rs.getString(2));
}

}).get(0);
}

In this way we got the first name attribute in the principal!

spring4security

This is only a little example to demostrate the Spring Authentication Framework potential. It could seem quite a large code but, trust me, I written less than 30 rows  to get a running a complete authentication system.

You can find the complete solution on GitHub (SpringSecurity4) and the references for this article.

http://consistentcoder.com/spring-security-4-xml-config-jdbc-authentication-annotation-security-method

Spring Security Remember Me

http://antuansoft.blogspot.it/2014/03/mongodb-spring-security-code-example.html

 

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s