Archive

Archive for July, 2011

Writing a custom JPA UserDetailService

When using Spring Security in a ROO application, often there is a need for user management, where you can add, edit and delete users etc.. For that you make entity beans for users, roles, groups etc. – depending on your needs. Then you change some sql queries in the jdbc-user-service section of the applicationContext-security.xml file, so that Spring Security uses the same tables as the new entity beans. I would prefer the security to access the tables in the same manner as the rest of the application. Here I will describe how to make a custom UserDetailService, that uses the JPA entity beans in the login process.
For this I only use one entity bean for the users, I have 4 roles and I don’t need more, so they are of type enum, but it could just as well have been a @ManyToMany relation to a role entity. Here is the user entity:

public class SiteUser {

    @NotNull
    @Size(min = 3, max = 30)
    @Column(unique = true)
    private String username;

    @Size(max = 100)
    private String passwd;

    @NotNull
    private Boolean enabled;

    @ElementCollection
    private Set<SiteRole> roles = new HashSet<SiteRole>();

    }
}

You need to create a new class that implements UserDetailService. There is only one method in the interface loadUserByUsername and it gives the fully populated User. Spring recommends to return an immutable implementation of UserDetails, so I will the return the usual spring User. The SiteUser. FindSiteUsersByUsername method is a ROO generated finder.

public class JpaUserDetailService implements UserDetailsService {

    @Override
    public UserDetails loadUserByUsername(String username) throws 								UsernameNotFoundException, DataAccessException {
        TypedQuery<SiteUser> siteUsersQuery = SiteUser.findSiteUsersByUsername(username);
        List<SiteUser> siteUsers = siteUsersQuery.getResultList();
        if (siteUsers.isEmpty()) {
            throw new UsernameNotFoundException("Username " + username + " not found");
        }
        SiteUser siteUser = siteUsers.get(0);
        Set<GrantedAuthority> authorities = new HashSet<GrantedAuthority>();
        Set<SiteRole> siteRoles = siteUser.getRoles();
        System.out.println("siteRoles = " + siteRoles);
        for (SiteRole siteRole : siteRoles) {
            authorities.add(new GrantedAuthorityImpl(siteRole.name()));
        }
        return new User(siteUser.getUsername(), siteUser.getPasswd(), siteUser.getEnabled(),
                true, true, true, authorities);
    }
}

Now we only need to tell spring security to use the new UserDetailservice. In applicationContext-security.xml do the following.

    <authentication-manager alias="authenticationManager">
        <authentication-provider user-service-ref="customUserDetailsService">
            <password-encoder hash="sha-256"/>
        </authentication-provider>
    </authentication-manager>

    <beans:bean id="customUserDetailsService" class="dk.amfibia.cv.security.JpaUserDetailService"/>

In the first attempt I ran into a LazyInitializationException in the siteUser.getRoles()

This error is described here https://jira.springsource.org/browse/ROO-609. It should have been solved in ROO 1.0.2 but the problem seems to be still here in 1.1.3 – I left a comment in jira. As described in the jira, the solution is, in the web.xml, to move the filter mapping of springSecurityFilterChain below the filter-mapping of Spring OpenEntityManagerInViewFilter. Now it works.