spring-cloud-security icon indicating copy to clipboard operation
spring-cloud-security copied to clipboard

Ribbon support to contact the auth server

Open masrawi opened this issue 9 years ago • 24 comments

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

masrawi avatar Apr 28 '15 15:04 masrawi

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.

dsyer avatar Apr 29 '15 06:04 dsyer

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.

huningd avatar Jan 25 '16 16:01 huningd

That won't work in quite that form because UserInfoTokenServices is part of Spring Boot now (which knows nothing about discovery).

dsyer avatar Jan 25 '16 17:01 dsyer

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?

huningd avatar Jan 26 '16 13:01 huningd

We solved it now with the UserInfoRestTemplateCustomizer. It workes great for us:

    @Override
    public void customize(OAuth2RestTemplate template) {
        template.setRequestFactory(ribbonClientHttpRequestFactory);
    }

huningd avatar Jan 28 '16 08:01 huningd

@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 avatar Mar 17 '17 14:03 kakawait

@kakawait could you provide a support for Dalston version? I try it but can not find uaa-service...

skyding1212 avatar Apr 28 '17 07:04 skyding1212

@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

kakawait avatar May 09 '17 14:05 kakawait

@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>

alexandr-efimov avatar Jun 16 '17 23:06 alexandr-efimov

@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

kakawait avatar Sep 21 '17 20:09 kakawait

Any progress on that?

HJK181 avatar Dec 21 '17 09:12 HJK181

Any progress on that?

Pryanic avatar Feb 28 '18 18:02 Pryanic

No there hasn't. If there is, we will update the issue.

spencergibb avatar Feb 28 '18 18:02 spencergibb

@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?

nickorfas avatar Mar 30 '18 18:03 nickorfas

it will errror when jwt token is expired and need to refresh . Why ?

stereo720712 avatar Jun 22 '18 02:06 stereo720712

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 .

stereo720712 avatar Jun 23 '18 05:06 stereo720712

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:

  1. RemoteTokenServices instance automatically created and configured, next
  2. Create RestTemplate bean and assign it to remoteTokenServices.setRestTemplate(restTemplate);, next
  3. 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 :)

ZoomAll avatar Sep 11 '18 17:09 ZoomAll

@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.

andye2004 avatar Nov 10 '18 11:11 andye2004

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?

martijnhiemstra avatar Nov 27 '18 14:11 martijnhiemstra

Anybody knows when would the PR #1523 be merged?

tuhao1020 avatar Mar 08 '19 14:03 tuhao1020

That is not a valid PR# can you provide a link?

ryanjbaxter avatar Mar 13 '19 00:03 ryanjbaxter

@ryanjbaxter https://github.com/spring-projects/spring-security-oauth/pull/1523

tuhao1020 avatar Mar 13 '19 01:03 tuhao1020

Why don’t you comment on that PR and ask?

ryanjbaxter avatar Mar 18 '19 23:03 ryanjbaxter

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:

  1. RemoteTokenServices instance automatically created and configured, next
  2. Create RestTemplate bean and assign it to remoteTokenServices.setRestTemplate(restTemplate);, next
  3. 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 :)

It works to me. Thaks.

davicarrano avatar Jun 01 '20 20:06 davicarrano