Spring Security6 教程

Jsr250AuthorizationManager 授权管理器

Jsr250AuthorizationManager 是 Spring Security 6.1 中用于处理基于 JSR-250 注解的授权管理器。JSR-250 是 Java 规范请求,定义了一组标准的安全注解(如 @RolesAllowed、@PermitAll、@DenyAll),用于声明式地控制方法或类的访问权限。

核心功能

  • 注解支持,解析和处理 JSR-250 标准注解:

    • @RolesAllowed:指定允许访问的角色列表

    • @PermitAll:允许所有用户访问

    • @DenyAll:拒绝所有用户访问

  • 角色前缀处理,自动处理角色前缀(如 ROLE_),与 Spring Security 的角色系统无缝集成。

  • 元注解支持,支持自定义元注解(即注解上的注解),实现更灵活的权限控制。

源码分析

package org.springframework.security.authorization.method;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import java.util.function.Supplier;

import jakarta.annotation.security.DenyAll;
import jakarta.annotation.security.PermitAll;
import jakarta.annotation.security.RolesAllowed;
import org.aopalliance.intercept.MethodInvocation;

import org.springframework.aop.support.AopUtils;
import org.springframework.core.annotation.AnnotationConfigurationException;
import org.springframework.lang.NonNull;
import org.springframework.security.authorization.AuthoritiesAuthorizationManager;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.core.Authentication;
import org.springframework.util.Assert;

/**
 * JSR-250 规范安全注解的授权管理器
 * 用于处理基于 jakarta.annotation.security 包下注解的方法授权
 * 支持的注解包括:DenyAll、PermitAll 和 RolesAllowed
 */
public final class Jsr250AuthorizationManager implements AuthorizationManager<MethodInvocation> {

    // 存储所有支持的 JSR-250 安全注解类型
    private static final Set<Class<? extends Annotation>> JSR250_ANNOTATIONS = new HashSet<>();

    static {
        // 初始化支持的注解集合
        JSR250_ANNOTATIONS.add(DenyAll.class);
        JSR250_ANNOTATIONS.add(PermitAll.class);
        JSR250_ANNOTATIONS.add(RolesAllowed.class);
    }

    // 内部注册表,用于缓存和查找方法对应的授权管理器
    private final Jsr250AuthorizationManagerRegistry registry = new Jsr250AuthorizationManagerRegistry();

    // 用于实际权限检查的授权管理器
    private AuthorizationManager<Collection<String>> authoritiesAuthorizationManager = new AuthoritiesAuthorizationManager();

    // 角色前缀,默认为 "ROLE_"
    private String rolePrefix = "ROLE_";

    /**
     * 设置用于检查权限集合的授权管理器
     * @param authoritiesAuthorizationManager 权限检查管理器
     * @since 6.2
     */
    public void setAuthoritiesAuthorizationManager(
            AuthorizationManager<Collection<String>> authoritiesAuthorizationManager) {
        Assert.notNull(authoritiesAuthorizationManager, "authoritiesAuthorizationManager cannot be null");
        this.authoritiesAuthorizationManager = authoritiesAuthorizationManager;
    }

    /**
     * 设置角色前缀,默认为 "ROLE_"
     * 例如,当使用 @RolesAllowed("ADMIN") 时,实际检查的权限为 "ROLE_ADMIN"
     * @param rolePrefix 角色前缀字符串
     */
    public void setRolePrefix(String rolePrefix) {
        Assert.notNull(rolePrefix, "rolePrefix cannot be null");
        this.rolePrefix = rolePrefix;
    }

    /**
     * 根据 JSR-250 安全注解检查用户是否有权限访问方法
     * 检查逻辑:
     * 1. 查找方法或类上的 JSR-250 注解
     * 2. 根据注解类型(DenyAll、PermitAll、RolesAllowed)做出授权决策
     * @param authentication 当前用户的认证信息
     * @param methodInvocation 被调用的方法
     * @return 授权决策结果,如果没有找到 JSR-250 注解则返回 null
     */
    @Override
    public AuthorizationDecision check(Supplier<Authentication> authentication, MethodInvocation methodInvocation) {
        // 从注册表中获取对应的授权管理器并进行检查
        AuthorizationManager<MethodInvocation> delegate = this.registry.getManager(methodInvocation);
        return delegate.check(authentication, methodInvocation);
    }

    /**
     * 内部类:JSR-250 授权管理器注册表
     * 负责解析方法和类上的注解,并创建对应的授权管理器
     */
    private final class Jsr250AuthorizationManagerRegistry extends AbstractAuthorizationManagerRegistry {

        /**
         * 解析方法和目标类上的注解,创建对应的授权管理器
         * @param method 被调用的方法
         * @param targetClass 目标类
         * @return 授权管理器实例
         */
        @NonNull
        @Override
        AuthorizationManager<MethodInvocation> resolveManager(Method method, Class<?> targetClass) {
            // 查找方法或类上的 JSR-250 注解
            Annotation annotation = findJsr250Annotation(method, targetClass);
            
            // 根据注解类型返回不同的授权管理器
            if (annotation instanceof DenyAll) {
                // 拒绝所有访问
                return (a, o) -> new AuthorizationDecision(false);
            }
            if (annotation instanceof PermitAll) {
                // 允许所有访问
                return (a, o) -> new AuthorizationDecision(true);
            }
            if (annotation instanceof RolesAllowed rolesAllowed) {
                // 基于角色的访问控制
                return (a, o) -> Jsr250AuthorizationManager.this.authoritiesAuthorizationManager.check(a,
                        getAllowedRolesWithPrefix(rolesAllowed));
            }
            
            // 如果没有找到注解,返回空管理器(表示不做授权检查)
            return NULL_MANAGER;
        }

        /**
         * 查找方法或类上的 JSR-250 注解
         * 优先查找方法上的注解,其次查找类上的注解
         * @param method 方法对象
         * @param targetClass 目标类
         * @return 找到的注解,或 null
         */
        private Annotation findJsr250Annotation(Method method, Class<?> targetClass) {
            // 获取最具体的方法实现(处理代理情况)
            Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);
            // 先查找方法上的注解
            Annotation annotation = findAnnotation(specificMethod);
            // 如果方法上没有找到,查找类上的注解
            return (annotation != null) ? annotation : findAnnotation(specificMethod.getDeclaringClass());
        }

        /**
         * 在方法上查找 JSR-250 注解
         * @param method 方法对象
         * @return 找到的注解,或 null
         * @throws AnnotationConfigurationException 如果发现多个冲突的注解
         */
        private Annotation findAnnotation(Method method) {
            Set<Annotation> annotations = new HashSet<>();
            // 遍历所有支持的注解类型
            for (Class<? extends Annotation> annotationClass : JSR250_ANNOTATIONS) {
                // 查找方法上的指定类型注解
                Annotation annotation = AuthorizationAnnotationUtils.findUniqueAnnotation(method, annotationClass);
                if (annotation != null) {
                    annotations.add(annotation);
                }
            }
            
            // 处理注解冲突情况
            if (annotations.isEmpty()) {
                return null;
            }
            if (annotations.size() > 1) {
                throw new AnnotationConfigurationException(
                        "The JSR-250 specification disallows DenyAll, PermitAll, and RolesAllowed from appearing on the same method.");
            }
            
            return annotations.iterator().next();
        }

        /**
         * 在类上查找 JSR-250 注解
         * @param clazz 类对象
         * @return 找到的注解,或 null
         * @throws AnnotationConfigurationException 如果发现多个冲突的注解
         */
        private Annotation findAnnotation(Class<?> clazz) {
            Set<Annotation> annotations = new HashSet<>();
            // 遍历所有支持的注解类型
            for (Class<? extends Annotation> annotationClass : JSR250_ANNOTATIONS) {
                // 查找类上的指定类型注解
                Annotation annotation = AuthorizationAnnotationUtils.findUniqueAnnotation(clazz, annotationClass);
                if (annotation != null) {
                    annotations.add(annotation);
                }
            }
            
            // 处理注解冲突情况
            if (annotations.isEmpty()) {
                return null;
            }
            if (annotations.size() > 1) {
                throw new AnnotationConfigurationException(
                        "The JSR-250 specification disallows DenyAll, PermitAll, and RolesAllowed from appearing on the same class definition.");
            }
            
            return annotations.iterator().next();
        }

        /**
         * 获取带有前缀的角色集合
         * @param rolesAllowed RolesAllowed 注解
         * @return 带有前缀的角色集合
         */
        private Set<String> getAllowedRolesWithPrefix(RolesAllowed rolesAllowed) {
            Set<String> roles = new HashSet<>();
            // 为每个角色添加前缀
            for (int i = 0; i < rolesAllowed.value().length; i++) {
                roles.add(Jsr250AuthorizationManager.this.rolePrefix + rolesAllowed.value()[i]);
            }
            return roles;
        }
    }
}

上述代码分析了源码,在 Spring Security 6.1 中,JSR-250 支持的配置方式更加简化:

  • 通过 @EnableMethodSecurity(jsr250Enabled = true) 启用标准注解支持

  • 直接使用 @RolesAllowed、@PermitAll 等注解进行权限控制

  • 框架会自动处理注解解析和授权决策,无需手动配置元数据源

这种设计符合 Spring Security 的 "约定大于配置" 原则,减少了样板代码,同时保持了强大的扩展性。

简单实例

启用 JSR-250 支持

在 Spring Security 配置中启用 JSR-250 注解支持:

@Configuration
@EnableMethodSecurity(jsr250Enabled = true) // 启用 JSR-250 注解
public class SecurityConfig {
    // 其他安全配置...
}

使用 JSR-250 注解

在服务方法上添加 JSR-250 注解:

@RolesAllowed 注解

使用 @RolesAllowed 注解指定允许访问的角色,例如:

// 只有 admin 角色可以调用此方法
@RolesAllowed("admin")
@GetMapping("/hello")
public String hello() {
    System.out.println("hello()");
    return "Hello, Spring Security!";
}

或者

// 只有 admin 或 normal 角色可以调用此方法
@RolesAllowed({"admin", "normal"})
@GetMapping("/hello")
public String hello() {
    System.out.println("hello()");
    return "Hello, Spring Security!";
}

默认情况下,@RolesAllowed 会自动添加 ROLE_ 前缀。例如,@RolesAllowed("admin") 实际检查的是 ROLE_admin 权限。

@PermitAll 注解

允许所有用户访问,例如:

@PermitAll
@GetMapping("/hello")
public String hello() {
    System.out.println("hello()");
    return "Hello, Spring Security!";
}

上述 /hello 接口允许所有人访问。

@DenyAll 注解

拒绝所有用户访问,例如:

// 禁止任何访问(通常用于废弃接口)
@DenyAll
@GetMapping("/hello")
public String hello() {
    System.out.println("hello()");
    return "Hello, Spring Security!";
}

上述 /hello 接口将拒绝所有用户访问。

注意:Jsr250AuthorizationManager 提供了基于 JSR-250 标准注解的方法级授权能力,适合需要遵循 Java EE 安全规范的项目。通过简单的注解配置,即可实现细粒度的访问控制。

  

说说我的看法
全部评论(
没有评论
关于
本网站专注于 Java、数据库(MySQL、Oracle)、Linux、软件架构及大数据等多领域技术知识分享。涵盖丰富的原创与精选技术文章,助力技术传播与交流。无论是技术新手渴望入门,还是资深开发者寻求进阶,这里都能为您提供深度见解与实用经验,让复杂编码变得轻松易懂,携手共赴技术提升新高度。如有侵权,请来信告知:hxstrive@outlook.com
其他应用
公众号