spring-attic / spring-security-oauth

Support for adding OAuth1(a) and OAuth2 features (consumer and provider) for Spring web applications.
http://github.com/spring-projects/spring-security-oauth
Apache License 2.0
4.7k stars 4.04k forks source link

Authorize user after authentication #904

Closed criticalbh closed 7 years ago

criticalbh commented 7 years ago

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?

jgrandja commented 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?

criticalbh commented 7 years ago

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.

jgrandja commented 7 years ago

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.

criticalbh commented 7 years ago

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

jgrandja commented 7 years ago

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!