Monday, May 25, 2009

Gregorio


Gregorio
Inserito originariamente da Lucio Benfante
Un benvenuto a Gregorio, fratellino di Carlo e Silvia, che gli fanno tanti auguri e non vedono l'ora che arrivi a casa.

Sì, al momento è tranquillo come appare, speriamo continui così

Tuesday, May 5, 2009

Mutable authorities with Spring Security and CAS

Recently I worked on an application with the following requisite:

The logged user must select its current role among the roles for which he's authorized.


A simple requisite, and (apparently) it's easy to implement it with Spring Security: write an UserDetails class in which you can select the returned authority(ies). For example:


public class LoggedUserWithSelectableRole extends User {
private GrantedAuthority currentAuthority;

public LoggedUserWithSelectableRole(String username, String password,
boolean enabled, GrantedAuthority[] authorities) throws IllegalArgumentException {
super(username, password, enabled, authorities);
}

public void setCurrentAuthority(GrantedAuthority currentAuthority) {
this.currentAuthority = currentAuthority;
}

@Override
public GrantedAuthority[] getAuthorities() {
if (Arrays.asList(super.getAuthorities()).contains(currentAuthority)) {
return new GrantedAuthority[] {currentAuthority};
} else {
return new GrantedAuthority[0];
}
}

public GrantedAuthority[] getAllAuthorities() {
return super.getAuthorities();
}
}


Now you can select an authority for the logged user (for example, in a controller):


@RequestMapping
public String selectRole(@RequestParam(value = "role") int role) {
LoggedUserWithSelectableRole user =
(LoggedUserWithSelectableRole) SecurityContextHolder.getContext().
getAuthentication().getPrincipal();
user.setCurrentAuthority(user.getAllAuthorities()[role]);
return "redirect:/";
}


Unfortunately this is not sufficient, as the authorities used by Spring Security for checking the user authorization are not (usually) stored in the principal object, but it the Authentication object.

It would be nice to write something like:


/* WARNING: The method setAuthorities doesn't exist */
SecurityContextHolder.getContext().getAuthentication().
setAuthorities(user.getAuthorities());


But the Authentication token is mostly immutable, so the setAuthorities doesn't exist. Worst, in the AbtractAuthenticationToken class, the base class of most of the token implementations, the authorities attribute is private, so you can't easily implement by yourself an alternative token implementation extending the original token class.

In our application we are using CAS. The only solution I found (as far as I know...please send me a line if you see a better solution) was to extend the CasAuthenticationToken, provinding a costructor for coping an existing token (of course of the same type):


public class UpdatableCasAuthenticationToken extends CasAuthenticationToken {

private final int keyHash;

public UpdatableCasAuthenticationToken(CasAuthenticationToken token, GrantedAuthority[] authorities) {
super("BOH", token.getPrincipal(), token.getCredentials(), authorities, token.getUserDetails(), token.getAssertion());
this.keyHash = token.getKeyHash();
}

@Override
public int getKeyHash() {
return this.keyHash;
}

}


As you can see, we also need to hide attributes and override methods not modifiable through the constructor of the base class.

Now I can substitute the original token with the modified one:


@RequestMapping
public String selectRole(@RequestParam(value = "role") int role) {
LoggedUserWithSelectableRole user =
(LoggedUserWithSelectableRole) SecurityContextHolder.getContext().
getAuthentication().getPrincipal();
user.setCurrentAuthority(user.getAllAuthorities()[role]);
SecurityContextHolder.getContext().setAuthentication(
new UpdatableCasAuthenticationToken(
(CasAuthenticationToken) SecurityContextHolder.getContext().getAuthentication(),
user.getAuthorities()));
return "redirect:/";
}


As CAS ha no concerns with the user roles, I think CasAuthenticationToken should provide a way for updating authorities, and maybe the authorites attribute of AbstractAuthenticationToken should be declared as protected.