Spring Cloud Gateway在尝试使用过期的access_token和refresh_token刷新_token时出现500异常

发布于 2025-01-15 21:22:22 字数 1351 浏览 1 评论 0原文

我有一个使用 ServerHttpSecurity.oauth2Login() 的安全 Spring Cloud Gateway 应用程序,它可以使用刷新令牌成功续订过期的访问令牌。但是,当刷新令牌也过期并且应用程序尝试使用它更新访问令牌时,我收到 500 内部服务器错误 [似乎是由之前的 400 错误请求错误引起的]以下例外:

org.springframework.security.oauth2.client.ClientAuthorizationException: [invalid_grant] Token is not active
    at org.springframework.security.oauth2.client.RefreshTokenReactiveOAuth2AuthorizedClientProvider.lambda$authorize$0(RefreshTokenReactiveOAuth2AuthorizedClientProvider.java:97) ~[spring-security-oauth2-client-5.4.1.jar:5.4.1]

此处有完整日志: https://github.com/spring-projects/spring-security/files/8319348/logs.txt

仅当我重新发出请求时(通过调用安全端点刷新浏览器),我将被重定向到登录页面(所需的行为)。

在调试时,我注意到在 500 内部服务器错误后重新发出请求会导致以下异常:

org.springframework.security.oauth2.client.ClientAuthorizationRequiredException: [client_authorization_required] Authorization required for Client Registration Id: <client-id>.

这可能是是什么原因导致重定向到登录页面。

在此处请求执行详细信息

我的问题:我可以避免收到 500 内部服务器错误并进行重定向吗到登录页面?如果是,我怎样才能做到这一点?

环境详细信息 春季启动:2.4.0 春云:2020.0.0 春季安全:5.4.1

I have a secured Spring Cloud Gateway application using ServerHttpSecurity.oauth2Login() that can successfully renew expired access tokens using the refresh token. However, when the refresh token also expires and the application tries to renew the access token with it, I get a 500 Internal Server Error [seems to be caused by a 400 Bad Request error just before it] with the following exception:

org.springframework.security.oauth2.client.ClientAuthorizationException: [invalid_grant] Token is not active
    at org.springframework.security.oauth2.client.RefreshTokenReactiveOAuth2AuthorizedClientProvider.lambda$authorize$0(RefreshTokenReactiveOAuth2AuthorizedClientProvider.java:97) ~[spring-security-oauth2-client-5.4.1.jar:5.4.1]

Full logs here: https://github.com/spring-projects/spring-security/files/8319348/logs.txt

Only if I re-issue the request (refresh browser with the call to the secured endpoint), I will get redirected to the login page (desired behavior).

While debugging, I noticed that re-issuing the request after the 500 Internal Server Error under the hood results in the following exception:

org.springframework.security.oauth2.client.ClientAuthorizationRequiredException: [client_authorization_required] Authorization required for Client Registration Id: <client-id>.

and that is probably what causes the redirect to the login page.

Request execution details here

My question: Can I avoid getting the 500 Internal Server Error and instead be redirected to the login page? If yes, how can I accomplish that?

Environment details
Spring Boot: 2.4.0
Spring Cloud: 2020.0.0
Spring Security: 5.4.1

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

风追烟花雨 2025-01-22 21:22:22

解决方案是捕获刷新令牌时引起的 500,然后使用下一个类启动新的授权流程:

import org.springframework.security.oauth2.client.*;
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
import org.springframework.security.oauth2.client.web.DefaultReactiveOAuth2AuthorizedClientManager;
import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository;
import org.springframework.util.Assert;
import reactor.core.publisher.Mono;

/**
 * A delegating implementation of ReactiveOAuth2AuthorizedClientManager to help deal with a 500 Internal Server Error
 * that is a result of an expired access token. With ReactiveOAuth2AuthorizedClientManagerCustom, we manage to redirect
 * to the login page instead of returning a 500 Internal Server Error to the user/client.
 */
public class ReactiveOAuth2AuthorizedClientManagerCustom implements ReactiveOAuth2AuthorizedClientManager {

    private final ReactiveClientRegistrationRepository clientRegistrationRepository;
    private final ServerOAuth2AuthorizedClientRepository authorizedClientRepository;
    private final ReactiveOAuth2AuthorizedClientManager authorizedClientManager;

    public ReactiveOAuth2AuthorizedClientManagerCustom(ReactiveClientRegistrationRepository clientRegistrationRepository,
                                                       ServerOAuth2AuthorizedClientRepository authorizedClientRepository) {
        this.clientRegistrationRepository = clientRegistrationRepository;
        this.authorizedClientRepository = authorizedClientRepository;
        this.authorizedClientManager = new DefaultReactiveOAuth2AuthorizedClientManager(
                this.clientRegistrationRepository, this.authorizedClientRepository
        );
    }

    public Mono<OAuth2AuthorizedClient> authorize(OAuth2AuthorizeRequest authorizeRequest) {
        Assert.notNull(authorizeRequest.getClientRegistrationId(), "Client registration id cannot be null");

        return this.authorizedClientManager.authorize(authorizeRequest)
                // The token has expired, therefore we initiate a new grant flow
                .onErrorMap(
                        ClientAuthorizationException.class,
                        error -> new ClientAuthorizationRequiredException(authorizeRequest.getClientRegistrationId())
                );
    }
}

然后添加下一个 @Bean

 public ReactiveOAuth2AuthorizedClientManagerCustomConfig(ReactiveClientRegistrationRepository clientRegistrationRepository,
                                                             ServerOAuth2AuthorizedClientRepository authorizedClientRepository) {
        this.clientRegistrationRepository = clientRegistrationRepository;
        this.authorizedClientRepository = authorizedClientRepository;
    }

    @Bean
    @Primary
    ReactiveOAuth2AuthorizedClientManager authorizedClientManager() {
        return new ReactiveOAuth2AuthorizedClientManagerCustom(
                this.clientRegistrationRepository, this.authorizedClientRepository
        );
    }

The solution was to catch the 500 caused while refreshing a token and then initiating a new authorization flow, using the next classes:

import org.springframework.security.oauth2.client.*;
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
import org.springframework.security.oauth2.client.web.DefaultReactiveOAuth2AuthorizedClientManager;
import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository;
import org.springframework.util.Assert;
import reactor.core.publisher.Mono;

/**
 * A delegating implementation of ReactiveOAuth2AuthorizedClientManager to help deal with a 500 Internal Server Error
 * that is a result of an expired access token. With ReactiveOAuth2AuthorizedClientManagerCustom, we manage to redirect
 * to the login page instead of returning a 500 Internal Server Error to the user/client.
 */
public class ReactiveOAuth2AuthorizedClientManagerCustom implements ReactiveOAuth2AuthorizedClientManager {

    private final ReactiveClientRegistrationRepository clientRegistrationRepository;
    private final ServerOAuth2AuthorizedClientRepository authorizedClientRepository;
    private final ReactiveOAuth2AuthorizedClientManager authorizedClientManager;

    public ReactiveOAuth2AuthorizedClientManagerCustom(ReactiveClientRegistrationRepository clientRegistrationRepository,
                                                       ServerOAuth2AuthorizedClientRepository authorizedClientRepository) {
        this.clientRegistrationRepository = clientRegistrationRepository;
        this.authorizedClientRepository = authorizedClientRepository;
        this.authorizedClientManager = new DefaultReactiveOAuth2AuthorizedClientManager(
                this.clientRegistrationRepository, this.authorizedClientRepository
        );
    }

    public Mono<OAuth2AuthorizedClient> authorize(OAuth2AuthorizeRequest authorizeRequest) {
        Assert.notNull(authorizeRequest.getClientRegistrationId(), "Client registration id cannot be null");

        return this.authorizedClientManager.authorize(authorizeRequest)
                // The token has expired, therefore we initiate a new grant flow
                .onErrorMap(
                        ClientAuthorizationException.class,
                        error -> new ClientAuthorizationRequiredException(authorizeRequest.getClientRegistrationId())
                );
    }
}

And then adding the next @Bean

 public ReactiveOAuth2AuthorizedClientManagerCustomConfig(ReactiveClientRegistrationRepository clientRegistrationRepository,
                                                             ServerOAuth2AuthorizedClientRepository authorizedClientRepository) {
        this.clientRegistrationRepository = clientRegistrationRepository;
        this.authorizedClientRepository = authorizedClientRepository;
    }

    @Bean
    @Primary
    ReactiveOAuth2AuthorizedClientManager authorizedClientManager() {
        return new ReactiveOAuth2AuthorizedClientManagerCustom(
                this.clientRegistrationRepository, this.authorizedClientRepository
        );
    }
赤濁 2025-01-22 21:22:22

从 Spring security 6.4.x 开始,非响应式实现:

public class DelegatingOAuth2AuthorizedClientManager implements OAuth2AuthorizedClientManager {

    private final OAuth2AuthorizedClientManager delegate;

    public DelegatingOAuth2AuthorizedClientManager(OAuth2AuthorizedClientManager delegate) {
        this.delegate = delegate;
    }

    @Override
    public OAuth2AuthorizedClient authorize(OAuth2AuthorizeRequest authorizeRequest) {
        try {
            return delegate.authorize(authorizeRequest);
        } catch (ClientAuthorizationException ex) {
            throw new ClientAuthorizationRequiredException(authorizeRequest.getClientRegistrationId());
        }
    }
}

Bean 注册:

@Bean
public OAuth2AuthorizedClientManager getOAuth2AuthorizedClientManager(
        ClientRegistrationRepository clientRegistrationRepository,
        OAuth2AuthorizedClientRepository authorizedClientRepository) {

    final var manager = new DefaultOAuth2AuthorizedClientManager(clientRegistrationRepository, authorizedClientRepository);

    return new DelegatingOAuth2AuthorizedClientManager(manager);
}

Non-reactive implementation as of Spring security 6.4.x:

public class DelegatingOAuth2AuthorizedClientManager implements OAuth2AuthorizedClientManager {

    private final OAuth2AuthorizedClientManager delegate;

    public DelegatingOAuth2AuthorizedClientManager(OAuth2AuthorizedClientManager delegate) {
        this.delegate = delegate;
    }

    @Override
    public OAuth2AuthorizedClient authorize(OAuth2AuthorizeRequest authorizeRequest) {
        try {
            return delegate.authorize(authorizeRequest);
        } catch (ClientAuthorizationException ex) {
            throw new ClientAuthorizationRequiredException(authorizeRequest.getClientRegistrationId());
        }
    }
}

Bean registration:

@Bean
public OAuth2AuthorizedClientManager getOAuth2AuthorizedClientManager(
        ClientRegistrationRepository clientRegistrationRepository,
        OAuth2AuthorizedClientRepository authorizedClientRepository) {

    final var manager = new DefaultOAuth2AuthorizedClientManager(clientRegistrationRepository, authorizedClientRepository);

    return new DelegatingOAuth2AuthorizedClientManager(manager);
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文