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

如何将自定义授权服务集成到Spring Authorization Server并实现权限范围管控?

实现方案与说明

一、流程合理性确认

你的流程逻辑完全通顺:认证通过后提取用户组→结合请求scope校验权限→展示可授权的scope→生成包含用户同意scope的令牌。需要注意两个细节:

  • openid是OIDC协议的标识,用于触发ID Token生成,本身不对应资源权限,建议在自研授权服务校验时排除该scope;
  • profile属于OIDC标准用户信息scope,通常只要用户认证通过即可默认允许,若需纳入自研权限控制,可同步加入校验逻辑。

二、具体实现步骤

1. 把用户组存入Authentication对象

在你已实现的AuthenticationProvider中,认证成功后将用户组信息存入自定义UserDetails,方便后续流程获取:

public class CustomAuthenticationProvider implements AuthenticationProvider {
    private final UserManagementService userManagementService;

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        // 调用自研用户管理服务完成认证
        UserInfo userInfo = userManagementService.authenticate(
                authentication.getName(), 
                authentication.getCredentials().toString()
        );

        // 自定义UserDetails,包含groups字段
        CustomUserDetails userDetails = new CustomUserDetails(
                userInfo.getUsername(),
                "",
                AuthorityUtils.createAuthorityList("ROLE_USER")
        );
        userDetails.setGroups(userInfo.getGroups());

        return new UsernamePasswordAuthenticationToken(
                userDetails, 
                authentication.getCredentials(), 
                userDetails.getAuthorities()
        );
    }

    // 重写supports方法
    @Override
    public boolean supports(Class<?> authentication) {
        return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
    }
}

// 自定义UserDetails实现类
public class CustomUserDetails implements UserDetails {
    private List<String> groups;

    // 构造方法、getter/setter
    // 实现UserDetails接口的其他方法(如isAccountNonExpired等,按需返回true/false)
}

2. 拦截授权请求,获取允许的scope

通过实现OAuth2AuthorizationRequestCustomizer,在用户认证后介入授权请求流程,调用自研授权服务过滤出用户可访问的scope:

@Component
public class ScopeFilterCustomizer implements OAuth2AuthorizationRequestCustomizer<OAuth2AuthorizationRequest.Builder> {
    private final AuthorizationServiceClient authorizationServiceClient;

    @Override
    public void customize(OAuth2AuthorizationRequest.Builder builder, Principal principal) {
        // 从Principal中提取用户组
        CustomUserDetails userDetails = (CustomUserDetails) ((UsernamePasswordAuthenticationToken) principal).getPrincipal();
        List<String> groups = userDetails.getGroups();

        // 提取请求中的业务scope,排除openid
        Set<String> requestedScopes = builder.build().getScopes();
        List<String> businessScopes = requestedScopes.stream()
                .filter(scope -> !"openid".equals(scope))
                .collect(Collectors.toList());

        // 调用自研授权服务,获取允许的scope
        AllowScopeResponse allowResponse = authorizationServiceClient.getAllowedScopes(groups, businessScopes);
        List<String> allowedScopes = allowResponse.getAllowedScopes();

        // 重新组装最终scope:允许的业务scope + 保留原请求的openid(如果有)
        Set<String> finalScopes = new HashSet<>(allowedScopes);
        if (requestedScopes.contains("openid")) {
            finalScopes.add("openid");
        }

        // 更新授权请求的scope
        builder.scopes(finalScopes);
    }
}

然后在Spring Security配置中注册该Customizer:

@Configuration
@EnableWebSecurity
public class SecurityConfig {
    @Bean
    public OAuth2AuthorizationRequestResolver authorizationRequestResolver(
            ClientRegistrationRepository clientRegistrationRepository,
            OAuth2AuthorizationRequestCustomizer<OAuth2AuthorizationRequest.Builder> customizer) {
        DefaultOAuth2AuthorizationRequestResolver resolver = new DefaultOAuth2AuthorizationRequestResolver(
                clientRegistrationRepository, "/oauth2/authorize"
        );
        resolver.setAuthorizationRequestCustomizer(customizer);
        return resolver;
    }

    // 其他配置(如AuthenticationProvider注册、csrf配置等)
}

3. 自定义授权同意页面

Spring Authorization Server默认会展示授权请求中的scope,我们只需替换为自定义页面,展示过滤后的允许scope即可:

  1. 创建Thymeleaf模板consent.html:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>授权确认</title>
</head>
<body>
    <h1>授权确认</h1>
    <p>客户端 <span th:text="${clientId}"></span> 请求访问以下权限:</p>
    <ul>
        <li th:each="scope : ${scopes}" th:text="${scope}"></li>
    </ul>
    <form method="post" th:action="${consentUri}">
        <input type="hidden" name="client_id" th:value="${clientId}">
        <input type="hidden" name="state" th:value="${state}">
        <button type="submit" name="action" value="approve">同意</button>
        <button type="submit" name="action" value="deny">拒绝</button>
    </form>
</body>
</html>
  1. 在配置中指定自定义页面:
@Bean
public OAuth2AuthorizationConsentController authorizationConsentController(
        OAuth2AuthorizationConsentService authorizationConsentService,
        OAuth2AuthorizationRequestResolver authorizationRequestResolver) {
    OAuth2AuthorizationConsentController controller = new OAuth2AuthorizationConsentController(authorizationConsentService);
    controller.setAuthorizationRequestResolver(authorizationRequestResolver);
    controller.setConsentPage("consent"); // 指定模板名称
    return controller;
}

4. 生成包含同意scope的令牌

Spring Authorization Server默认会根据用户同意的scope生成令牌,若需自定义令牌内容(如把scope写入claims),可实现OAuth2TokenCustomizer:

@Component
public class JwtTokenCustomizer implements OAuth2TokenCustomizer<JwtEncodingContext> {
    @Override
    public void customize(JwtEncodingContext context) {
        Set<String> consentedScopes = context.getAuthorization().getAuthorizedScopes();
        // 将同意的scope存入令牌claims(默认已包含,可按需扩展)
        context.getClaims().claim("authorized_scopes", consentedScopes);
    }
}

在授权服务器配置中注册该Customizer:

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig {
    @Bean
    public OAuth2TokenGenerator<?> tokenGenerator(
            JwtEncoder jwtEncoder,
            OAuth2TokenCustomizer<JwtEncodingContext> tokenCustomizer) {
        JwtGenerator jwtGenerator = new JwtGenerator(jwtEncoder);
        jwtGenerator.setJwtCustomizer(tokenCustomizer);
        return new DelegatingOAuth2TokenGenerator(
                jwtGenerator,
                new OAuth2AccessTokenGenerator(),
                new OAuth2RefreshTokenGenerator()
        );
    }

    // 其他配置(如ClientRegistrationRepository、JwtEncoder等)
}

三、关键逻辑纠正

  1. 不要将openid传入自研授权服务做权限校验,它是OIDC协议的标识,不对应资源权限;
  2. 若profile需纳入权限控制,需在自研授权服务中添加对应规则,否则可默认允许客户端的该scope请求。

内容的提问来源于stack exchange,提问作者Suvin Nimnaka Sukka

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 21:36:02