Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#oauth2.hasScope and Resource Server #5096

Closed
silent-box opened this issue Feb 5, 2016 · 17 comments

Comments

@silent-box
Copy link

@silent-box silent-box commented Feb 5, 2016

Hello,

I have multiple Resource servers that share an Authorization server, everything works pretty good.

Now I'm trying to protect controllers in Resource servers with @PreAuthorize("#oauth2.hasScope('scope')") annotation, but it just rejects every call, even with the right scope.

Here is an example Resource server code:

@SpringBootApplication
@EnableResourceServer
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MyResourceApplication {

    public static void main(String[] args) {
        SpringApplication.run(MyResourceApplication.class, args);
    }
}
@RestController
public class MyController {

    @PreAuthorize("#oauth2.hasScope('ui')")
    @RequestMapping(value = "/", method = RequestMethod.GET)
    public String hello(Principal principal) {
        return "hello, " + principal.getName();
    }
}

Auth-server have the user-info-uri controller, which returns Principal object.

I'm making request from Browser('ui' scope) to Account-service.
Account-service invokes Auth-server and receives the following response:

screen shot 2016-02-04 at 00 38 54

I can see this in my logs:

Previously Authenticated: org.springframework.security.oauth2.provider.OAuth2Authentication@48cce0de: Principal: account-service; Credentials: [PROTECTED]; Authenticated: true; Details: remoteAddress=127.0.0.1, tokenType=BearertokenValue=<TOKEN>; Not granted any authorities
Voter: org.springframework.security.access.prepost.PreInvocationAuthorizationAdviceVoter@4cd3541a, returned: -1
Voter: org.springframework.security.access.vote.RoleVoter@7e09868e, returned: 0
Voter: org.springframework.security.access.vote.AuthenticatedVoter@23c38124, returned: 0

So, UsernamePasswordAuthenticationToken contains target scope somewhere in details, but OAuth2Authentication object has no scopes and OAuth2SecurityExpressionMethods.hasScope doesn't work.

My question is, why 'ui' scope does not appear in OAuth2Authentication object after Resource-Auth servers talking? What am i doing wrong?

Thank you.

@pablobenitogonzalez

This comment has been minimized.

Copy link

@pablobenitogonzalez pablobenitogonzalez commented Feb 21, 2016

Same issue here.
And role @PreAuthorize and @RolesAllowed not working as expected.
We get Principal at Resouce Server with his role associated but @PreAuthorize and @RolesAllowed didn't match. Is there any way to make it work?

@silent-box

This comment has been minimized.

Copy link
Author

@silent-box silent-box commented Mar 11, 2016

Any updates on this, please?

@silent-box

This comment has been minimized.

Copy link
Author

@silent-box silent-box commented Mar 14, 2016

I've shared minimum sample project to easily reproduce the issue:
https://github.com/silent-box/spring-oauth2-test

May be there is at least temporary workaround?
@dsyer? anybody? please, have a look

@dsyer

This comment has been minimized.

Copy link
Member

@dsyer dsyer commented Mar 14, 2016

You are relying on the UserInfoTokenServices from Spring Boot for the token information (including scopes), but it is only designed to return user details (not OAuth2 stuff like scope) by default. If you want to change the behaviour you can provide your own implementation, or fix the UserController to extract more information from the details (which as you have shown are retained and available if you need them).

@dsyer dsyer added the question label Mar 14, 2016
@silent-box

This comment has been minimized.

Copy link
Author

@silent-box silent-box commented Mar 15, 2016

Thank you for response!

I've added my ResourceServerTokenServices implementation and now everything works good.

Actually, it is 100% copy-paste of UserInfoTokenServices with 5 new lines (because most of the methods are private). Is it a reasonable enhancement, to change private to protected and maybe provide build-in implementation with full OAuth2Request details for this kind of cases?

@miguelfgar

This comment has been minimized.

Copy link

@miguelfgar miguelfgar commented Mar 15, 2016

I'm experiencing the same issue... @silent-box would you share your solution?

@silent-box

This comment has been minimized.

Copy link
Author

@silent-box silent-box commented Mar 15, 2016

Yes, here it is.

I'm still a little bit confused. It is a temporary solution, isn't it? Eventually there will be a default implementation, which can handle machine-to-machine communication completely, right?

@miguelfgar

This comment has been minimized.

Copy link

@miguelfgar miguelfgar commented Mar 30, 2016

Hi @silent-box,

Thanks so much. I implemented my custom RemoteTokenServices adapted to the particularities of the token endpoint of the Authorization Server I use.
However, I'm not able to override the RemoteTokenServices bean that is included by spring boot autoconfiguration from class: org.springframework.boot.autoconfigure.security.oauth2.resource.ResourceServerTokenServicesConfiguration.
The aforementioned class creates the bean without any @ConditonalOnMissingBean annotation.
I thought I could override the bean by creating the bean with the same name:

@Bean
public ResourceServerTokenServices remoteTokenServices() {
    CustomRemoteTokenServices services = new CustomRemoteTokenServices();           
    //services.setCheckTokenEndpointUrl(this.resource.getTokenInfoUri());       services.setCheckTokenEndpointUrl("http://openam.example.com:8080/openam/oauth2/tokeninfo");
    services.setClientId(this.resource.getClientId());
    services.setClientSecret(this.resource.getClientSecret());
    return services;
}

However, my bean is completely ignored. How did you manage to override the bean from the auto-configuration? I thought also to disable the autoconfiguration so my bean is used but the auto-configuration does many other things... not just creating this bean...

Any help will be appreciated. Thank you!

@miguelfgar

This comment has been minimized.

Copy link

@miguelfgar miguelfgar commented Mar 31, 2016

Hi @dsyer,

I tried to override org.springframework.boot.autoconfigure.security.oauth2.resource.ResourceServerTokenServicesConfiguration.RemoteTokenServicesConfiguration to provide a custom implementation of remoteTokenServices by overriding the bean set in the autoconfiguration, like this (same class and bean name):

    @Bean
    public ResourceServerTokenServices remoteTokenServices() {
        CustomRemoteTokenServices services = new CustomRemoteTokenServices();
      services.setCheckTokenEndpointUrl("http://openam.example.com:8080/openam/oauth2/tokeninfo");
        return services;
    }

However, the Spring Bean must always be found after my custom one and thus overriding it (I need this to behave the other way around). I tried also using @order but I was not sucessful... I always get:

2016-03-31 10:18:24.534  INFO 15424 --- [           main] o.s.b.f.s.DefaultListableBeanFactory     : Overriding bean definition for bean 'remoteTokenServices' with a different definition: replacing [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=application; factoryMethodName=remoteTokenServices; initMethodName=null; destroyMethodName=(inferred); defined in org.appverse.server.showcases.oauth.carservice.Application] with [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=org.springframework.boot.autoconfigure.security.oauth2.resource.ResourceServerTokenServicesConfiguration$RemoteTokenServicesConfiguration$TokenInfoServicesConfiguration; factoryMethodName=remoteTokenServices; initMethodName=null; destroyMethodName=(inferred); defined in class path resource [org/springframework/boot/autoconfigure/security/oauth2/resource/ResourceServerTokenServicesConfiguration$RemoteTokenServicesConfiguration$TokenInfoServicesConfiguration.class]]

The "remoteTokenServices" bean is not defined in the autoconfiguration as @ConditionalOnMissingBean. Would it make sense to add the @ConditionalOnMissingBean annotation in the auto-configuration so it's easier to override?

Any clue how can I be sucessful overriding the 'remoteTokenService' bean?

Thank you!

@dsyer

This comment has been minimized.

Copy link
Member

@dsyer dsyer commented Mar 31, 2016

To override that bean you would need to make one of the conditions used to control it's inclusion fail. Easiest would be to simply not define any security.oauth2.* properties.

@miguelfgar

This comment has been minimized.

Copy link

@miguelfgar miguelfgar commented Mar 31, 2016

Thanks @dsyer for your answer.

I know what you mean... I understand that making the @conditional(TokenInfoCondition.class) be evaluated as 'false' will not add the 'remoteTokenService' bean.
However, the TokenInfoCondition is evalutated taking into account the standard spring-boot properties that set the user token info and token info endpoints which I still need in my own implementation (see config):

security:
  oauth2:
    resource: 
      tokenInfoUri: http://${authserver.hostname}:${authserver.port}/${authserver.contextPath}/oauth2/tokeninfo
      userInfoUri: http://${authserver.hostname}:${authserver.port}/${authserver.contextPath}/oauth2/userinfo
      preferTokenInfo: true

At the end of the day, the reason why I need a custom 'remoteTokenService' is 'just' because the Authorization Server I use (Forgerock OpenAM) expects the token info / token validation endpoint to be invoked with HTTP GET method but the spring-boot implementation of RemoteTokenService invokes with POST (see method 'postForMap') thus, I get a 405 - Method not allowed from the Authorization Server when the ResourceServer tries to check the token.
At the end of the day I just wanted to implement a custom RemoteTokenService allowing to specify the HTTP method to use as a property (so it could be used with POST or GET).

Being the standard spring-boot properties completely useful for my custom implementation is a shame not to be able to use them and having in the end to add them with other names and deal with them...

I hope I managed to explain clearly my scenario.. Do you have any other idea? Might adding a @ConditonalOnMissingBean in TokenInfoServicesConfiguration be a solution?

Thanks again

@dsyer

This comment has been minimized.

Copy link
Member

@dsyer dsyer commented Mar 31, 2016

I'm not sure why you would need a RemoteTokenServices as well as UserInfoTokenServices but Spring Boot will not create both anyway, so you should be able to create your own bean for the one that Boot does not.

@miguelfgar

This comment has been minimized.

Copy link

@miguelfgar miguelfgar commented Mar 31, 2016

Thanks @dsyer, that was helpful.

I had my application already working with the UserInfoTokenServices but then I could not add expressions to check the OAuth2 scopes as this endpoint doesn't provide scopes information (and in my case I can't modify the endpoint to add information because I use a third party Authorization Server - OpenAM). Basically the problem originally reported in this issue.
Then I decided to use the 'tokenInfo' endpoint and I guess that's the reason why RemoteTokenServices is required - so the ResourceServer can invoke the token info endpoint of the Auth Server.
However I found another problem: spring-boot invokes the token info endpoint with POST but my third party requires GET method :-( As you know the spec is open regarding the token validation endpoint "format". That's why i wanted to override my RemoteTokenServices with a custom one.

So in this case I can use the spring provided UserInfoTokenServices (which is necessary to obtain the user information) and then provide my own RemoteTokenServices making the TokenInfoCondition fail as you suggested (basically adding my properties for the custom RemoteTokenServices with different names than the spring ones). Am I right? Do you think this will work?

Thanks as always for your help :-)

@dsyer

This comment has been minimized.

Copy link
Member

@dsyer dsyer commented Mar 31, 2016

Do you think this will work?

Yes, I do

@miguelfgar

This comment has been minimized.

Copy link

@miguelfgar miguelfgar commented Mar 31, 2016

@dsyer I just write to explain that I implemented this and it worked fine :-) I had to do some changes in my custom RemoteTokenServices to adapt to the particularities of the Authorization Server Token Info endpoint and all is working fine. Thanks so much!!! :-)

@ghost

This comment has been minimized.

Copy link

@ghost ghost commented Jan 23, 2018

@miguelfgar
How did you implemented custom RemoteTokenServices?

@gpt559

This comment has been minimized.

Copy link

@gpt559 gpt559 commented Apr 18, 2018

@miguelfgar I'm also interested in your OpenAM solution. Could you post the code + properties somewhere?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
7 participants
You can’t perform that action at this time.