spring-cloud-security
spring-cloud-security copied to clipboard
Ribbon support to contact the auth server
it would be great if I could replace localhost:9999
with the name of the eureka service id
spring:
application:
name: ui-service
oauth2:
sso:
home:
secure: false
path: /,/**/*.html
client:
accessTokenUri: http://localhost:9999/uaa/oauth/token
userAuthorizationUri: http://localhost:9999/uaa/oauth/authorize
clientId: acme
clientSecret: acmesecret
I agree that might be cool, when the auth server is a registered service. Contributions gladly accepted. Note (for anyone attempting a pull request): the userAuthorizationUri
is passed back to the browser in a redirect, so we would need to inject a custom RedirectResolver
. The accessTokenUri
is used in a back channel inside the OAuth2RestTemplate
, so it will also be fiddly (but not impossible) to override the nested RestTemplate
used for that call.
We have the same situation for property security.oauth2.resource.userInfoUri. In this case wouldn't it be enough to inject the DiscoveryClient into org.springframework.boot.autoconfigure.security.oauth2.resource.UserInfoTokenServices? If a DiscoveryClient is available we use it to resolve the userinfo service uri. Something like this:
public UserInfoTokenServices(String userInfoEndpointUrl, String clientId, DiscoveryClient discoveryClient){
String uri = discoveryClient.getInstances(userInfoEndpointUrl).get(0).getUri().toString();
this.userInfoEndpointUrl = uri;
this.clientId = clientId;
}
If that is enough I would enhance the UserInfoTokenServices and configuration classes.
That won't work in quite that form because UserInfoTokenServices
is part of Spring Boot now (which knows nothing about discovery).
Didn't saw that UserInfoTokenServices is from spring-boot-autoconfigure. Never develop in a text editor, sorry. So you mean you would enhance spring-cloud-security. Does that mean you would provide alternative configurations, exclude original configurations from spring-boot-autoconfigure and provide some alternative implementations. Do you have some recommendation how we could start to solve this?
We solved it now with the UserInfoRestTemplateCustomizer. It workes great for us:
@Override
public void customize(OAuth2RestTemplate template) {
template.setRequestFactory(ribbonClientHttpRequestFactory);
}
@huningd good catch. However that is not enough to also support security.oauth2.client.accessTokenUri
You can do that thing (ATTENTION not heavily tested and only tested with authorization-code mode, copy at your own risk)
CAMDEN VERSION (you should use bean LoadBalancerInterceptor interceptor
if you don't add spring-retry
)
@Bean
UserInfoRestTemplateCustomizer oauth2RestTemplateCustomizer(RetryLoadBalancerInterceptor interceptor) {
List<ClientHttpRequestInterceptor> interceptors = new ArrayList<>();
interceptors.add(interceptor);
return template -> {
AccessTokenProviderChain accessTokenProviderChain = Stream
.of(new AuthorizationCodeAccessTokenProvider(), new ImplicitAccessTokenProvider(),
new ResourceOwnerPasswordAccessTokenProvider(), new ClientCredentialsAccessTokenProvider())
.peek(tp -> tp.setInterceptors(interceptors))
.collect(Collectors.collectingAndThen(Collectors.toList(), AccessTokenProviderChain::new));
template.setAccessTokenProvider(accessTokenProviderChain);
};
}
BRIXTON VERSION
@Bean
UserInfoRestTemplateCustomizer userInfoRestTemplateCustomizer(SpringClientFactory springClientFactory) {
return template -> {
AccessTokenProviderChain accessTokenProviderChain = Stream
.of(new AuthorizationCodeAccessTokenProvider(), new ImplicitAccessTokenProvider(),
new ResourceOwnerPasswordAccessTokenProvider(), new ClientCredentialsAccessTokenProvider())
.peek(tp -> tp.setRequestFactory(new RibbonClientHttpRequestFactory(springClientFactory)))
.collect(Collectors.collectingAndThen(Collectors.toList(), AccessTokenProviderChain::new));
template.setAccessTokenProvider(accessTokenProviderChain);
};
}
With that Bean
following configuration is working
security:
oauth2:
client:
accessTokenUri: http://uaa-service/oauth/token
where uaa-service
is service name (is not a resolvable hostname)
It can be a partial response for #94 we just need a trick for userAuthorizationUri
(currently possible as explained on POC) because userAuthorizationUri
is used by browser (during redirection) so we can't (and we must not) use service registry
@kakawait could you provide a support for Dalston version? I try it but can not find uaa-service...
@skyding1212 sorry but I've just upgraded my project to Dalston
and Spring boot 1.5.2
and I don't face any issue with that code when using Camden
version
@skyding1212 I had the same problem with Dalston. But sulution providen by @kakawait work fine(that was for CAMDEN version) with dependency solved all problems!
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
<version>1.2.0.RELEASE</version>
</dependency>
@skyding1212 As I said
you should use bean LoadBalancerInterceptor interceptor if you don't add spring-retry
So you need to autowired LoadBalancerInterceptor
instead
@Bean
UserInfoRestTemplateCustomizer userInfoRestTemplateCustomizer(LoadBalancerInterceptor loadBalancerInterceptor) {
return template -> {
List<ClientHttpRequestInterceptor> interceptors = new ArrayList<>();
interceptors.add(loadBalancerInterceptor);
AccessTokenProviderChain accessTokenProviderChain = Stream
.of(new AuthorizationCodeAccessTokenProvider(), new ImplicitAccessTokenProvider(),
new ResourceOwnerPasswordAccessTokenProvider(), new ClientCredentialsAccessTokenProvider())
.peek(tp -> tp.setInterceptors(interceptors))
.collect(Collectors.collectingAndThen(Collectors.toList(), AccessTokenProviderChain::new));
template.setAccessTokenProvider(accessTokenProviderChain);
};
}
Or add dependencies as @alexandr-efimov explained above
Sample was updated to Spring boot 1.5.7
and Spring Cloud Dalston SR3
https://github.com/kakawait/uaa-behind-zuul-sample
Any progress on that?
Any progress on that?
No there hasn't. If there is, we will update the issue.
@dsyer on your first response of this issue you said that "The accessTokenUri is used in a back channel inside the OAuth2RestTemplate, so it will also be fiddly (but not impossible) to override the nested RestTemplate used for that call." This is exactly what is happening even in Spring Cloud Edward.SR2... Is there any way that you can think on how eureka instanceId could be used for access-token-uri?
it will errror when jwt token is expired and need to refresh . Why ?
it should be ribbon issue, because it only refresh token fail with the load balance inteceptor and call it fail because of not change the service name to ip .
I have encountered a similar problem with authentication server name resolution. I will describe how I solved it.
application.properties
security.oauth2.client.client-id=trusted
security.oauth2.client.client-secret=trusted
security.oauth2.resource.token-info-uri=http://NEVIS/oauth/check_token
ribbon.http.client.enabled=true
Pay attention to ribbon.http.client.enabled=true
- without it will not be created RibbonClientHttpRequestFactory bean.
Configure RestTemplate to use the Ribbon functionality:
@Configuration
public class BaliRestClientConfig
{
/**
* Customize the RestTemplate to use Ribbon load balancer to resolve service endpoints
*/
@Bean
public RestTemplateCustomizer ribbonClientRestTemplateCustomizer(
final RibbonClientHttpRequestFactory ribbonClientHttpRequestFactory)
{
return restTemplate -> restTemplate.setRequestFactory(ribbonClientHttpRequestFactory);
}
}
this configurator will work for all RestTemplate instances: RestTemplate, OAuth2RestTemplate, etc.
Now we need to force RemoteTokenServices to use our RestTemplate instance. Because in my case the main problem was that RemoteTokenServices itself creates its own personal RestTemplate. Reassign the RestTemplate instance to RemoteTokenServices at the time It is defined:
@Autowired
private RemoteTokenServices remoteTokenServices;
@Bean
@LoadBalanced
public RestTemplate restTemplate()
{
var restTemplate = new RestTemplate();
remoteTokenServices.setRestTemplate(restTemplate);
return restTemplate;
}
And that's all.
The short sequence will be as follows:
- RemoteTokenServices instance automatically created and configured, next
- Create RestTemplate bean and assign it to
remoteTokenServices.setRestTemplate(restTemplate);
, next - Custom RestTemplate to use the Ribbon functionality
restTemplate.setRequestFactory(ribbonClientHttpRequestFactory);
When a POST request is sent to http://NEVIS/oauth/check_token
, RemoteTokenServices can now resolve the name of the NEVIS
authentication server because it is associated with the RestTemplate that is associated with the Ribbon :)
@dsyer I've just been looking at how to resolve this issue for myself and been thinking about your very first comment re the browser userAuthorizationUri
redirect. Whilst the feature itself is a cool idea I was thinking that from a Spring Security OAuth2 perspective we really shouldn't be pulling in any discovery client. Instead, it should probably be a default implementation doing pretty much what is done now but also providing an over-ride mechanism where people can provide their own RedirectResolver
to account for whatever discovery service they are using?
There have already been a few posts above that highlight how to take care of the other redirect issues so if you agree with this kind of approach I've suggested I can have a look at putting a PR together that would cover it.
I have tried all the solutions mentioned above and NONE!!! of them work. This is a major flaw in Spring since now a days with discovery services almost becoming standard it should be working. Does anybody know what the status of this is?
Anybody knows when would the PR #1523 be merged?
That is not a valid PR# can you provide a link?
@ryanjbaxter https://github.com/spring-projects/spring-security-oauth/pull/1523
Why don’t you comment on that PR and ask?
I have encountered a similar problem with authentication server name resolution. I will describe how I solved it.
application.properties
security.oauth2.client.client-id=trusted security.oauth2.client.client-secret=trusted security.oauth2.resource.token-info-uri=http://NEVIS/oauth/check_token ribbon.http.client.enabled=true
Pay attention to
ribbon.http.client.enabled=true
- without it will not be created RibbonClientHttpRequestFactory bean.Configure RestTemplate to use the Ribbon functionality:
@Configuration public class BaliRestClientConfig { /** * Customize the RestTemplate to use Ribbon load balancer to resolve service endpoints */ @Bean public RestTemplateCustomizer ribbonClientRestTemplateCustomizer( final RibbonClientHttpRequestFactory ribbonClientHttpRequestFactory) { return restTemplate -> restTemplate.setRequestFactory(ribbonClientHttpRequestFactory); } }
this configurator will work for all RestTemplate instances: RestTemplate, OAuth2RestTemplate, etc.
Now we need to force RemoteTokenServices to use our RestTemplate instance. Because in my case the main problem was that RemoteTokenServices itself creates its own personal RestTemplate. Reassign the RestTemplate instance to RemoteTokenServices at the time It is defined:
@Autowired private RemoteTokenServices remoteTokenServices; @Bean @LoadBalanced public RestTemplate restTemplate() { var restTemplate = new RestTemplate(); remoteTokenServices.setRestTemplate(restTemplate); return restTemplate; }
And that's all.
The short sequence will be as follows:
- RemoteTokenServices instance automatically created and configured, next
- Create RestTemplate bean and assign it to
remoteTokenServices.setRestTemplate(restTemplate);
, next- Custom RestTemplate to use the Ribbon functionality
restTemplate.setRequestFactory(ribbonClientHttpRequestFactory);
When a POST request is sent to
http://NEVIS/oauth/check_token
, RemoteTokenServices can now resolve the name of theNEVIS
authentication server because it is associated with the RestTemplate that is associated with the Ribbon :)
It works to me. Thaks.