You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot Security中/oauth2/token空Scope返回全Scope的配置需求

解决Spring Boot Security OAuth2 Token接口默认返回全部Scope的问题

针对你遇到的/oauth2/token接口在client_credentials模式下,不传Scope就默认返回客户端所有权限的问题,不需要自定义过滤器(之前的过滤器没生效是因为OAuth2的请求处理逻辑有特定拦截链),可以通过以下两种方式实现需求:

方法一:修改Scope解析逻辑(轻量方案)

通过自定义OAuth2ClientCredentialsTokenEndpointFilter,重写Scope解析方法,当请求中无Scope参数时直接返回空集合,替代框架默认的"填充客户端所有Scope"逻辑:

import org.springframework.security.oauth2.server.authorization.web.OAuth2ClientCredentialsTokenEndpointFilter;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletRequest;
import java.util.Collections;
import java.util.Set;

public class CustomClientCredentialsTokenFilter extends OAuth2ClientCredentialsTokenEndpointFilter {

    @Override
    protected Set<String> resolveScopes(HttpServletRequest request) {
        String scopeParam = request.getParameter("scope");
        // 无Scope参数时返回空集合,而非默认的null
        if (!StringUtils.hasText(scopeParam)) {
            return Collections.emptySet();
        }
        return super.resolveScopes(request);
    }
}

然后在授权服务器配置中替换默认的Filter:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
public class AuthorizationServerConfig {

    @Bean
    public SecurityFilterChain authorizationServerFilterChain(HttpSecurity http) throws Exception {
        // 应用OAuth2授权服务器默认配置
        OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
        
        // 替换默认的ClientCredentials Token端点Filter
        http.addFilterBefore(new CustomClientCredentialsTokenFilter(), 
                             org.springframework.security.web.authentication.AuthenticationFilter.class);

        return http.build();
    }
}

方法二:自定义认证Provider(灵活扩展方案)

如果需要更复杂的权限控制逻辑,可以自定义ClientCredentialsAuthenticationProvider,直接控制授权的Scope集合:

import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.oauth2.core.*;
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientCredentialsAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
import org.springframework.util.CollectionUtils;

import java.util.Collections;
import java.util.Set;
import java.util.stream.Collectors;

public class CustomClientCredentialsAuthProvider implements AuthenticationProvider {

    private final RegisteredClientRepository registeredClientRepository;
    private final OAuth2AuthorizationService authorizationService;
    private final OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator;

    public CustomClientCredentialsAuthProvider(RegisteredClientRepository registeredClientRepository,
                                               OAuth2AuthorizationService authorizationService,
                                               OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator) {
        this.registeredClientRepository = registeredClientRepository;
        this.authorizationService = authorizationService;
        this.tokenGenerator = tokenGenerator;
    }

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        OAuth2ClientCredentialsAuthenticationToken authToken =
                (OAuth2ClientCredentialsAuthenticationToken) authentication;

        RegisteredClient registeredClient = registeredClientRepository.findByClientId(authToken.getPrincipal().toString());
        if (registeredClient == null) {
            throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_CLIENT);
        }

        // 核心逻辑:无请求Scope时授权空集合,否则只授权客户端已配置的请求Scope
        Set<String> requestedScopes = authToken.getScopes();
        Set<String> authorizedScopes = CollectionUtils.isEmpty(requestedScopes) ?
                Collections.emptySet() :
                registeredClient.getScopes().stream()
                        .filter(requestedScopes::contains)
                        .collect(Collectors.toSet());

        // 生成Token并保存授权信息
        OAuth2TokenContext tokenContext = OAuth2TokenContext.builder()
                .registeredClient(registeredClient)
                .principal(authToken)
                .authorizedScopes(authorizedScopes)
                .tokenType(OAuth2TokenType.ACCESS_TOKEN)
                .build();

        OAuth2Token accessToken = tokenGenerator.generate(tokenContext);
        if (accessToken == null) {
            throw new OAuth2AuthenticationException(OAuth2ErrorCodes.SERVER_ERROR);
        }

        OAuth2Authorization authorization = OAuth2Authorization.withRegisteredClient(registeredClient)
                .principalName(registeredClient.getClientId())
                .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
                .authorizedScopes(authorizedScopes)
                .token((OAuth2AccessToken) accessToken, metadata -> {})
                .build();

        authorizationService.save(authorization);

        return new OAuth2ClientCredentialsAuthenticationToken(
                registeredClient, null, authorizedScopes, accessToken);
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return OAuth2ClientCredentialsAuthenticationToken.class.isAssignableFrom(authentication);
    }
}

然后在配置中替换默认的Provider:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
public class AuthorizationServerConfig {

    @Bean
    public SecurityFilterChain authorizationServerFilterChain(HttpSecurity http,
                                                              RegisteredClientRepository registeredClientRepository,
                                                              OAuth2AuthorizationService authorizationService,
                                                              OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator) throws Exception {
        OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);

        http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
                .tokenEndpoint(tokenEndpoint -> tokenEndpoint
                        .authenticationProvider(
                                new CustomClientCredentialsAuthProvider(
                                        registeredClientRepository,
                                        authorizationService,
                                        tokenGenerator
                                )
                        )
                );

        return http.build();
    }
}

可选:确保JWT返回空Scope字段

如果需要JWT的Payload中明确返回空的scope数组(而非不包含该字段),可以自定义Token增强器:

import org.springframework.security.oauth2.server.authorization.token.JwtEncodingContext;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenEnhancer;

import java.util.Collections;

public class EmptyScopeTokenEnhancer implements OAuth2TokenEnhancer {
    @Override
    public void enhance(JwtEncodingContext context) {
        if (context.getAuthorizedScopes().isEmpty()) {
            context.getClaims().claim("scope", Collections.emptyList());
        }
    }
}

然后配置Token生成器时添加该增强器:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.server.authorization.token.JwtGenerator;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
import org.springframework.security.oauth2.server.authorization.token.DelegatingOAuth2TokenGenerator;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;

@Configuration
public class TokenConfig {

    @Bean
    public OAuth2TokenGenerator<?> tokenGenerator(JWKSource<SecurityContext> jwkSource) {
        JwtGenerator jwtGenerator = new JwtGenerator(jwkSource);
        jwtGenerator.setJwtCustomizer(new EmptyScopeTokenEnhancer());
        return new DelegatingOAuth2TokenGenerator(jwtGenerator);
    }
}

验证效果

  • 当请求不带scope参数时,响应的scope字段为空,JWT解码后scope为空数组(或按配置显示);
  • 当请求传入合法scope时,正常返回对应权限,JWT也包含指定Scope。

内容的提问来源于stack exchange,提问作者sujith M

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 10:14:55