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

Add automatic token refresh to TokenRelayGatewayFilterFactory #175

Closed
thekalinga opened this issue Mar 7, 2019 · 36 comments · Fixed by spring-cloud/spring-cloud-gateway#1976

Comments

@thekalinga
Copy link

Bug report

As of now TokenRelayGatewayFilterFactory cascades access token even if it expired. If the access token is expired, this filter should attempt to refresh the token if refresh tokens are supported, else get fresh new token before sending the request downstream

ServerOAuth2AuthorizedClientExchangeFilterFunction already does this for WebClient based on the current OAuth2AuthenticationToken, TokenRelayGatewayFilterFactory should emulate it

@thekalinga thekalinga changed the title Add automatic token refresh Add automatic token refresh to TokenRelayGatewayFilterFactory Mar 7, 2019
@spencergibb spencergibb transferred this issue from spring-cloud/spring-cloud-gateway Mar 7, 2019
@spencergibb
Copy link
Contributor

/cc @jgrandja @jzheaux

@agerock
Copy link

agerock commented Jun 3, 2019

We use TokenRelayGatewayFilterFactory with OAuth2 and the access token expiry is 5 min. Any further requests are forwarded with expired token. Is there any timeline for implementation of this enhancement?

@wtatum
Copy link

wtatum commented Jun 4, 2019

It feels like the implementation of this would be really similar to ServerOAuth2AuthorizedClientExchangeFilterFunction but the interfaces of .web.reactive.function.client.ExchangeFilterFunction and .cloud.gateway.filter.GatewayFilter are pretty dramatically different even though their doing something trivially similar. I've tried to implement my own version of the TokenRelayGatewayFilterFactory but it feels like the only way to get it to work would be to re-implement the shouldRefresh and refreshAuthorizedClient code from ServerOAuth2AuthorizedClientExchangeFilterFunction again, but in terms of ServerWebExchange and GatewayFilterChain instead of ClientRequest.

@Alan210
Copy link

Alan210 commented Jun 11, 2019

Thanks for advice! I wrote a custom TokenRelayGatewayFilterFactory to implement the shouldRefresh and refreshAuthorizedClient code from ServerOAuth2AuthorizedClientExchangeFilterFunction, used webclient post instead of the origin ExchangeFunction, and it worked.

@floresek
Copy link

Hi Alan210

Could you be so kind and share your implementation, please?

Cheers
Flores

@gongchao8888
Copy link

We use TokenRelayGatewayFilterFactory with OAuth2 and the access token expiry is 1 min.
When token expires。Token do not refreshed spring cloud G and spring cloud Hoxton

@tschlegel
Copy link

tschlegel commented Sep 25, 2019

Here is my implementation of the custom TokenRelayGatewayFilterFactory with automatic token refresh based on the functionality from class ServerOAuth2AuthorizedClientExchangeFilterFunction

@floresek
Copy link

Thanks ;)

@gongchao8888
Copy link

Here is my implementation of the custom TokenRelayGatewayFilterFactory with automatic token refresh based on the functionality from class ServerOAuth2AuthorizedClientExchangeFilterFunction

this link do not open . can you let me see your demo? thanks

@spencergibb
Copy link
Contributor

The link works fine for me

@gongchao8888
Copy link

@RequestMapping("/")
@responsebody
public String index(@RegisteredOAuth2AuthorizedClient("website") OAuth2AuthorizedClient authorizedClient {
OAuth2RefreshToken refreshToken = authorizedClient.getRefreshToken();
// in my demo refreshToken.getExpiresAt() is null why?
OAuth2AccessToken accessToken = authorizedClient.getAccessToken()
return "index";

@gongchao8888
Copy link

The link works fine for me

thank you . I can open it with VPN

@boal
Copy link

boal commented Oct 8, 2019

An alternative TokenRelayGatewayFilterFactory with access token refreshment can be shown here.

CustomTokenRelayGatewayFilterFactory

This class is also inspired by ServerOAuth2AuthorizedClientExchangeFilterFunction as mentioned above.

I am also looking forward to the implementation of the access token refreshment in the class TokenRelayGatewayFilterFactory.

@timtebeek
Copy link

@tschlegel & @boal thanks both for sharing your implementations of token refresh; either of you willing to contribute this to spring-cloud-security? That way it's easier to pick up going forward.

@jgrandja
Copy link

@spencergibb You can easily inherit automatic token refresh using the new (in 5.2.0) ReactiveOAuth2AuthorizedClientManager and associated ReactiveOAuth2AuthorizedClientProvider(s). Take a look at this comment for further details.

Take a look at ServerOAuth2AuthorizedClientExchangeFilterFunction as TokenRelayGatewayFilterFactory would have similar implementation.

@tschlegel
Copy link

I have a new TokenRelayFilter implementation that requires Spring Boot 2.2.0. See TokenRelayWithTokenRefreshGatewayFilterFactory.

@timtebeek
Copy link

Thanks @tschlegel! That seems to work here; Also meant I could drop the dependency on spring-cloud-security, which kind of makes me wonder of this filter should be included here, or part of spring-cloud-gateway or spring-security itself.

@vijavin01
Copy link

Thanks @tschlegel & @boal for the samples. I'm getting below error when I tried to integrate the given custom token relay filter in my sample cloud gateway. Could you guys please help. My sample gateway follows the code https://github.com/benwilcock/spring-cloud-gateway-demo/tree/master/security-gateway/security-gateway


APPLICATION FAILED TO START


Description:

Parameter 0 of constructor in gateway.config.TokenRelayWithTokenRefreshGatewayFilterFactory required a bean of type 'org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository' that could not be found.

Action:

Consider defining a bean of type 'org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository' in your configuration.

@tschlegel
Copy link

You have to update to spring boot 2.2.x

@vijavin01
Copy link

vijavin01 commented Feb 4, 2020

Thank you very much @tschlegel for the hint. I have upgraded it to 2.2.4.

@vijavin01
Copy link

One interesting fact is in the controller REST method if I use @RegisteredOAuth2AuthorizedClient OAuth2AuthorizedClient it will automatically renews the token when it is called, but it is missing in the TokenRelayFilter.

@vijavin01
Copy link

The Websession replication using Redis also fails to replicate the Oauth2Token in the SecurityContext. When I tried to replicate the Websession using 2 instances of Cloud Gateway, the TokenRelayGatewayFilterFactory is not forwarding the token in the replicated instance since it is missing in the SecurityContext. Eventhough the replicated Websession has OAuth2AuthenticationToken in the SecurityContext, it don't have the access token and refresh token.

@Simbosan
Copy link

Thanks for the input @tschlegel but I seem to be missing a configuration to allow your relay to work

It tries to contact keycloak on a URL we don't use for refresh and gets 500

2020-02-21 09:32:19.058 ERROR 14024 --- [or-http-epoll-2] a.w.r.e.AbstractErrorWebExceptionHandler : [6666ee3e] 500 Server Error for HTTP GET "/login/oauth2/code/keycloak"

org.springframework.security.oauth2.core.OAuth2AuthorizationException: [authorization_request_not_found]

In another application we refresh by calling a url
/auth/realms//protocol/openid-connect/token posting just
client_id, grant_type = "refresh_token", refresh_token.

I assume (hope) this is just misconfiguration on my part

@rigon
Copy link

rigon commented Apr 8, 2020

Any plans when this issue will be fixed and released?

@aks8m
Copy link

aks8m commented Apr 10, 2020

I can confirm that the refresh functionality works with @tschlegel solution (Nov 21, 2019 comment). Thank you!!

@Simbosan
Copy link

I've not been able to get it to work as we dont use authorisation code flow and it always seems to try that even though i remove the auth code enabling.

@rigon
Copy link

rigon commented Apr 14, 2020

The @tschlegel solution (Nov 21, 2019 comment) works for me after removing the spring-cloud-starter-security dependency. I just added TokenRelayWithTokenRefreshGatewayFilterFactory to my project and it is all working now. Thanks @aks8m!

@yogacat
Copy link

yogacat commented May 25, 2020

Are there any plans to work on it? I'm stuck with 2.1.9.RELEASE version for spring boot and Greenwich.SR3 and cannot update my project because of this issue.

@boal
Copy link

boal commented Aug 6, 2020

Are there plans to implement the filter within the Spring lib? The solution of @tschlegel works fine with the current Spring boot versions. If there should be incompatibilities with @tschlegel solution in the future, then the proposed solution would always have to be adapted in the issues comments. Therefore it would make sense to implement the functionality in a Spring standard filter, because many projects will secure their application with a token.

@exxbrain
Copy link

You migrated TokenRelay from spring-cloud-security but would you say where is the refresh functionality?
Thanks.

@spencergibb
Copy link
Contributor

it is also in gateway

@psytester
Copy link

I'm somehow lost.
My Dev Team is using the updated solution from @tschlegel:

I have a new TokenRelayFilter implementation that requires Spring Boot 2.2.0. See TokenRelayWithTokenRefreshGatewayFilterFactory.

The client browser knows the session cookie only, and no token details.
The Gateway service has to process the Client Requests from different users and based on its session cookie it has to pass them to the backend server, extended by the oAuth Token -- done with .map(token -> withBearerAuth(exchange, token))

The backend service will bind some jobs based on the oAuth Token details (here the keycloak userId is used)

BUT under parallel "load" (two parallel browser sessions are already enough) the gateway is using only ONE oAuth token, altrough the users have different token with different userIds.
That's strange! Am I the first one, how has such problem?

Because of .map(token -> withBearerAuth(exchange, token)) -- i added in method withBearerAuth() some logging to see the used value for setting Authentication Header and yes they are mixed up.
It looks like, the token from last logged-in user will be used only.

Is the code "session safe" at this place?
Or do we have some principle http session / web session problems in some other code locations?

I don't have to code this, but as a black-box tester I like such errors to get more knowledge about internal workflows 😄

@igorroman777
Copy link

Hi @psytester ,
i have the same problem: users are swapped. :(
Does anyone have an idea?

@psytester
Copy link

psytester commented Apr 15, 2021

For sure! I belive that you have exaclty the same issue, because it's the same project ;-)
While analysing the logfiles, I catched the root cause ..... maybe
and hopefully the simple configuration solution.

In logfile I see the output for Principal Name [noname noname].
It's because our Keycloak registration page is using as hidden form data First- & Lastname = "noname"
I changed the registration data to have unique First- & Lastname values per user, only to better distinguish the log output.
and upps.... the problem disappered.

Inspired by that change of Principal Name, I searched and found this SO article https://stackoverflow.com/questions/37499307/whats-the-principal-in-spring-security
answer The principal is the currently logged in user.
In my case all logged-in users have the same principal.

After some more research based on „principal-attribute“ shown here: https://www.keycloak.org/docs/latest/securing_apps/#_java_adapter_config

principal-attribute
OpenID Connect ID Token attribute to populate the UserPrincipal name with. If token attribute is null, defaults to sub. Possible values are:
         sub, preferred_username, email, name, nickname, given_name, family_name.

If token attribute is null, defaults to sub -- that makes sence!

I think and hope the fix is a simple configuration change, because currently we are using in application.yaml
spring.security.oauth2.client.provider.keycloak.user-name-attribute=name

  security:
    oauth2:
      client:
        provider:
          keycloak:
            user-name-attribute: name

Changing it to
spring.security.oauth2.client.provider.keycloak.user-name-attribute=sub
gives back userId as Principal Name: [49a79b38-2549-4e9a-ab59-c831beea22b1]

And finally I'm not longer able to get the error with wrong token for the parallel logged-in users.

Can someone confirm that this change is the correct place and was the root cause?

Edit:
Since Github is no question tracker, I posted on stackoverflow my question as spring-cloud-security: Identification of individual users in http session handling by value of principal? Dangerous setting of user-name-attribute?

go2max pushed a commit to go2max/spring-microservice-api that referenced this issue May 15, 2021
* Migrates token relay from spring-cloud-security.

Adds refresh token support.

Fixes spring-attic/spring-cloud-security#175

Fixes gh-1975

See spring-attic/spring-cloud-security#231
@jndietz
Copy link

jndietz commented Sep 20, 2021

I'm really sorry for dredging up this old issue -- but with Spring Boot 2.4.9, and Cloud 2020.0.3, I don't see TokenRelayAutoConfiguration in my project. What am I missing? I have org.springframework.cloud:spring-cloud-starter-gateway declared as a dependency, and based on @spencergibb comment, it should be in there.

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

Successfully merging a pull request may close this issue.