Friday, April 13, 2007

Customize Acegi SecurityContext

This post describes how to modify the User details stored in the Acegi Security Context.

Acegi Security uses the SecurityContextHolder object to store details of the current security context of the application. The SecurityContext holds the details of the authenticated principal in an Authentication object. By default the SecurityContextHolder uses a ThreadLocal to store these details, so that they will be available all methods in the current thread of execution.In order to obtain the Principal, you would use the following line
Object obj = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
Usually, the principal is an object of the type UserDetails. The UserDetails object is sort of an adapter between the security database and Acegi SecurityContextHolder. Acegi uses a UserDetailsSevice to build the UserDetails object. In order to modify the data stored Security Context of Acegi, you either have to
  • setContext() method of the SecurityContextHolder. The SecurityContext object can be set to hold an Authentication object. This will mean that you have to add some additional security code to your application code.
  • Implement the UserDetailsService and UserDetails interfaces. This way you can keep security code seperated from the application code.
This post will describe how to implement UserDetailsService and UserDetails Objects. Here's what to do to implement a UserDetailsService.
  1. Start with the implementing acegi security posts part 1 and part 2.
  2. Create the UserDetailsService:
    package authentication;

    import java.util.HashMap;
    import java.util.Map;

    import org.acegisecurity.GrantedAuthority;
    import org.acegisecurity.userdetails.UserDetails;
    import org.acegisecurity.userdetails.UserDetailsService;
    import org.acegisecurity.userdetails.UsernameNotFoundException;
    import org.springframework.dao.DataAccessException;

    public class MyUserDetailsService implements UserDetailsService {

    private Map users = init();

    private Map init() {
    Map tUsers = new HashMap();

    tUsers.put("scott", new User("scott", "tiger", "ROLE_USER"));
    tUsers.put("harry", new User("harry", "potter", "ROLE_ADMIN"));
    tUsers.put("frodo", new User("frodo", "baggins", "ROLE_USER"));

    return tUsers;
    }

    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException, DataAccessException {
    User user = (User) users.get(s);
    GrantedAuthority authority = new MyGrantedAuthority(user.getRole());
    UserDetails userDetails = new MyUserDetails(new GrantedAuthority[] { authority }, user.getUserId(), user.getPassword(), "Additional Data");
    return userDetails;
    }

    }
    MyUserDetailsService.java

    Notice that all the user data is defined in this class. The UserDetailsService has to return a UserDetails object using the User name.
  3. Create the UserDetails class:
    package authentication;

    import org.acegisecurity.GrantedAuthority;
    import org.acegisecurity.userdetails.UserDetails;

    public class MyUserDetails implements UserDetails {


    private GrantedAuthority[] authorities = null;
    private String password = null;
    private String username = null;
    private String additionalData = null;


    public MyUserDetails(GrantedAuthority[] authorities, String password, String username, String additionalData) {
    super();
    this.authorities = authorities;
    this.password = password;
    this.username = username;
    this.additionalData = additionalData;
    }

    public GrantedAuthority[] getAuthorities() {
    return authorities;
    }

    public String getPassword() {
    return password;
    }

    public String getUsername() {
    return username;
    }

    public boolean isAccountNonExpired() {
    return true;
    }

    public boolean isAccountNonLocked() {
    return true;
    }

    public boolean isCredentialsNonExpired() {
    return true;
    }

    public boolean isEnabled() {
    return true;
    }
    }
    MyUserDetails.java
  4. The User class:
    package authentication;

    public class User {

    private String userId;

    private String password;

    private String role;

    public String getPassword() {
    return password;
    }

    public void setPassword(String password) {
    this.password = password;
    }

    public String getRole() {
    return role;
    }

    public void setRole(String role) {
    this.role = role;
    }

    public String getUserId() {
    return userId;
    }

    public void setUserId(String userId) {
    this.userId = userId;
    }

    public User(String userId, String password, String role) {
    super();
    this.userId = userId;
    this.password = password;
    this.role = role;
    }

    }
    User.java
  5. Modify the applicationContext.xml file: Modify the userDetailsService bean in the application context to as shown below
    <bean id="userDetailsService" class="authentication.MyUserDetailsService">
    </bean>
  6. The GrantedAuthority class: The MyGrantedAuthority class is typically used to store application roles.
    package authentication;

    import org.acegisecurity.GrantedAuthority;

    public class MyGrantedAuthority implements GrantedAuthority {

    private String authority = null;

    public MyGrantedAuthority(String authority) {
    this.authority = authority;
    }
    public String getAuthority() {
    return authority;
    }

    }
    MyGrantedAuthority.java

5 comments:

  1. hi, thanks a lot for this useful post.

    i have some question about the user context on spring. is it possible to propogate user information from the web layer down to the service layer without having to pollute the service layer interfaces with extraneous user parameters?

    i'm thinking about using threadlocal, but i couldn't find a way to get rid of parameters either. would you please give me some idea.

    ReplyDelete
  2. MyUserDetails's constructor contains mistake that it's username gets password and password gets username. So method prototype must be as MyUserDetails(GrantedAuthority[] authorities, String username,
    String password, String additionalData)

    ReplyDelete
  3. 'seven', the SecurityContextHolder uses a ThreadLocal, so you don't need to do anything extra - just grab the authentication object using SecurityContextHolder.getSecurityContext().getAuthentication() from your service layer.

    This is assuming the same thread is used to process a user's request throughout, from your web tier through to your services. If you're using different threads, e.g. using JMS, you'll need to do something a bit cleverer (e.g. attach the SecurityContext as a JMS message attribite).

    Note that Acegi can also apply role-based security transparently on your service methods using AOP (similar to Spring's declarative transaction support).

    Al.

    ReplyDelete
  4. hi...

    I am raju..,
    in case of not returning any result set, then how to handle that?

    ReplyDelete
  5. my SP will return an integer , if query executes successfully ,it returns 1 else -1.

    then how to handle resultsetmapping?

    ReplyDelete

Popular Posts