Skip to content
This repository has been archived by the owner on Apr 5, 2022. It is now read-only.

Ribbon support to contact the auth server #61

Open
masrawi opened this issue Apr 28, 2015 · 24 comments
Open

Ribbon support to contact the auth server #61

masrawi opened this issue Apr 28, 2015 · 24 comments

Comments

@masrawi
Copy link

masrawi commented Apr 28, 2015

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
@dsyer
Copy link
Contributor

dsyer commented Apr 29, 2015

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.

@huningd
Copy link

huningd commented Jan 25, 2016

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.

@dsyer
Copy link
Contributor

dsyer commented Jan 25, 2016

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

@huningd
Copy link

huningd commented Jan 26, 2016

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
Copy link

huningd commented Jan 28, 2016

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

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

@kakawait
Copy link

kakawait commented Mar 17, 2017

@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

@skyding1212
Copy link

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

@kakawait
Copy link

kakawait commented May 9, 2017

@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

@alexandr-efimov
Copy link

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

@kakawait
Copy link

kakawait commented Sep 21, 2017

@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

@HJK181
Copy link

HJK181 commented Dec 21, 2017

Any progress on that?

1 similar comment
@Pryanic
Copy link

Pryanic commented Feb 28, 2018

Any progress on that?

@spencergibb
Copy link
Contributor

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

@nickorfas
Copy link

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

@stereo720712
Copy link

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

@stereo720712
Copy link

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 .

@ZoomAll
Copy link

ZoomAll commented Sep 11, 2018

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 :)

@andye2004
Copy link

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

@martijnhiemstra
Copy link

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?

@tuhao1020
Copy link

Anybody knows when would the PR #1523 be merged?

@ryanjbaxter
Copy link
Contributor

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

@tuhao1020
Copy link

@ryanjbaxter
Copy link
Contributor

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

@davicarrano
Copy link

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.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Development

No branches or pull requests