Acegi is the security component for springframework. Since Acegi works with interfaces, extending the framework is easy. There are some good abstract classes you can extend as well. Todays blog deals with customizing the UserDetails class. This is an interface with the following methods:

public interface UserDetails {
  public boolean isAccountNonExpired();
  public boolean isAccountNonLocked();
  public GrantedAuthority[] getAuthorities();
  public boolean isCredentialsNonExpired();
  public boolean isEnabled();
  public String getPassword();
  public String getUsername();
}

If you want a custom UserDetails implementation you need to implement this interface. I added two methods to the implementation:

  public String getFullname();
  public Date getLastLoggedIn();

I take it the names of the methods are self documenting. What other components do we need? We need a custom jdbcDaoImpl that has a mapper to map the query to the PersonDetails object. We also have to change the configuration of this custom component so that the custom properties are also loaded. Lets have a look at the xml part of the configuration first. Pay special attention to the queries.

<bean id=”jdbcDaoImpl” class=”org.appfuse.security.CustomJdbcDaoImpl”>
  <property name=”dataSource”><ref bean=”dataSource”/></property>
  <property name=”usersByUsernameQuery”>
    <value>select username,password,fullname,last_logged_in,enabled from person where username=?</value>
  </property>
  <property name=”authoritiesByUsernameQuery”>
    <value>select p.username,r.name as rolename from person p, role r, authority a where a.person_id = p.id AND a.role_id = r.id AND p.username=?</value>
  </property>
</bean>

Now we implement the CustomJdbcDaoImpl class. This class is a subclass of the JdbcDaoImpl class. We override the loadByUsername method. An innerclass is created as a mapper between the sql statement and the PersonDetails object.

public class CustomJdbcDaoImpl extends JdbcDaoImpl {
  public UserDetails loadUserByUsername(String username)
      throws UsernameNotFoundException, DataAccessException {
    List users = (new PersonsByUsernameMapping(getDataSource())).execute(username);

    if (users.size() == 0) {
      throw new UsernameNotFoundException(“User not found”);
    }

    PersonDetails person = (PersonDetails) users.get(0);
    List dbAuths = getAuthoritiesByUsernameMapping().execute(person.getUsername());

    if (dbAuths.size() == 0) {
      throw new UsernameNotFoundException(“User has no GrantedAuthority”);
    }

    GrantedAuthority[] arrayAuths = {};

    addCustomAuthorities(person.getUsername(), dbAuths);

    arrayAuths = (GrantedAuthority[]) dbAuths.toArray(arrayAuths);

    return new PersonDetails(person.getUsername(), person.getFullname(),
      person.getPassword(),person.getLastLoggedIn(),person.isEnabled(),
      true, true, true,arrayAuths);
  }

  protected class PersonsByUsernameMapping extends MappingSqlQuery {

    protected PersonsByUsernameMapping(DataSource ds) {
      super(ds, getUsersByUsernameQuery());
      declareParameter(new SqlParameter(Types.VARCHAR));
      compile();
    }

    protected Object mapRow(ResultSet rs, int rowNum) throws SQLException {
      String username = rs.getString(1);
      String password = rs.getString(2);
      String fullname = rs.getString(3);
      Date lastLoggedIn = rs.getDate(4);
      boolean enabled = rs.getBoolean(5);

      PersonDetails person = new PersonDetails(
        username, fullname, password, lastLoggedIn, enabled, true, true, true,
        new GrantedAuthority[] { new GrantedAuthorityImpl(“HOLDER”) });
      return person;
    }
  }
}

Actually this is all there is to it. But why all this trouble? We added two properties, the fullname and LastLoggedIn. Fullname is a value the user enters when he registers for the website. We want to show this property on the page to see who is logged on. What about the second property? We want to show what the last time was that a used logged in to the application. Showing the data is not that hard, but how can we plugin to the acegi framework to get information about who logged in when. The answer is [b]events[/b].

Spring uses a very simple event mechanism that is used by acegi to provide you with information. You need to create a bean that implements the ApplicationListener interface, then simply configure the bean in the spring config file.

  <bean id=”wrongPasswordListener” class=”org.appfuse.security.AuthenticationFailurePasswordEventListener”>
    <property name=”personManager” ref=”personManager”/>
  </bean>

The code for the bean looks like this:

  public void onApplicationEvent(ApplicationEvent event) {
    if (event instanceof AuthenticationFailurePasswordEvent) {
      AuthenticationFailurePasswordEvent authEvent = (AuthenticationFailurePasswordEvent) event;
      personManager.disablePerson(authEvent.getUser().getUsername());
    }
    if (event instanceof AuthenticationSuccessEvent) {
      AuthenticationSuccessEvent authEvent = (AuthenticationSuccessEvent) event;
      personManager.saveLoggedOnPerson(authEvent.getUser().getUsername());
    }
  }

So we respond to two types of events, one that is raised in case of entering a wrong password, the other if a user successfully logged on. With these events we can lock an account if a user enters a wrong password more then three times and we can store the lastLoggedIn time if a user successfully logges in.

The last part is showing the fullname and the lastloggedin property in the jsp page.

<fmt:message key=”general.welcome”>
  <fmt:param><%=((PersonDetails) ((SecureContext)ContextHolder.getContext()).getAuthentication().getPrincipal()).getFullname()%></fmt:param>
</fmt:message>
<fmt:message key=”general.lastlogin”>
  <fmt:param><fmt:formatDate pattern=”dd/MM/yyyy” value=”<%=((PersonDetails) ((SecureContext)ContextHolder.getContext()).getAuthentication().getPrincipal()).getLastLoggedIn()%>”/></fmt:param>
</fmt:message–%>

Hope this can help you if you want to create your own custom authentication principal. If you want to have a look at the complete code, have a look at the javaforge project I created.
http://javaforge.com/proj/forum.do?proj_id=71

Springframework Acegi principal customization and events