Sunday, November 10, 2013

Variation on authentication-manager in Spring Security

During the feasibility study on authentication module, I been thinking to implement BO/DAO pattern. While working on the POC, I keep asking myself: "Is this the right way of doing this?" Without re-implementing the wheel, I search on the WEB to see whether is there any existing framework out there which specialize on authentication? Yes, I found Apache Acegi and Spring Security. Interestingly, there is a rumours about Spring Security is actually origin from Apache Acegi. Anyway what I'm concern is since I'm already using Spring framework at the earlier stage of the development, thus I will continue with the Spring family.

The configuration is pretty straight forward to setup. One thing that caught my attention is the usage of authentication-manager. The code snippet shown below is the typical usage to everyone which is to make a quick POC demo with a static account. In this case the login ID is huahsin and the password is 1234.
 ...
 <authentication-manager alias="authenticationManager">
  
  <authentication-provider>
   <password-encoder hash="plaintext"/>
   <user-service>
    <user authorities="ROLE_USER" name="huahsin" password="1234"/>
   </user-service>
  </authentication-provider>
 </authentication-manager>
In real world, the user account are usually store in a database for later verification. Now code snippet below shows that user-service has been replace by jdbc-user-service. Notice that users-by-username-query is responsible for retrieving the user name whereas authorities-by-username-query is the sub-query of previous query that retrieve the user role.
 ...
 <authentication-manager alias="authenticationManager">
  ...
  <authentication-provider>
   <jdbc-user-service authorities-by-username-query="select users.username, authority.authority as authority from users, authority where users.username = ? and users.username = authority.username" data-source-ref="dataSource" users-by-username-query="select username, password, 'true' as enabled from users where username=?"/>
  </authentication-provider>
 </authentication-manager>
This is the initial idea from me but somehow there are still objection on it as I have expose the risk on the SQL code is being published to the other developers. This is very subjective to some company. But in mine company there is IT governance look after us. They are very concern on this and they don't like any sensitive data being retrieved easily. Thus I have to move this into Java code like this:
package org.huahsin.security;

public class AuthServiceProvider implements AuthenticationProvider {

 private User user;
 
 private Role role;

 @Override
 public Authentication authenticate(Authentication authentication) throws AuthenticationException {
  
  String name = authentication.getName();
  String password = authentication.getCredentials().toString();
  
  /*
   * assuming there is an DAO object retrieving the particular user information
   * from table and the information is store inside User entity.
   */
  
  if( user.getUsername().equals(name) && user.getPassword().equals(password) ) {
   List<grantedauthority> grantedAuths = new ArrayList<grantedauthority>();
   grantedAuths.add(new SwitchUserGrantedAuthority(role.getRole(), authentication));
   Authentication auth = new UsernamePasswordAuthenticationToken(name, password, grantedAuths);
   
   return auth;
  }
  else {
   return null;
  }
 }

 @Override
 public boolean supports(Class authentication) {
  
  return authentication.equals(UsernamePasswordAuthenticationToken.class);
 }

}

And trigger this bean from Spring like this way:
 ...
   <authentication-manager alias="authenticationManager">
  
      <authentication-provider ref="authServiceProvider"/>
   </authentication-manager>

   <beans:bean class="org.huahsin.security.AuthServiceProvider" id="authServiceProvider"/>
   ...
Notice that the code above is extending the AuthenticationProvider class, this shows the general usage during authentication process as it provide wider flexibility on identifying a user. There is always an alternate solution that could allow me to authenticate a user, which is by extending the UserDetailsService. This class has an override method, loadUserByUsername() which will retrieve the specific user during the authentication process. As shown in the code snippet below:
@Service("authServiceProvider")
public class AuthServiceProvider implements UserDetailsService {

 private User user;
 
 private Role role;

 @Override
 public UserDetails loadUserByUsername(String username)
   throws UsernameNotFoundException, DataAccessException {
  
  /*
   * assuming there is an DAO object retrieving the particular user information
   * from table and the information is store inside User entity.
   */
  
  return new org.springframework.security.core.userdetails.User(user.getUsername(), 
      user.getPassword(),
      true,
      true,
      true,
      true,
      role.getRole());
 }
}
Since this class is extending UserDetailsService, there is a slightly difference when trigger this bean from Spring, now the bean is register with user-service-ref.
 ...
 <authentication-manager alias="authenticationManager">
  <authentication-provider user-service-ref="authServiceProvider"/>
 </authentication-manager>
Usually the user's credential store in database are encrypted, says SHA512, then I just need to mention the encryption algorithm used in Spring like this:
 ...
 <authentication-manager alias="authenticationManager">
  <authentication-provider ref="daoAuthenticationProvider"/>
 </authentication-manager>

 <beans:bean class="org.springframework.security.authentication.dao.DaoAuthenticationProvider" id="daoAuthenticationProvider">
  <beans:property name="userDetailsService" ref="authServiceProvider"/>
  <beans:property name="passwordEncoder" ref="passwordEncoder"/>
 </beans:bean>

 <beans:bean class="org.springframework.security.authentication.encoding.ShaPasswordEncoder" id="passwordEncoder">
  <beans:constructor-arg index="0" value="512"/>
 </beans:bean>
Spring very kind for me, there is no single piece of Java code on user's password encryption, it is all handle automatically.

No comments: