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:
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.
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?
Any updates on this, please?
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
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).
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?
I'm experiencing the same issue... @silent-box would you share your solution?
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?
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!
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!
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.
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
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.
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 :-)
Do you think this will work?
Yes, I do
@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!!! :-)
@miguelfgar
How did you implemented custom RemoteTokenServices?
@miguelfgar I'm also interested in your OpenAM solution. Could you post the code + properties somewhere?
Most helpful comment
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?