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

Spring Security自定义授权规则实现:用户资源修改权限问题

实现方案说明

针对你的场景,不需要强制自定义AuthorizationManager,Spring Security提供了两种贴合其设计的实现方式,可根据场景复杂度选择:

前提准备:解析JWT到Authentication对象

首先需要确保JWT中的sub(用户ID)和roles(角色)被正确提取并转换为Spring Security的Authentication对象。通过配置JwtAuthenticationConverter实现:

@Bean
public JwtAuthenticationConverter jwtAuthenticationConverter() {
    JwtGrantedAuthoritiesConverter authoritiesConverter = new JwtGrantedAuthoritiesConverter();
    authoritiesConverter.setAuthorityPrefix("ROLE_"); // 为角色添加ROLE_前缀,适配Spring Security的角色校验规则
    authoritiesConverter.setAuthoritiesClaimName("roles"); // 指定JWT中存储角色的字段名

    JwtAuthenticationConverter converter = new JwtAuthenticationConverter();
    converter.setJwtGrantedAuthoritiesConverter(authoritiesConverter);
    return converter;
}

在SecurityFilterChain中配置OAuth2资源服务器时关联该转换器,确保JWT被正确解析为JwtAuthenticationToken。


方式一:表达式访问控制(简单场景首选)

利用Spring Security的SpEL表达式,结合自定义校验Bean实现授权规则,无需自定义AuthorizationManager。

步骤1:编写授权校验Bean

创建一个组件类,封装所有者/管理员的校验逻辑:

@Component
public class UserAuthorizationChecker {
    public boolean isOwnerOrAdmin(Authentication authentication, HttpServletRequest request) {
        // 优先校验是否为管理员角色
        boolean isAdmin = authentication.getAuthorities().stream()
                .anyMatch(auth -> auth.getAuthority().equals("ROLE_ADMIN"));
        if (isAdmin) {
            return true;
        }

        // 校验是否为资源所有者
        if (!(authentication instanceof JwtAuthenticationToken jwtToken)) {
            return false;
        }
        String currentUserId = jwtToken.getToken().getClaim("sub");
        // 从请求中获取路径参数userId
        Map<String, String> pathVariables = (Map<String, String>) request.getAttribute(
                HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE
        );
        String targetUserId = pathVariables.get("userId");
        return currentUserId.equals(targetUserId);
    }
}

步骤2:配置HttpSecurity

在SecurityFilterChain中通过access()方法引用自定义校验逻辑:

@Configuration
@EnableWebSecurity
public class SecurityConfig {
    private final UserAuthorizationChecker authorizationChecker;

    public SecurityConfig(UserAuthorizationChecker authorizationChecker) {
        this.authorizationChecker = authorizationChecker;
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
                .authorizeHttpRequests(auth -> auth
                        // 针对PUT请求的用户资源路径配置授权规则
                        .requestMatchers(HttpMethod.PUT, "/some_path/user/{userId}")
                        .access("@userAuthorizationChecker.isOwnerOrAdmin(authentication, request)")
                        // 其他请求只需认证即可
                        .anyRequest().authenticated()
                )
                .oauth2ResourceServer(oauth2 -> oauth2
                        .jwt(jwt -> jwt
                                .jwtAuthenticationConverter(jwtAuthenticationConverter())
                        )
                );
        return http.build();
    }

    // 引入前面定义的jwtAuthenticationConverter Bean
}

方式二:自定义/组合AuthorizationManager(复杂场景首选)

Spring Security 5.6+推荐使用AuthorizationManager作为授权核心组件,适合需要复用授权规则、扩展性要求高的场景。可以通过组合现有组件+自定义逻辑实现:

步骤1:实现所有者校验的AuthorizationManager

public class OwnerAuthorizationManager implements AuthorizationManager<RequestAuthorizationContext> {
    @Override
    public AuthorizationDecision check(Supplier<Authentication> authenticationSupplier, RequestAuthorizationContext context) {
        Authentication authentication = authenticationSupplier.get();
        if (!(authentication instanceof JwtAuthenticationToken jwtToken)) {
            return new AuthorizationDecision(false);
        }
        String currentUserId = jwtToken.getToken().getClaim("sub");
        String targetUserId = context.getVariables().get("userId");
        boolean isOwner = Objects.equals(currentUserId, targetUserId);
        return new AuthorizationDecision(isOwner);
    }
}

步骤2:组合管理员角色校验与所有者校验

通过OrAuthorizationManager将两个规则组合(满足任一即可):

@Bean
public AuthorizationManager<RequestAuthorizationContext> userModifyAuthorizationManager() {
    // 管理员角色校验规则
    AuthorizationManager<RequestAuthorizationContext> adminRule = AuthorityAuthorizationManager.hasRole("ADMIN");
    // 所有者校验规则
    AuthorizationManager<RequestAuthorizationContext> ownerRule = new OwnerAuthorizationManager();
    // 组合为"或"规则
    return new OrAuthorizationManager<>(adminRule, ownerRule);
}

步骤3:配置HttpSecurity

在SecurityFilterChain中直接引用组合后的AuthorizationManager:

@Configuration
@EnableWebSecurity
public class SecurityConfig {
    private final AuthorizationManager<RequestAuthorizationContext> userModifyAuthorizationManager;

    public SecurityConfig(AuthorizationManager<RequestAuthorizationContext> userModifyAuthorizationManager) {
        this.userModifyAuthorizationManager = userModifyAuthorizationManager;
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
                .authorizeHttpRequests(auth -> auth
                        .requestMatchers(HttpMethod.PUT, "/some_path/user/{userId}")
                        .access(userModifyAuthorizationManager)
                        .anyRequest().authenticated()
                )
                .oauth2ResourceServer(oauth2 -> oauth2
                        .jwt(jwt -> jwt
                                .jwtAuthenticationConverter(jwtAuthenticationConverter())
                        )
                );
        return http.build();
    }

    // 引入前面定义的jwtAuthenticationConverter Bean
}

总结

  • 是否必须自定义AuthorizationManager? 否。简单场景用表达式访问控制更简洁;复杂场景或需要遵循Spring Security最新授权模型时,自定义/组合AuthorizationManager是更贴合的选择。
  • 两种方式均无需访问用户数据库,完全基于JWT中的信息完成授权校验,适配微服务架构的无状态要求。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 18:47:10