繁体   English   中英

尝试撤销 Spring 授权服务器中的访问令牌时出现 invalid_client 错误

[英]Getting invalid_client error when trying to revoke an access token in Spring Authorization Server

我正在使用Spring 授权服务器 0.3.1并从 OAuth 客户端(angular-oauth2-oidc)调用/oauth2/revoke端点。 有效载荷:

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.authentication.OAuth2TokenRevocationAuthenticationProvider#authenticate 方法中抛出了异常:

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

        OAuth2ClientAuthenticationToken clientPrincipal =
                getAuthenticatedClientElseThrowInvalidClient(tokenRevocationAuthentication);

getAuthenticatedClientElseThrowInvalidClient 方法:

    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);
    }

似乎 OAuth2TokenRevocationAuthenticationProvider 期望通过身份验证的 object 主体中会有 OAuth2ClientAuthenticationToken,但实际上主体是 JwtAuthenticationToken 的一个实例。 据我了解,由于调用具有承载令牌,因此已获得授权,并且 JwtAuthenticationToken 设置为 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]

我错过了什么吗? 如何在这里获取 OAuth2ClientAuthenticationToken 而不是 JwtAuthenticationToken?

设法解决了问题。 发生这种情况是因为我在授权过滤器链设置中有oauth2ResourceServer() (以启用 userInfo 端点)并在 /oauth2/revoke 调用的 header 中发送了承载令牌:

@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();
}

因此,解决方案是删除承载令牌 header 或删除此特定过滤器链中的 oauth2ResourceServer()。

我还发现,截至目前,Spring 授权服务器仅支持 /oauth2/revoke 端点的基本授权,如果没有在基本身份验证 header 中指定客户端密码,则无法调用此端点。 当您有公共客户时,这是一个问题。 例如,Keycloak 提供了一个选项来在请求正文以及基本身份验证 header 中指定客户端凭据,并允许省略客户端密码参数

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM