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
.
No comments:
Post a Comment