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

如何仅用JSR 250(@RolesAllowed)实现sec:authorize的URL权限校验

问题描述

Spring Security的<sec:authorize>标签可用于校验URL权限,示例如下:

<sec:authorize url="/details" var="allow_url_details"/>

该标签默认通过WebInvocationPrivilegeEvaluator进行权限评估,规则来源于HttpSecurity的配置:

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http.authorizeHttpRequests(auth -> auth
        .requestMatchers("/details").hasAnyRole("OPERATOR","TECH")
        .requestMatchers("/static/*", "/*", "/favicon/*").permitAll()
        .anyRequest().authenticated()
    );
    // ...
}

目前已启用@EnableMethodSecurity(jsr250Enabled = true),并在控制器方法上添加了JSR 250的@RolesAllowed注解:

@Controller
public class DetailController extends ControllerTemplate {
    @GetMapping("/details")
    @RolesAllowed({"OPERATOR", "TECH"})
    public String list() {
        return "details/list";
    }
}

现在存在两处重复的权限配置:一是HttpSecurity中的URL权限规则,二是控制器方法上的@RolesAllowed注解。请问能否仅通过JSR 250方式定义URL权限,无需在HttpSecurity中重复配置即可让<sec:authorize>标签生效?若可以,该如何实现?

实现方案

可以实现。核心思路是替换默认的WebInvocationPrivilegeEvaluator实现,让它从控制器方法的JSR 250注解中获取权限规则,而非依赖HttpSecurity的配置。具体步骤如下:

1. 自定义WebInvocationPrivilegeEvaluator实现

这个类的作用是根据URL匹配对应的控制器方法,读取方法上的@RolesAllowed注解,并判断当前用户是否具备对应权限:

import org.springframework.security.access.SecurityMetadataSource;
import org.springframework.security.access.method.MethodSecurityMetadataSource;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.WebInvocationPrivilegeEvaluator;
import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestWrapper;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import jakarta.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;

public class MethodAnnotationWebInvocationPrivilegeEvaluator implements WebInvocationPrivilegeEvaluator {

    private final RequestMappingHandlerMapping handlerMapping;
    private final MethodSecurityMetadataSource methodSecurityMetadataSource;

    public MethodAnnotationWebInvocationPrivilegeEvaluator(RequestMappingHandlerMapping handlerMapping,
                                                           MethodSecurityMetadataSource methodSecurityMetadataSource) {
        this.handlerMapping = handlerMapping;
        this.methodSecurityMetadataSource = methodSecurityMetadataSource;
    }

    @Override
    public boolean isAllowed(String uri, String method, Authentication authentication) {
        try {
            // 构建模拟请求,匹配对应的控制器方法
            HttpServletRequest request = new SecurityContextHolderAwareRequestWrapper(
                    new FilterInvocation(uri, method, null).getRequest(), null);
            // 获取URL对应的处理器方法
            Method handlerMethod = handlerMapping.getHandler(request).getHandlerMethod().getMethod();
            // 读取方法上的权限元数据(包含@RolesAllowed注解信息)
            var attributes = methodSecurityMetadataSource.getAttributes(handlerMethod, null);
            
            if (attributes == null || attributes.isEmpty()) {
                // 无权限注解时,默认要求用户已认证(可按需调整逻辑)
                return authentication.isAuthenticated();
            }
            // 调用Spring Security的权限决策器判断用户是否有权限
            org.springframework.security.access.intercept.AbstractSecurityInterceptor
                    .getAccessDecisionManager().decide(authentication, null, attributes);
            return true;
        } catch (Exception e) {
            // 匹配不到方法或权限不足时返回false
            return false;
        }
    }

    @Override
    public boolean isAllowed(String uri, Authentication authentication) {
        // 默认以GET方法匹配URL
        return isAllowed(uri, "GET", authentication);
    }
}

2. 注册自定义Bean并简化HttpSecurity配置

在Spring Security配置类中,注册自定义的WebInvocationPrivilegeEvaluator,同时简化HttpSecurity的权限配置(无需再单独配置URL的角色规则):

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.method.MethodSecurityMetadataSource;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.web.access.WebInvocationPrivilegeEvaluator;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

@Configuration
@EnableMethodSecurity(jsr250Enabled = true)
public class SecurityConfig {

    // 注册自定义的权限评估器
    @Bean
    public WebInvocationPrivilegeEvaluator webInvocationPrivilegeEvaluator(
            RequestMappingHandlerMapping handlerMapping,
            MethodSecurityMetadataSource methodSecurityMetadataSource) {
        return new MethodAnnotationWebInvocationPrivilegeEvaluator(handlerMapping, methodSecurityMetadataSource);
    }

    // 简化HttpSecurity配置,仅做基础认证控制
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
                .authorizeHttpRequests(auth -> auth
                        .anyRequest().authenticated() // 所有请求需先认证,具体权限由方法注解控制
                )
                // 按需配置登录、退出等其他规则
                .formLogin(form -> form.permitAll())
                .logout(logout -> logout.permitAll());
        return http.build();
    }
}

3. 注意事项

  • URL匹配兼容性:如果URL包含路径变量、请求参数等复杂场景,需要调整自定义评估器中构建请求的逻辑,确保能正确匹配到对应的控制器方法。
  • 静态资源处理:对于无对应控制器方法的静态资源,可在HttpSecurity中单独配置permitAll(),或在自定义评估器中添加特殊逻辑处理。
  • 权限决策逻辑:示例中直接复用了Spring Security的默认权限决策器,若有自定义权限需求,可替换为自定义的决策器。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 23:04:53