Skip to content

OAuth2 client integration with WebSocketClient #6711

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

Closed
philsttr opened this issue Mar 27, 2019 · 8 comments
Closed

OAuth2 client integration with WebSocketClient #6711

philsttr opened this issue Mar 27, 2019 · 8 comments
Assignees
Labels
in: oauth2 An issue in OAuth2 modules (oauth2-core, oauth2-client, oauth2-resource-server, oauth2-jose) status: declined A suggestion or change that we don't feel we should currently apply

Comments

@philsttr
Copy link
Contributor

philsttr commented Mar 27, 2019

In spring-security-oauth2-client 5.1, OAuth2 client is supported fairly well with webflux's WebClient via ServerOAuth2AuthorizedClientExchangeFilterFunction.

However, there is no equivalent support for webflux's WebSocketClient. For example, I would like to:

  • obtain an access token from ClientRegistration / OAuth2AuthorizedClient
  • automatically refresh the token before sending it if required, similar to how tokens are automatically refreshed in ServerOAuth2AuthorizedClientExchangeFilterFunction
  • include the access token in the Authorization header of the initial websocket outbound upgrade request

In my application, I'm currently debating on whether I want to copy/paste ServerOAuth2AuthorizedClientExchangeFilterFunction and it's corresponding OAuth2AuthorizedClientResolver (which is package-private) in order to provide similar support for my websocket use cases.

It's really a shame that WebSocketClient does not use ExchangeFilterFunctions, otherwise we'd get this for free. Instead, it looks like I'll have to use reactor netty's HttpClient.headersWhen method as a hook to provide headers instead.

At a minimum, it would be nice if most of the logic for obtaining an access token in ServerOAuth2AuthorizedClientExchangeFilterFunction was extracted out into a class that could be reused in

  • an ExchangeFilterFunction (for WebClient),
  • a "headersWhen function" (for WebSocketClient).
  • any other location where an access token is needed (e.g. a different http client or 3rd party sdk)

Mono<OAuth2AuthorizedClient> OAuth2AuthorizedClientResolver.loadAuthorizedClient is almost what I need. Except it doesn't handle refreshing tokens, and it's not public.

@philsttr
Copy link
Contributor Author

I'm not proud of this, but what I ended up doing as a workaround (rather than copy/paste ServerOAuth2AuthorizedClientExchangeFilterFunction) to capture the Authorization header value so that I can reuse it in other places (e.g. WebSocketClient) is:

  1. Create an ExchangeFunction that has two filters that execute in the following order:
    • ServerOAuth2AuthorizedClientExchangeFilterFunction
    • a custom ExchangeFilterFunction that:
      • if the request is a bogus request (from step 2) capture the request's Authorization header and returns a ClientResponse with an Authorization header (without invoking the downstream ExchangeFunction)
      • else invoke the downstream ExchangeFunction (to handle requests created by the ServerOAuth2AuthorizedClientExchangeFilterFunction, such as a request to refresh the token)
  2. Send a bogus request through the ExchangeFunction created in step 1
    • grab the Authorization header from the ClientResponse

Using this stream, I can reuse ExchangeFilterFunctions provided by spring security to generically obtain the Authorization header value for use in places other than a WebClient.

Even though I have a workaround, I'd still rather spring-security-oauth2-client provide a generic mechanism that can be used to retrieve tokens that can be used in any outbound client.

@jgrandja
Copy link
Contributor

jgrandja commented Apr 1, 2019

@philsttr Thanks for the report. This is the first request for oauth2 client support using WebSocketClient. I haven't seen any demand for this yet and I don't recall seeing any use cases for it either? We are quite stretched with our current plan for 5.2 and still need to build out further support in WebClient.

I will see what I can do to extract logic out so it can potentially be reused in WebSocketClient.

Let's leave this issue open and see if other users are looking for this type of integration.

@etienne-sf
Copy link

etienne-sf commented Jan 20, 2021

Hello,

@philsttr ,

I start with a big "Thank you" to you!
I had exactly this need. And your workaround works :) . I searched for hours, and days, until finding this thread.
For those interested, you'll find an implementation in this class: https://github.com/graphql-java-generator/graphql-maven-plugin-project/blob/generate_relay_schema/graphql-java-runtime/src/main/java/com/graphql_java_generator/client/OAuthTokenExtractor.java
It is used in this class: https://github.com/graphql-java-generator/graphql-maven-plugin-project/blob/generate_relay_schema/graphql-java-runtime/src/main/java/com/graphql_java_generator/client/QueryExecutorSpringReactiveImpl.java
(in the second execute method)

@jgrandja,

My use case is this one: I'm building a java code generator, to generate GraphQL client or server: https://github.com/graphql-java-generator/graphql-maven-plugin-project
A GraphQL server allows operations that can be: query, mutation or subscription.
A subscription is actually a Websocket.
So, to access a GraphQL server that has subscription operations, and that is protected by OAuth2 (which seems quite the state of the art), we need a way to open a WebSocket with an OAuth authorization.
Now, I have a workaround.

But perhaps this message could help to make this request be implemented ?

Etienne

@jgrandja
Copy link
Contributor

@etienne-sf I'm still not seeing much demand for OAuth2 WebSocketClient support. There are quite a few higher priority items that we are working on so this will remain in the Icebox. Upvotes help with prioritizing features.

@etienne-sf
Copy link

Yes, I understand.
Actually, I expected this answer.

BTW, thanks to phillsttr, I have a working workaround.
And hopefully, it can be useful for others too.

Étienne

@jgrandja
Copy link
Contributor

@philsttr I'm going to close this as there isn't much demand for OAuth2 WebSocketClient support.

If there are areas in the code that we could re-factor to allow for greater reuse and make it easier for you (and others) to work with WebSocketClient then we would be open to these suggestions.

@jgrandja jgrandja self-assigned this May 18, 2021
@jgrandja jgrandja added the status: declined A suggestion or change that we don't feel we should currently apply label May 18, 2021
@VitaliyKulikov
Copy link

Hey, I demand it )). Reopen plz!

@maikwodarz
Copy link

maikwodarz commented Jan 17, 2025

Precondition is my client id registration with registration id: johndoeservice

my properties:

spring:
  security:
    oauth2:
      client:
        registration:
          johndoeservice:
            client-id: f72351d3-bab3-42cd-a261-c6clientId6f
            client-secret: UAy8Q~vignl7SKQQbigsecretJHzZHZN-kaEP
            authorization-grant-type: client_credentials
            scope: api://f72351d3-bab3-42cd-a261-c6clientId6f/.default
        provider:
          johndoeservice:
            authorization-uri: https://login.myauthprovider.com/334553-2332323-23232/oauth2/v2.0/authorize
            token-uri: https://login.myauthprovider.com/334553-2332323-23232/oauth2/v2.0/token

Getting JWT with Spring without filter:

OAuth2AuthorizeRequest req = OAuth2AuthorizeRequest.withClientRegistrationId(clientRegistrationId).principal(principal).build();
OAuth2AuthorizedClient authorizedClient oAuth2AuthorizedClientManager.authorize(req);
OAuth2AccessToken newAccessToken = authorizedClient.getAccessToken();
String jwt = newAccessToken.getTokenValue();

I have the whole thing cached and also picked the expiration date from jwt. If a service wants the token from the cache shortly before it expires, the chache takes a new one.

During websocket connect:

WebSocketHttpHeaders httpHeaders = new WebSocketHttpHeaders();
httpHeaders.add(HttpHeaders.AUTHORIZATION, "Bearer " + jwt);
        
CompletableFuture<StompSession> completableFuture = webSocketStompClient.connectAsync(settings.getEndpoint(), httpHeaders, new StompHeaders(), sessionHandler);

As result, you have the Auth Header in WebSocket connect. You can observe it with Wireshark. When SpringBoot receives this connect with invalid Header it denies. Otherwise it lets work.

I tested it with SpringBoot 3.2.7 until 3.4.1

Drawback is that you have to care about expiration. Without caching you would call auth endpoint on every request.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: oauth2 An issue in OAuth2 modules (oauth2-core, oauth2-client, oauth2-resource-server, oauth2-jose) status: declined A suggestion or change that we don't feel we should currently apply
Projects
None yet
Development

No branches or pull requests

5 participants