Closed criticalbh closed 7 years ago
Hi @criticalbh It's not recommended to change the state of the Authentication
after it's been authenticated by the AuthenticationProvider
. Ideally you would set the user authorities
for the Authentication
in your UserDetailsService
.
I'm not sure I understand your scenario completely. Can you provide more details or even a github sample?
Can you clarify the following for me...
loading roles from database according to selected employment
What do you mean selected employment and what scenario does this occur in?
Hi @jgrandja, thanks for Your reply.
My domain model looks like this: User implements UserDetails -id -name -list of [employments]
Employment -id -name -institutionId -set of [roles]
Now as you have said in UserDetailsService is simple to set user authorities under assumption that User has set of roles. But in my case User has list of employments, where each employment can have own set of roles.
Scenario is following: User enters username, password and gets prompted with list of employments. Once he click on one of employments I would like to give him authorisation only for selected employment. In that case I am enhancing token with selected employment, so that token is valid only for that selection (and with this I can handle business logic). Also during this enhancement I am trying to load authorities for selected employment and set it in principal, so on next request spring will know that this user has certain roles.
Code: UserDetailsService
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return userRepository
.findByName(username)
.map(user -> user)
.orElseThrow(() -> new UsernameNotFoundException(String.format("Users %s does not exist!", username)));
}
TokenEnhancer
@Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
User user = (User) authentication.getPrincipal();
final Map<String, Object> additionalInfo = new HashMap<>();
List<Long> employmentIds = employmentDao.findEmploymentIdsByUser(user.getUsername());
additionalInfo.put(EMPLOYMENTID, employmentIds.size() > 1 ? null : employmentIds.get(0));
for (String key : this.additionalInfo.keySet()) {
Object obj = this.additionalInfo.get(key);
if (key.equalsIgnoreCase(EMPLOYMENTID) && obj != null && !employmentIds.contains(obj)) {
throw new RuntimeException("Invalid employment");
}
additionalInfo.put(key, obj);
}
if (additionalInfo.get(EMPLOYMENTID) != null) {
Employment employment = user.getEmployment((long)additionalInfo.get(EMPLOYMENTID));
user.setAuthorities(employment.getRoles()); **this here is issue**
List<String> roles = employment.getRoles().stream().map(Role::getName).collect(Collectors.toList());
additionalInfo.put(ROLES, roles);
}
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);
return accessToken;
}
During debug I saw that authorithies is RandomAccessUnmodifiableList, so is there any way to hack this or even better, to make it work "legally".
I have idea to just store roles in token, but in that case I will have to create own custom authorisation mechanism, but I would like to use annotations provided by spring security.
Thanks in advance.
Dynamically changing the authorities
in the Authentication
for the current SecurityContext
(dependent on the selected/current employment) may have adverse affects even if you can make this work. I personally wouldn't recommend this approach. I'm wondering if there is an easier way for you to implement your overall solution?
Either way, at first thought I'm thinking you can implement your authorization scenario using a custom AccessDecisionManager
and/or AccessDecisionVoter
. However, this gets you deep into the Spring Security internals and can be complicated as well. But it is the main extension point for authorization decisions.
If your UserDetailsService
populates your User
with a Map<Employment, List<Roles>>
, as per your model, then your Authentication
will have all the authorities per employment loaded for that user for the session. Then you can use this information when making an access decision based on your custom AccessDecisionManager
and/or AccessDecisionVoter
.
NOTE: Both AccessDecisionManager
and/or AccessDecisionVoter
expose the current request and response.
Thank you very much, that way it is possible to create "real time" authorisations. Two detailed helpful links: https://spring.io/blog/2009/01/03/spring-security-customization-part-2-adjusting-secured-session-in-real-time
and
http://docs.spring.io/spring-security/site/docs/current/reference/html/authz-arch.html
You're welcome. The 1st link is definitely a good one and should help you set this up. I'd recommend you to read the Authorization Architecture (2nd link) first to gain a deeper understanding on how Spring Security authorization works under the covers. Good luck!
I have system that does not connect user directly to roles. User can have multiple employments and in each employment unit he has different roles.
Now it is easy in UserDetailsService to specify user authorities if they were connected directly to user.
What I have tried is to manipulate reference of authentication.getPrincipal() in TokenEnhancer by loading roles from database according to selected employment and using setter of my principal object to set them. But this does not work.
Any other ideas that would work?