在弹簧授权服务器中试图撤销访问令牌时获取Invalid_client错误

发布于 2025-02-11 06:40:26 字数 2862 浏览 1 评论 0原文

我正在使用弹簧授权服务器0.3.1 ,并调用/oauth2/devoke oauth client(angular-oauth2-oidc)的端点。有效载荷:

client_id: my-client-id
token: eyJraWQiOiJlYzA3N2Y2OC1jMjQ1LTQ[the rest is stripped for readability]
token_type_hint: access_token

响应包含错误“ Invalid_client”。
几分钟的调试表明,在org.springframework.security.oauth2.server.authorization.authertication.oauthentication.oauth2TokenRevocationauthentication provider#authentication

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        OAuth2TokenRevocationAuthenticationToken tokenRevocationAuthentication =
                (OAuth2TokenRevocationAuthenticationToken) authentication;

        OAuth2ClientAuthenticationToken clientPrincipal =
                getAuthenticatedClientElseThrowInvalidClient(tokenRevocationAuthentication);

    static OAuth2ClientAuthenticationToken getAuthenticatedClientElseThrowInvalidClient(Authentication authentication) {
        OAuth2ClientAuthenticationToken clientPrincipal = null;
        if (OAuth2ClientAuthenticationToken.class.isAssignableFrom(authentication.getPrincipal().getClass())) {
            clientPrincipal = (OAuth2ClientAuthenticationToken) authentication.getPrincipal();
        }
        if (clientPrincipal != null && clientPrincipal.isAuthenticated()) {
            return clientPrincipal;
        }
        throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_CLIENT);
    }

威尔在主要中具有OAuth2ClientAuthentication token,但实际上是JWTauthenticationToken的一个实例。 据我所知,自呼叫带有一个携带者令牌以来,它已获得授权,并且将jwtauthentication token设置为SecurityContextholder:

2022-06-28 23:27:01.392 DEBUG 16904 --- [  XNIO-1 task-1] o.s.security.web.FilterChainProxy        : Securing POST /oauth2/revoke
2022-06-28 23:27:01.393 DEBUG 16904 --- [  XNIO-1 task-1] s.s.w.c.SecurityContextPersistenceFilter : Set SecurityContextHolder to empty SecurityContext
2022-06-28 23:27:01.394 DEBUG 16904 --- [  XNIO-1 task-1] o.s.s.o.s.r.a.JwtAuthenticationProvider  : Authenticated token
2022-06-28 23:27:01.397 DEBUG 16904 --- [  XNIO-1 task-1] .o.s.r.w.BearerTokenAuthenticationFilter : Set SecurityContextHolder to JwtAuthenticationToken [Principal=org.springframework.security.oauth2.jwt.Jwt@1615fb2d, Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=127.0.0.1, SessionId=null], Granted Authorities=[SCOPE_openid, SCOPE_profile, SCOPE_offline_access, SCOPE_email]]
2022-06-28 23:27:01.398 DEBUG 16904 --- [  XNIO-1 task-1] o.s.s.w.a.i.FilterSecurityInterceptor    : Authorized filter invocation [POST /oauth2/revoke] with attributes [authenticated]

我是否缺少某些东西?我如何在这里获得oauth2clientauthentication token而不是jwtauthentication token?

I am using Spring Authorization Server 0.3.1 and calling /oauth2/revoke endpoint from OAuth client (angular-oauth2-oidc). Payload:

client_id: my-client-id
token: eyJraWQiOiJlYzA3N2Y2OC1jMjQ1LTQ[the rest is stripped for readability]
token_type_hint: access_token

Response contains error "invalid_client".
Several minutes of debugging show that exception is being thrown in org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenRevocationAuthenticationProvider#authenticate method:

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        OAuth2TokenRevocationAuthenticationToken tokenRevocationAuthentication =
                (OAuth2TokenRevocationAuthenticationToken) authentication;

        OAuth2ClientAuthenticationToken clientPrincipal =
                getAuthenticatedClientElseThrowInvalidClient(tokenRevocationAuthentication);

getAuthenticatedClientElseThrowInvalidClient method:

    static OAuth2ClientAuthenticationToken getAuthenticatedClientElseThrowInvalidClient(Authentication authentication) {
        OAuth2ClientAuthenticationToken clientPrincipal = null;
        if (OAuth2ClientAuthenticationToken.class.isAssignableFrom(authentication.getPrincipal().getClass())) {
            clientPrincipal = (OAuth2ClientAuthenticationToken) authentication.getPrincipal();
        }
        if (clientPrincipal != null && clientPrincipal.isAuthenticated()) {
            return clientPrincipal;
        }
        throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_CLIENT);
    }

It seems that OAuth2TokenRevocationAuthenticationProvider expects that passed authentication object will have OAuth2ClientAuthenticationToken in principal, but in fact principal is an instance of JwtAuthenticationToken.
As far as I understood since the call has a bearer token, it is authorized and JwtAuthenticationToken is set to SecurityContextHolder:

2022-06-28 23:27:01.392 DEBUG 16904 --- [  XNIO-1 task-1] o.s.security.web.FilterChainProxy        : Securing POST /oauth2/revoke
2022-06-28 23:27:01.393 DEBUG 16904 --- [  XNIO-1 task-1] s.s.w.c.SecurityContextPersistenceFilter : Set SecurityContextHolder to empty SecurityContext
2022-06-28 23:27:01.394 DEBUG 16904 --- [  XNIO-1 task-1] o.s.s.o.s.r.a.JwtAuthenticationProvider  : Authenticated token
2022-06-28 23:27:01.397 DEBUG 16904 --- [  XNIO-1 task-1] .o.s.r.w.BearerTokenAuthenticationFilter : Set SecurityContextHolder to JwtAuthenticationToken [Principal=org.springframework.security.oauth2.jwt.Jwt@1615fb2d, Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=127.0.0.1, SessionId=null], Granted Authorities=[SCOPE_openid, SCOPE_profile, SCOPE_offline_access, SCOPE_email]]
2022-06-28 23:27:01.398 DEBUG 16904 --- [  XNIO-1 task-1] o.s.s.w.a.i.FilterSecurityInterceptor    : Authorized filter invocation [POST /oauth2/revoke] with attributes [authenticated]

Am I missing something? How can I get OAuth2ClientAuthenticationToken instead of JwtAuthenticationToken here?

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

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

发布评论

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

评论(1

一直在等你来 2025-02-18 06:40:26

设法解决了问题。之所以发生,是因为我在授权过滤器链设置(启用UserInfo Endpoint)中有 oauth2resourceserver(),并在/oauth2/revoke call的标题中发送了一个持有人令牌:

@Bean
@Order(2)
@SuppressWarnings("unused")
public SecurityFilterChain authorizationFilterChain(HttpSecurity http) throws Exception {
    OAuth2AuthorizationServerConfigurer<HttpSecurity> authorizationServerConfigurer = new OAuth2AuthorizationServerConfigurer<>();

    RequestMatcher authServerEndpointsMatcher = authorizationServerConfigurer.getEndpointsMatcher();

    // Custom User Info Mapper that retrieves claims from a signed JWT
    Function<OidcUserInfoAuthenticationContext, OidcUserInfo> userInfoMapper = context -> {
        OidcUserInfoAuthenticationToken authentication = context.getAuthentication();
        JwtAuthenticationToken principal = (JwtAuthenticationToken) authentication.getPrincipal();
        return new OidcUserInfo(principal.getToken().getClaims());
    };

    if (StringUtils.isNotEmpty(ldapProperties.getUrl())) {
        http.authenticationProvider(ldapAuthenticationProvider());
    }
    http.authenticationProvider(daoAuthenticationProvider());

    http
            .requestMatchers((matchers) -> matchers.requestMatchers(authServerEndpointsMatcher)
                    .mvcMatchers("/login**", "/saml2/**"))
            .authorizeRequests()
            .antMatchers("/login/**").permitAll()
            .anyRequest().authenticated()
            .and()
            .csrf().disable()
            .oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt) // to enable userInfo endpoint
            .apply(authorizationServerConfigurer)
            .oidc(oidc -> oidc
                    .clientRegistrationEndpoint(Customizer.withDefaults())
                    .userInfoEndpoint(userInfo -> userInfo.userInfoMapper(userInfoMapper))
            ).and()
    ;

    if (authProperties.getSso().isEnabled()) {
        http
                .saml2Login().successHandler(new SavedRequestAwareAuthenticationSuccessHandler()).permitAll()
                .and()
                .sessionManagement((session) -> session.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED))
                .saml2Logout(Customizer.withDefaults());
    } else {
        http.formLogin().loginPage("/login").permitAll();
    }

    return http.build();
}

,解决方案是解决方案。要删除该特定过滤链中的载体令牌标题或删除OAuth2Resourceserver()。

另外,我发现截至目前,Spring授权服务器仅支持 /OAuth2 /Revoke Endpoint的基本授权,并且您在不指定基本AUTH标题中的客户端秘密的情况下无法调用此端点。当您拥有公共客户时,这是一个问题。例如,KeyCloak提供了一个选项,可以在请求主体和基本验证标头中指定客户端凭据,并允许省略客户端秘密参数

Managed to solve the problem. It happened because I had oauth2ResourceServer() in the authorization filter chain settings (to enable userInfo endpoint) and sent a Bearer token in the header of /oauth2/revoke call:

@Bean
@Order(2)
@SuppressWarnings("unused")
public SecurityFilterChain authorizationFilterChain(HttpSecurity http) throws Exception {
    OAuth2AuthorizationServerConfigurer<HttpSecurity> authorizationServerConfigurer = new OAuth2AuthorizationServerConfigurer<>();

    RequestMatcher authServerEndpointsMatcher = authorizationServerConfigurer.getEndpointsMatcher();

    // Custom User Info Mapper that retrieves claims from a signed JWT
    Function<OidcUserInfoAuthenticationContext, OidcUserInfo> userInfoMapper = context -> {
        OidcUserInfoAuthenticationToken authentication = context.getAuthentication();
        JwtAuthenticationToken principal = (JwtAuthenticationToken) authentication.getPrincipal();
        return new OidcUserInfo(principal.getToken().getClaims());
    };

    if (StringUtils.isNotEmpty(ldapProperties.getUrl())) {
        http.authenticationProvider(ldapAuthenticationProvider());
    }
    http.authenticationProvider(daoAuthenticationProvider());

    http
            .requestMatchers((matchers) -> matchers.requestMatchers(authServerEndpointsMatcher)
                    .mvcMatchers("/login**", "/saml2/**"))
            .authorizeRequests()
            .antMatchers("/login/**").permitAll()
            .anyRequest().authenticated()
            .and()
            .csrf().disable()
            .oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt) // to enable userInfo endpoint
            .apply(authorizationServerConfigurer)
            .oidc(oidc -> oidc
                    .clientRegistrationEndpoint(Customizer.withDefaults())
                    .userInfoEndpoint(userInfo -> userInfo.userInfoMapper(userInfoMapper))
            ).and()
    ;

    if (authProperties.getSso().isEnabled()) {
        http
                .saml2Login().successHandler(new SavedRequestAwareAuthenticationSuccessHandler()).permitAll()
                .and()
                .sessionManagement((session) -> session.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED))
                .saml2Logout(Customizer.withDefaults());
    } else {
        http.formLogin().loginPage("/login").permitAll();
    }

    return http.build();
}

So the solution is either to remove a Bearer token header or remove oauth2ResourceServer() in this particular filter chain.

Also I found that as of now Spring Authorization Server supports only basic authorization for the /oauth2/revoke endpoint, and you cannot call this endpoint without specifying a client secret in the basic auth header. It's a problem when you have public clients. Keycloak for example provides an option to specify client credentials in the request body as well as in the basic auth header, and allows to omit the client secret parameter

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