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

rseugnpd  于 2022-11-11  发布在  Spring
关注(0)|答案(1)|浏览(385)

我正在使用Spring授权服务器0.3.1并从OAuth客户端调用**/oauth2/revoke**端点(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.authentication.OAuth2TokenRevocationAuthenticationProvider#authenticate方法中抛出了异常:

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

看起来OAuth2 TokenRevocationAuthenticationProvider期望传递的authentication对象在主体中具有OAuth2 ClientAuthenticationToken,但实际上主体是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]

我是否遗漏了什么?如何在此处获得OAuth2 ClientAuthenticationToken而不是JwtAuthenticationToken?

lnxxn5zx

lnxxn5zx1#

设法解决了这个问题。发生这个问题的原因是我在授权筛选器链设置中有oauth2 ResourceServer()(以启用userInfo端点),并在/oauth2/revoke调用的标头中发送了一个承载令牌:

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

因此,解决方案是删除此特定筛选器链中的Bearer标记标头或删除oauth2 ResourceServer()。

我还发现Spring Authorization Server目前只支持对/oauth2/revoke端点的基本授权,如果没有在基本的auth头中指定一个客户端秘密,就不能调用这个端点。当您有公共客户端时,这是一个问题。例如,Keycloak提供了一个选项,可以在请求主体以及基本的auth头中指定客户端凭据,并允许省略客户端密码参数

相关问题