权限校验最佳实践模型代码

分层架构

src/main/java/com/zwnsyw/zwwwspringbootbasetemplate
├── security/                         # 安全模块
│   ├── annotation/                   # 注解定义
│   │   ├── Anonymous.java            # 匿名访问
│   │   ├── RequiresPermission.java   # 权限校验
│   │   └── RequiresRole.java         # 角色校验
│   │
│   ├── config/                        # 配置
│   │   ├── SecurityConfig.java        # 安全配置
│   │   └── AnonymousUrlConfig.java    # 匿名URL配置
│   │
│   ├── context/                       # 安全上下文
│   │   └── SecurityContext.java       # 当前用户上下文
│   │
│   │── enums/                         # 枚举
│   │   └── Logical.java               # 逻辑符枚举
│   │
│   │── handler/                       # 权限处理器
│   │   └── PermissionHandler.java     # 权限校验逻辑
│   │
│   │── interceptor/                       # 拦截器
│   │   └── AuthorizationInterceptor.java  # 鉴权拦截器
│   │
│   └── utils/                          # 工具类
│       └── SecurityUtils.java          # 安全工具类

代码

1. 注解定义

Anonymous

package com.zwnsyw.zwwwspringbootbasetemplate.security.annotation;

import java.lang.annotation.*;

/**
 * 标记接口允许匿名访问(无需登录)
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Anonymous {
}

RequiresPermission

package com.zwnsyw.zwwwspringbootbasetemplate.security.annotation;

import com.zwnsyw.zwwwspringbootbasetemplate.security.enums.Logical;

import java.lang.annotation.*;

/**
 * 权限校验注解
 * <p>
 * 使用示例:
 * - @RequiresPermission("system:user:add")         单个权限
 * - @RequiresPermission(value = {"user:add", "user:edit"}, logical = Logical.OR)  任一权限
 * </p>
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequiresPermission {

    /**
     * 需要的权限码
     */
    String[] value();

    /**
     * 多个权限之间的逻辑关系
     */
    Logical logical() default Logical.AND;
}

RequiresRole

package com.zwnsyw.zwwwspringbootbasetemplate.security.annotation;

import com.zwnsyw.zwwwspringbootbasetemplate.security.enums.Logical;

import java.lang.annotation.*;

/**
 * 角色校验注解
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequiresRole {

    /**
     * 需要的角色
     */
    String[] value();

    /**
     * 多个角色之间的逻辑关系
     */
    Logical logical() default Logical.AND;
}

Logical

package com.zwnsyw.zwwwspringbootbasetemplate.security.enums;

/**
 * 逻辑枚举
 */
public enum Logical {
    /**
     * 必须满足所有条件
     */
    AND,

    /**
     * 满足任一条件即可
     */
    OR
}

2. 安全上下文(存储当前请求的用户信息

package com.zwnsyw.zwwwspringbootbasetemplate.security.context;

import com.zwnsyw.zwwwspringbootbasetemplate.exception.BusinessException;
import com.zwnsyw.zwwwspringbootbasetemplate.exception.ErrorCode;
import com.zwnsyw.zwwwspringbootbasetemplate.model.vo.LoginUserVO;
import lombok.extern.slf4j.Slf4j;

/**
 * 安全上下文 - 使用 ThreadLocal 存储当前请求的用户信息
 * <p>
 * ==================== 工作原理 ====================
 *
 * 每个 HTTP 请求线程独立存储一份用户信息,不同线程互不影响:
 *
 * Thread-1 (请求A): SecurityContext -> User A
 * Thread-2 (请求B): SecurityContext -> User B
 * Thread-3 (请求C): SecurityContext -> User C
 *
 * ==================== 关键特性 ====================
 *
 * 1. ThreadLocal 隔离:每个请求线程独立存储
 * 2. 自动清理:拦截器 afterCompletion 中调用 clear()
 * 3. 防止泄露:线程池复用线程时防止数据混乱
 * 4. 获取用户:getCurrentUser()、requireCurrentUser()、getCurrentUserId()
 *
 * ==================== 使用方式 ====================
 *
 * // 在 Controller 中使用
 * @GetMapping("/profile")
 * public ResponseEntity<?> getProfile() {
 *     LoginUserVO user = SecurityContext.requireCurrentUser();
 *     return ResponseEntity.ok(user);
 * }
 *
 * // 在 Service 中使用
 * public void updateOrder(Order order) {
 *     Long userId = SecurityContext.requireCurrentUserId();
 *     // 验证用户是否为订单所有者
 *     if (!order.getUserId().equals(userId)) {
 *         throw new BusinessException("无权操作他人订单");
 *     }
 * }
 *
 * // 判断是否登录
 * if (SecurityContext.isAuthenticated()) {
 *     // 用户已登录
 * }
 */
@Slf4j
public class SecurityContext {

    // ThreadLocal 容器,每个线程存储一份用户信息
    private static final ThreadLocal<LoginUserVO> USER_HOLDER = new ThreadLocal<>();

    private SecurityContext() {
        // 私有构造函数,防止误操作实例化
    }

    /**
     * 设置当前请求的用户信息
     * <p>
     * 由 AuthorizationInterceptor 在请求处理前调用
     * </p>
     *
     * @param user 登录用户信息,null 时清除之前的信息
     */
    public static void setCurrentUser(LoginUserVO user) {
        if (user == null) {
            clear();
        } else {
            USER_HOLDER.set(user);
            log.debug("Set current user: {}", user.getId());
        }
    }

    /**
     * 获取当前请求的用户信息
     * <p>
     * 如果用户未登录返回 null
     * </p>
     *
     * @return 当前登录用户,未登录时返回 null
     */
    public static LoginUserVO getCurrentUser() {
        return USER_HOLDER.get();
    }

    /**
     * 获取当前用户信息,如果未登录抛出异常
     * <p>
     * 推荐在需要用户信息的地方使用此方法
     * </p>
     *
     * @return 当前登录用户
     * @throws BusinessException 用户未登录时抛出 NOT_LOGIN_ERROR
     */
    public static LoginUserVO requireCurrentUser() {
        LoginUserVO user = getCurrentUser();
        if (user == null) {
            log.warn("User not authenticated when calling requireCurrentUser()");
            throw new BusinessException(ErrorCode.NOT_LOGIN_ERROR, "用户未登录");
        }
        return user;
    }

    /**
     * 获取当前用户的 ID
     * <p>
     * 如果用户未登录返回 null
     * </p>
     *
     * @return 当前用户 ID,未登录时返回 null
     */
    public static Long getCurrentUserId() {
        LoginUserVO user = getCurrentUser();
        return user != null ? user.getId() : null;
    }

    /**
     * 获取当前用户的 ID,如果未登录抛出异常
     * <p>
     * 推荐在需要用户 ID 的地方使用此方法
     * </p>
     *
     * @return 当前用户 ID
     * @throws BusinessException 用户未登录时抛出 NOT_LOGIN_ERROR
     */
    public static Long requireCurrentUserId() {
        return requireCurrentUser().getId();
    }

    /**
     * 获取当前用户名
     *
     * @return 当前用户名
     */
    public static String getCurrentUsername() {
        LoginUserVO user = getCurrentUser();
        return user != null ? user.getUserName() : null;
    }

    /**
     * 清除当前线程的用户信息
     * <p>
     * 由 AuthorizationInterceptor.afterCompletion() 自动调用
     * 防止线程池中线程复用时信息泄露
     * </p>
     */
    public static void clear() {
        USER_HOLDER.remove();
        log.debug("SecurityContext cleared");
    }

    /**
     * 判断用户是否已认证(已登录)
     * <p>
     * 用于条件判断,不抛异常
     * </p>
     *
     * @return true 用户已登录,false 用户未登录
     */
    public static boolean isAuthenticated() {
        return getCurrentUser() != null;
    }

    /**
     * 判断当前用户 ID 是否与指定 ID 相同
     * <p>
     * 用于数据级权限检查
     * </p>
     *
     * @param userId 要比较的用户 ID
     * @return true 相同,false 不相同或未登录
     */
    public static boolean isCurrentUser(Long userId) {
        if (userId == null) {
            return false;
        }
        Long currentUserId = getCurrentUserId();
        return currentUserId != null && currentUserId.equals(userId);
    }

    /**
     * 判断当前用户是否具有指定角色
     * <p>
     * 这里仅做简单演示,实际应该通过 PermissionHandler 检查
     * </p>
     *
     * @param role 角色名
     * @return true 拥有指定角色,false 没有或未登录
     */
    public static boolean hasRole(String role) {
        LoginUserVO user = getCurrentUser();
        if (user == null) {
            return false;
        }

//        // 检查单角色
//        if (role.equals(user.getUserRole())) {
//            return true;
//        }

        // 检查多角色集合
        if (user.getRoles() != null) {
            return user.getRoles().contains(role);
        }

        return false;
    }
}

3. 权限处理器

package com.zwnsyw.zwwwspringbootbasetemplate.security.handler;

import com.zwnsyw.zwwwspringbootbasetemplate.model.vo.LoginUserVO;
import com.zwnsyw.zwwwspringbootbasetemplate.security.enums.Logical;
import com.zwnsyw.zwwwspringbootbasetemplate.security.context.SecurityContext;
import com.zwnsyw.zwwwspringbootbasetemplate.service.PermissionCacheService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.Arrays;
import java.util.Set;

import static com.zwnsyw.zwwwspringbootbasetemplate.constant.UserConstant.ADMIN_ROLE;
import static com.zwnsyw.zwwwspringbootbasetemplate.constant.UserConstant.SUPER_PERMISSION;

/**
 * 权限和角色校验处理器
 * <p>
 * ==================== 权限模型 ====================
 * <p>
 * 1. 超级权限:如果用户拥有 SUPER_PERMISSION,则拥有所有权限
 * 2. 管理员角色:如果用户角色包含 ADMIN_ROLE,则拥有所有权限
 * 3. 权限集合:检查用户的 permissions 集合
 * 4. 角色集合:检查用户的 roles 集合
 * <p>
 * ==================== 逻辑关系 ====================
 * <p>
 * AND 逻辑:必须拥有所有权限/角色
 * OR 逻辑:只需拥有任一权限/角色
 * <p>
 * ==================== 使用流程 ====================
 * <p>
 * 1. AuthorizationInterceptor 读取 @RequiresPermission/@RequiresRole 注解
 * 2. 调用 hasPermission/hasRole 方法进行校验
 * 3. 如果校验失败抛出 BusinessException
 */
@Slf4j
@Component
public class PermissionHandler {

    private PermissionCacheService permissionCacheService;

    @Autowired(required = false)
    public void setPermissionCacheService(PermissionCacheService permissionCacheService) {
        this.permissionCacheService = permissionCacheService;
    }

    /**
     * 校验用户是否拥有指定权限
     * <p>
     * 校验优先级(从高到低):
     * 1. 如果用户拥有超级权限 (SUPER_PERMISSION),直接返回 true
     * 2. 如果用户是管理员但没有配置权限,返回 true
     * 3. 检查用户的权限集合 (AND/OR 逻辑)
     * </p>
     *
     * @param permissions 需要的权限数组
     * @param logical     多权限之间的逻辑关系 (AND/OR)
     * @return true 拥有权限,false 没有权限
     */
    public boolean hasPermission(String[] permissions, Logical logical) {
        if (permissions == null || permissions.length == 0) {
            log.warn("Permission array is null or empty");
            return false;
        }

        LoginUserVO user = SecurityContext.getCurrentUser();
        if (user == null) {
            log.debug("User not authenticated, permission denied");
            return false;
        }

        // 优先使用 LoginUserVO 中的权限集合
        Set<String> userPermissions = user.getPermissions();

        // 如果 LoginUserVO 中没有权限,从缓存加载
        if ((userPermissions == null || userPermissions.isEmpty()) && permissionCacheService != null) {
            userPermissions = permissionCacheService.getPermissions(user.getId());
        }

        // 如果仍然没有权限配置
        if (userPermissions == null || userPermissions.isEmpty()) {
            // 管理员拥有所有权限
            boolean isAdmin = isAdmin(user);
            log.debug("User {} has no permission config, isAdmin: {}", user.getId(), isAdmin);
            return isAdmin;
        }

        // 超级权限检查
        if (userPermissions.contains(SUPER_PERMISSION)) {
            log.debug("User {} has super permission", user.getId());
            return true;
        }

        // 根据逻辑关系检查权限
        boolean hasPermission;
        if (logical == Logical.AND) {
            hasPermission = Arrays.stream(permissions).allMatch(userPermissions::contains);
        } else {
            hasPermission = Arrays.stream(permissions).anyMatch(userPermissions::contains);
        }

        if (!hasPermission) {
            log.warn("User {} missing permissions: {}, logical: {}",
                    user.getId(), Arrays.toString(permissions), logical);
        }

        return hasPermission;
    }

    /**
     * 校验用户是否拥有指定角色
     * <p>
     * 校验优先级(从高到低):
     * 1. 如果用户是管理员,拥有所有角色
     * 2. 检查用户的角色集合
     * 3. 根据 AND/OR 逻辑判断是否拥有指定角色
     * </p>
     *
     * @param roles   需要的角色数组
     * @param logical 多角色之间的逻辑关系 (AND/OR)
     * @return true 拥有指定角色,false 没有指定角色
     */
    public boolean hasRole(String[] roles, Logical logical) {
        if (roles == null || roles.length == 0) {
            log.warn("Role array is null or empty");
            return false;
        }

        LoginUserVO user = SecurityContext.getCurrentUser();
        if (user == null) {
            log.debug("User not authenticated, role denied");
            return false;
        }

        // 管理员拥有所有角色权限
        if (isAdmin(user)) {
            log.debug("User {} is admin, has all roles", user.getId());
            return true;
        }

        // 优先使用 LoginUserVO 中的角色集合
        Set<String> userRoles = user.getRoles();

        // 如果 LoginUserVO 中没有角色,从缓存加载
        if ((userRoles == null || userRoles.isEmpty()) && permissionCacheService != null) {
            userRoles = permissionCacheService.getRoles(user.getId());
        }

        // 如果仍然没有角色
        if (userRoles == null || userRoles.isEmpty()) {
            log.debug("User {} has no role config", user.getId());
            return false;
        }

        // 根据逻辑关系检查角色
        boolean hasRole;
        if (logical == Logical.AND) {
            hasRole = Arrays.stream(roles).allMatch(userRoles::contains);
        } else {
            hasRole = Arrays.stream(roles).anyMatch(userRoles::contains);
        }

        if (!hasRole) {
            log.warn("User {} missing roles: {}, logical: {}",
                    user.getId(), Arrays.toString(roles), logical);
        }

        return hasRole;
    }

    /**
     * 判断用户是否为管理员
     * <p>
     * 检查用户角色集合中是否包含 ADMIN_ROLE
     * </p>
     *
     * @param user 登录用户信息
     * @return true 是管理员,false 不是管理员
     */
    private boolean isAdmin(LoginUserVO user) {
        if (user == null) {
            return false;
        }
        // 使用 LoginUserVO 提供的便捷方法
        return user.isAdmin();
    }

    /**
     * 检查单个权限
     *
     * @param permission 权限标识
     * @return true 拥有权限,false 没有权限
     */
    public boolean checkPermission(String permission) {
        return hasPermission(new String[]{permission}, Logical.AND);
    }

    /**
     * 检查单个角色
     *
     * @param role 角色标识
     * @return true 拥有角色,false 没有角色
     */
    public boolean checkRole(String role) {
        return hasRole(new String[]{role}, Logical.AND);
    }
}

4. 匿名 URL 配置

package com.zwnsyw.zwwwspringbootbasetemplate.security.config;

import com.zwnsyw.zwwwspringbootbasetemplate.security.annotation.Anonymous;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;

/**
 * 匿名访问 URL 配置
 * <p>
 * 自动扫描 @Anonymous 注解,收集允许匿名访问的 URL
 * </p>
 *
 * ==================== 工作流程 ====================
 *
 * 1. 在拦截器中调用 isAnonymousUrl() 检查请求
 * 2. 通过 AntPathMatcher 匹配请求路径是否允许匿名访问
 * 3. 首次调用时自动扫描 @Anonymous 注解
 * 4. 后续调用使用缓存的 URL 列表
 *
 * ==================== 使用方式 ====================
 *
 * 在拦截器中使用:
 *
 * @Component
 * public class AuthorizationInterceptor implements HandlerInterceptor {
 *     @Autowired
 *     private AnonymousUrlConfig anonymousUrlConfig;
 *
 *     public boolean preHandle(HttpServletRequest request, ...) {
 *         String requestUri = request.getRequestURI();
 *         
 *         if (anonymousUrlConfig.isAnonymousUrl(requestUri)) {
 *             // 允许匿名访问
 *             return true;
 *         }
 *         // 需要验证权限
 *     }
 * }
 *
 * ==================== @Anonymous 注解位置 ====================
 *
 * 1. 方法级别(推荐):
 *    @GetMapping("/public")
 *    @Anonymous
 *    public ResponseEntity<?> publicApi() {
 *        return ResponseEntity.ok("公开接口");
 *    }
 *
 * 2. 类级别(整个 Controller 允许匿名):
 *    @RestController
 *    @Anonymous
 *    public class PublicController { }
 *
 * ==================== 关键特性 ====================
 *
 * - 支持路径变量:/user/{id} -> /user/*
 * - 线程安全:使用 volatile + synchronized 保证只扫描一次
 * - 延迟加载:首次调用时才扫描注解
 * - 缓存机制:扫描结果缓存在内存中
 */
@Slf4j
@Configuration
public class AnonymousUrlConfig {

    private static final Pattern PATH_VARIABLE_PATTERN = Pattern.compile("\\{[^}]+}");

    private final AntPathMatcher pathMatcher = new AntPathMatcher();

    @Getter
    private volatile Set<String> anonymousUrls;

    private final ObjectProvider<RequestMappingHandlerMapping> requestMappingHandlerMappingProvider;

    /**
     * 使用 ObjectProvider 延迟加载,避免 Bean 创建顺序问题
     */
    public AnonymousUrlConfig(ObjectProvider<RequestMappingHandlerMapping> requestMappingHandlerMappingProvider) {
        this.requestMappingHandlerMappingProvider = requestMappingHandlerMappingProvider;
    }

    /**
     * 检查请求 URL 是否允许匿名访问
     *
     * ==================== 处理流程 ====================
     * 
     * 1. 规范化请求路径
     *    - 原始路径:/api/auth/login
     *    - 处理后:/auth/login(移除 /api 前缀)
     * 
     * 2. 首次加载匿名 URL 列表
     *    - 扫描所有 @Anonymous 注解的方法/类
     *    - 缓存到 anonymousUrls
     * 
     * 3. 匹配请求路径
     *    - 使用 AntPathMatcher 进行模式匹配
     *    - 支持通配符:/user/* 匹配 /user/123
     *
     * @param requestUrl 请求 URL(完整路径,包含 /api 前缀)
     * @return 是否允许匿名访问
     * 
     * 使用示例:
     * boolean isAnonymous = anonymousUrlConfig.isAnonymousUrl("/api/auth/login");
     */
    public boolean isAnonymousUrl(String requestUrl) {
        // 第一步:规范化请求路径
        String normalizedUrl = normalizeRequestPath(requestUrl);
        
        log.trace("Checking anonymous URL: original={}, normalized={}", requestUrl, normalizedUrl);

        // 第二步:首次调用时才加载匿名 URL 列表
        if (anonymousUrls == null) {
            synchronized (this) {
                if (anonymousUrls == null) {
                    loadAnonymousUrls();
                }
            }
        }

        // 第三步:遍历所有匿名 URL 模式,使用 AntPathMatcher 判断是否匹配
        for (String pattern : anonymousUrls) {
            if (pathMatcher.match(pattern, normalizedUrl)) {
                log.debug("Request matched anonymous URL pattern: url={}, pattern={}", normalizedUrl, pattern);
                return true;
            }
        }
        
        log.trace("Request does not match any anonymous URL pattern: {}", normalizedUrl);
        return false;
    }

    /**
     * 规范化请求路径
     * 
     * 作用:移除服务器上下文路径前缀,只保留业务 URI
     * 
     * 处理规则:
     * 1. 如果以 /api 开头,去掉 /api(匹配 server.servlet.context-path=/api)
     * 2. 否则保持原样
     * 
     * 示例:
     * /api/auth/login → /auth/login
     * /auth/login → /auth/login
     * /api/user/123 → /user/123
     *
     * @param requestUrl 原始请求 URL
     * @return 规范化后的路径
     */
    private String normalizeRequestPath(String requestUrl) {
        if (requestUrl == null) {
            return "";
        }

        // ✅ 方法一:移除已知的上下文路径前缀
        // 适用于:server.servlet.context-path=/api 的配置
        if (requestUrl.startsWith("/api")) {
            return requestUrl.substring(4);  // 移除 "/api" 前缀
        }

        // ✅ 方法二:如果需要支持动态上下文路径,可以从 HTTP Request 中获取
        // 但由于 isAnonymousUrl 方法没有 request 参数,这里保持简单处理
        
        return requestUrl;
    }

    /**
     * 扫描 @Anonymous 注解,加载匿名访问 URL
     * 
     * 此方法在首次调用 isAnonymousUrl() 时执行
     * 
     * ==================== 扫描流程 ====================
     * 
     * 1. 获取所有 Spring MVC 映射的处理方法
     * 2. 检查是否有 @Anonymous 注解
     * 3. 提取 URL 路径模式
     * 4. 将路径变量转换为通配符
     * 5. 缓存到 anonymousUrls
     */
    private synchronized void loadAnonymousUrls() {
        // 双重检查:如果已加载,直接返回
        if (anonymousUrls != null) {
            return;
        }

        Set<String> urls = new HashSet<>();
        RequestMappingHandlerMapping mapping = requestMappingHandlerMappingProvider.getIfAvailable();

        if (mapping == null) {
            log.warn("RequestMappingHandlerMapping not available, using empty anonymous URL list");
            anonymousUrls = urls;
            return;
        }

        Map<RequestMappingInfo, HandlerMethod> handlerMethods = mapping.getHandlerMethods();

        handlerMethods.forEach((mappingInfo, handlerMethod) -> {
            // 检查是否有 @Anonymous 注解
            if (hasAnonymousAnnotation(handlerMethod)) {
                // 兼容两种路径匹配策略
                Set<String> patterns = new HashSet<>();

                // Spring Boot 2.6+ 使用 PathPatternsCondition
                if (mappingInfo.getPathPatternsCondition() != null) {
                    mappingInfo.getPathPatternsCondition().getPatterns()
                            .forEach(p -> patterns.add(p.getPatternString()));
                }
                // 旧版本使用 PatternsCondition
                else if (mappingInfo.getPatternsCondition() != null) {
                    patterns.addAll(mappingInfo.getPatternsCondition().getPatterns());
                }

                patterns.forEach(pattern -> {
                    // 将路径变量转换为 AntPattern
                    // 例如:/user/{id} -> /user/*
                    String antPattern = PATH_VARIABLE_PATTERN.matcher(pattern).replaceAll("*");
                    urls.add(antPattern);
                    log.debug("Registered anonymous URL pattern: {} -> {}", pattern, antPattern);
                });
            }
        });

        anonymousUrls = urls;
        log.info("Loaded {} anonymous URL patterns: {}", urls.size(), urls);
    }

    /**
     * 判断 Handler 方法或其所属类是否有 @Anonymous 注解
     *
     * ==================== 检查顺序 ====================
     * 
     * 1. 优先检查方法级别的注解
     *    - 如果方法有 @Anonymous,直接返回 true
     * 
     * 2. 再检查类级别的注解
     *    - 如果类有 @Anonymous,该类的所有方法都允许匿名访问
     *
     * @param handlerMethod Handler 方法
     * @return 是否有 @Anonymous 注解
     */
    private boolean hasAnonymousAnnotation(HandlerMethod handlerMethod) {
        // 先检查方法级别的注解
        Anonymous methodAnnotation = AnnotationUtils.findAnnotation(
                handlerMethod.getMethod(),
                Anonymous.class
        );
        if (methodAnnotation != null) {
            return true;
        }

        // 再检查类级别的注解
        Anonymous classAnnotation = AnnotationUtils.findAnnotation(
                handlerMethod.getBeanType(),
                Anonymous.class
        );
        return classAnnotation != null;
    }

    /**
     * 获取当前已加载的所有匿名 URL(用于调试)
     *
     * @return 匿名 URL 集合
     */
    public Set<String> getLoadedAnonymousUrls() {
        if (anonymousUrls == null) {
            synchronized (this) {
                if (anonymousUrls == null) {
                    loadAnonymousUrls();
                }
            }
        }
        return new HashSet<>(anonymousUrls);
    }

    /**
     * 打印所有匿名 URL(用于调试和监控)
     * 
     * 使用示例:
     * anonymousUrlConfig.printAnonymousUrls();
     */
    public void printAnonymousUrls() {
        Set<String> urls = getLoadedAnonymousUrls();
        log.info("========== Anonymous URLs ==========");
        urls.forEach(url -> log.info("  - {}", url));
        log.info("Total: {}", urls.size());
        log.info("====================================");
    }
}

5. 鉴权拦截器

package com.zwnsyw.zwwwspringbootbasetemplate.security.interceptor;

import com.zwnsyw.zwwwspringbootbasetemplate.exception.BusinessException;
import com.zwnsyw.zwwwspringbootbasetemplate.exception.ErrorCode;
import com.zwnsyw.zwwwspringbootbasetemplate.model.vo.LoginUserVO;
import com.zwnsyw.zwwwspringbootbasetemplate.security.annotation.RequiresPermission;
import com.zwnsyw.zwwwspringbootbasetemplate.security.annotation.RequiresRole;
import com.zwnsyw.zwwwspringbootbasetemplate.security.config.AnonymousUrlConfig;
import com.zwnsyw.zwwwspringbootbasetemplate.security.context.SecurityContext;
import com.zwnsyw.zwwwspringbootbasetemplate.security.handler.PermissionHandler;
import com.zwnsyw.zwwwspringbootbasetemplate.service.UserService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.Arrays;

/**
 * 鉴权拦截器 - 权限和认证的核心
 * <p>
 * ==================== 执行流程 ====================
 *
 * preHandle (请求处理前):
 * 1. 检查是否是 HandlerMethod(Controller 方法)
 * 2. 放行 OPTIONS 请求(CORS 预检)
 * 3. 检查 URL 是否允许匿名访问
 *   - 是:尝试获取用户信息(可选),继续处理
 *   - 否:必须获取用户信息,否则拒绝
 * 4. 检查 @RequiresPermission@RequiresRole 注解
 *
 * afterCompletion (请求完成后,总是会调用):
 * 5. 清理 ThreadLocal 中的用户信息(防止线程池泄露)
 *
 * ==================== 注意事项 ====================
 *
 * - 即使发生异常,afterCompletion 也会被调用
 * - OPTIONS 请求自动放行(CORS 预检)
 * - 非 Controller 方法(静态资源等)自动放行
 * </p>
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class AuthorizationInterceptor implements HandlerInterceptor {

    private final UserService userService;
    private final AnonymousUrlConfig anonymousUrlConfig;
    private final PermissionHandler permissionHandler;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {

        // 1. 非 Controller 方法直接放行(静态资源、异常处理等)
        if (!(handler instanceof HandlerMethod)) {
            log.debug("Not a HandlerMethod, pass through");
            return true;
        }

        String requestUri = request.getRequestURI();
        String method = request.getMethod();

        // 2. OPTIONS 请求放行(CORS 预检)
        if ("OPTIONS".equalsIgnoreCase(method)) {
            log.debug("OPTIONS request, pass through for CORS preflight");
            return true;
        }

        log.debug("Checking authorization for {} {}", method, requestUri);

        // 3. 检查是否匿名访问 URL
        if (anonymousUrlConfig.isAnonymousUrl(requestUri)) {
            log.debug("Anonymous URL, no authentication required");
            // 尝试获取用户信息(用于日志、审计等)
            trySetCurrentUser(request);
            return true;
        }

        // 4. 非匿名 URL 必须获取用户信息
        LoginUserVO loginUser = userService.getLoginUser(request);
        if (loginUser == null) {
            log.warn("Authentication required but user not found for {}", requestUri);
            throw new BusinessException(ErrorCode.NOT_LOGIN_ERROR);
        }

        // 将用户信息存储到 ThreadLocal
        SecurityContext.setCurrentUser(loginUser);
        log.debug("User authenticated: {}", loginUser.getId());

        // 5. 检查权限和角色注解
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        checkPermissionAnnotations(handlerMethod);
        checkRoleAnnotations(handlerMethod);

        return true;
    }

    /**
     * 请求完成后必须清理 ThreadLocal
     * 即使发生异常也会调用此方法
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
                                Object handler, Exception ex) throws Exception {
        // 清理 ThreadLocal,防止线程池复用时信息泄露
        if (SecurityContext.isAuthenticated()) {
            Long userId = SecurityContext.getCurrentUserId();
            log.debug("Clearing SecurityContext for user {}", userId);
        }
        SecurityContext.clear();
    }

    /**
     * 尝试获取用户信息(匿名访问时使用)
     * 不抛异常,失败时忽略
     */
    private void trySetCurrentUser(HttpServletRequest request) {
        try {
            LoginUserVO loginUser = userService.getLoginUser(request);
            if (loginUser != null) {
                SecurityContext.setCurrentUser(loginUser);
                log.debug("Set user info for anonymous request: {}", loginUser.getId());
            }
        } catch (Exception e) {
            // 忽略异常,匿名访问不需要用户信息
            log.trace("Failed to get user info for anonymous request", e);
        }
    }

    /**
     * 检查权限注解
     */
    private void checkPermissionAnnotations(HandlerMethod handlerMethod) {
        Method method = handlerMethod.getMethod();
        Class<?> beanType = handlerMethod.getBeanType();

        // 优先检查方法级别注解
        RequiresPermission methodPermission = method.getAnnotation(RequiresPermission.class);
        if (methodPermission != null) {
            validatePermission(methodPermission, method.getName(), "方法");
            return;
        }

        // 再检查类级别注解
        RequiresPermission classPermission = beanType.getAnnotation(RequiresPermission.class);
        if (classPermission != null) {
            validatePermission(classPermission, beanType.getName(), "类");
        }
    }

    /**
     * 检查角色注解
     */
    private void checkRoleAnnotations(HandlerMethod handlerMethod) {
        Method method = handlerMethod.getMethod();
        Class<?> beanType = handlerMethod.getBeanType();

        // 优先检查方法级别注解
        RequiresRole methodRole = method.getAnnotation(RequiresRole.class);
        if (methodRole != null) {
            validateRole(methodRole, method.getName(), "方法");
            return;
        }

        // 再检查类级别注解
        RequiresRole classRole = beanType.getAnnotation(RequiresRole.class);
        if (classRole != null) {
            validateRole(classRole, beanType.getName(), "类");
        }
    }

    /**
     * 验证权限,失败抛出异常
     */
    private void validatePermission(RequiresPermission annotation, String target, String type) {
        if (!permissionHandler.hasPermission(annotation.value(), annotation.logical())) {
            String permissions = Arrays.toString(annotation.value());
            Long userId = SecurityContext.getCurrentUserId();

            log.warn("{} {} - 权限不足 - userId: {}, required: {}, logical: {}",
                    type, target, userId, permissions, annotation.logical());

            throw new BusinessException(ErrorCode.FORBIDDEN,
                    "权限不足,需要 " + permissions + " 权限");
        }
    }

    /**
     * 验证角色,失败抛出异常
     */
    private void validateRole(RequiresRole annotation, String target, String type) {
        if (!permissionHandler.hasRole(annotation.value(), annotation.logical())) {
            String roles = Arrays.toString(annotation.value());
            Long userId = SecurityContext.getCurrentUserId();

            log.warn("{} {} - 角色不足 - userId: {}, required: {}, logical: {}",
                    type, target, userId, roles, annotation.logical());

            throw new BusinessException(ErrorCode.NO_AUTH_ERROR,
                    "角色权限不足,需要 " + roles + " 角色");
        }
    }
}

6. 安全配置(注册拦截器)

package com.zwnsyw.zwwwspringbootbasetemplate.security.config;

import com.zwnsyw.zwwwspringbootbasetemplate.security.interceptor.AuthorizationInterceptor;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
@RequiredArgsConstructor
public class SecurityConfig implements WebMvcConfigurer {

    private final AuthorizationInterceptor authorizationInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(authorizationInterceptor)
                .addPathPatterns("/**")
                .excludePathPatterns(
                        // 静态资源
                        "/static/**",
                        "/favicon.ico",
                        // Swagger/Knife4j
                        "/doc.html",
                        "/swagger-resources/**",
                        "/webjars/**",
                        "/v3/api-docs/**",
                        // 健康检查和错误页面
                        "/health",
                        "/error"
                );
    }
}

7. 安全工具类

package com.zwnsyw.zwwwspringbootbasetemplate.security.utils;

import com.zwnsyw.zwwwspringbootbasetemplate.exception.BusinessException;
import com.zwnsyw.zwwwspringbootbasetemplate.exception.ErrorCode;
import com.zwnsyw.zwwwspringbootbasetemplate.model.vo.LoginUserVO;
import com.zwnsyw.zwwwspringbootbasetemplate.security.context.SecurityContext;
import com.zwnsyw.zwwwspringbootbasetemplate.security.enums.Logical;
import com.zwnsyw.zwwwspringbootbasetemplate.security.handler.PermissionHandler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

/**
 * 安全工具类 - 提供权限校验、用户获取等便捷方法
 * <p>
 * ==================== 使用场景 ====================
 *
 * 1. 获取当前用户信息
 *    LoginUserVO user = SecurityUtils.requireLoginUser();
 *    Long userId = SecurityUtils.getUserId();
 *
 * 2. 权限校验(失败抛异常)
 *    SecurityUtils.checkPermission("user:add");
 *    SecurityUtils.checkAnyPermission("user:add", "user:edit");
 *    SecurityUtils.checkRole("admin");
 *
 * 3. 权限判断(返回 boolean)
 *    if (SecurityUtils.hasPermission("user:delete")) { }
 *    if (SecurityUtils.isAdmin()) { }
 *
 * 4. 数据级权限检查
 *    SecurityUtils.checkSelfOrAdmin(userId);  // 只允许本人或管理员
 *    SecurityUtils.checkOwner(ownerId);        // 只允许所有者
 *
 * ==================== 方法分类 ====================
 *
 * 获取用户信息类:
 * - getLoginUser()
 * - requireLoginUser()
 * - getUserId()
 * - getUsername()
 *
 * 权限校验类(失败抛异常):
 * - checkPermission()
 * - checkAnyPermission()
 * - checkRole()
 * - checkAnyRole()
 * - checkSelfOrAdmin()
 * - checkOwner()
 *
 * 权限判断类(返回 boolean):
 * - hasPermission()
 * - hasRole()
 * - isAdmin()
 * - isSuperAdmin()
 * - isAuthenticated()
 * - isSelfOrAdmin()
 *
 * ==================== 线程安全 ====================
 *
 * 所有方法都是线程安全的,基于 ThreadLocal 实现
 * 每个请求线程独立存储用户信息
 */
@Slf4j
@Component
public class SecurityUtils {

    private static PermissionHandler permissionHandler;

    /**
     * Spring 注入 PermissionHandler
     * 在构造器中初始化静态变量,避免空指针
     */
    public SecurityUtils(PermissionHandler permissionHandler) {
        SecurityUtils.permissionHandler = permissionHandler;
    }

    // ==================== 用户信息获取 ====================

    /**
     * 获取当前登录用户(可能为 null)
     * <p>
     * 用于可选判断场景,不抛异常
     * </p>
     *
     * @return 当前登录用户,未登录时返回 null
     */
    public static LoginUserVO getLoginUser() {
        return SecurityContext.getCurrentUser();
    }

    /**
     * 获取当前登录用户(必须登录)
     * <p>
     * 推荐使用此方法,确保用户已登录
     * </p>
     *
     * @return 当前登录用户
     * @throws BusinessException NOT_LOGIN_ERROR 用户未登录时抛出
     */
    public static LoginUserVO requireLoginUser() {
        return SecurityContext.requireCurrentUser();
    }

    /**
     * 获取当前用户 ID(可能为 null)
     *
     * @return 当前用户 ID,未登录时返回 null
     */
    public static Long getUserId() {
        return SecurityContext.getCurrentUserId();
    }

    /**
     * 获取当前用户 ID(必须登录)
     * <p>
     * 推荐使用此方法
     * </p>
     *
     * @return 当前用户 ID
     * @throws BusinessException NOT_LOGIN_ERROR 用户未登录时抛出
     */
    public static Long requireUserId() {
        return SecurityContext.requireCurrentUserId();
    }

    /**
     * 获取当前用户名
     *
     * @return 用户名,未登录时返回 null
     */
    public static String getUsername() {
        return SecurityContext.getCurrentUsername();
    }

    // ==================== 权限校验(失败抛异常)====================

    /**
     * 校验用户是否拥有指定权限,无权限则抛异常
     * <p>
     * 用于强制要求权限的场景
     * 失败时抛出 FORBIDDEN 异常
     * </p>
     *
     * @param permissions 需要的权限(支持多个,AND 逻辑)
     * @throws BusinessException FORBIDDEN 无权限时抛出
     *
     * 示例:
     * SecurityUtils.checkPermission("user:add");
     * SecurityUtils.checkPermission("user:edit", "user:delete");
     */
    public static void checkPermission(String... permissions) {
        if (!permissionHandler.hasPermission(permissions, Logical.AND)) {
            Long userId = SecurityContext.getCurrentUserId();
            log.warn("Permission denied for user: {}, required permissions: {}", userId, permissions);
            throw new BusinessException(ErrorCode.FORBIDDEN, "权限不足,无法执行此操作");
        }
    }

    /**
     * 校验用户是否拥有任一权限,无权限则抛异常
     * <p>
     * OR 逻辑:只需拥有任一权限即可
     * </p>
     *
     * @param permissions 需要的权限(支持多个,OR 逻辑)
     * @throws BusinessException FORBIDDEN 无权限时抛出
     *
     * 示例:
     * SecurityUtils.checkAnyPermission("content:publish", "content:review");
     */
    public static void checkAnyPermission(String... permissions) {
        if (!permissionHandler.hasPermission(permissions, Logical.OR)) {
            Long userId = SecurityContext.getCurrentUserId();
            log.warn("Permission denied for user: {}, required any of: {}", userId, permissions);
            throw new BusinessException(ErrorCode.FORBIDDEN, "权限不足,无法执行此操作");
        }
    }

    /**
     * 校验用户是否拥有指定角色,无角色则抛异常
     * <p>
     * AND 逻辑:必须拥有所有指定角色
     * </p>
     *
     * @param roles 需要的角色(支持多个,AND 逻辑)
     * @throws BusinessException NO_AUTH_ERROR 角色不足时抛出
     *
     * 示例:
     * SecurityUtils.checkRole("admin");
     * SecurityUtils.checkRole("editor", "reviewer");  // 需要同时拥有两个角色
     */
    public static void checkRole(String... roles) {
        if (!permissionHandler.hasRole(roles, Logical.AND)) {
            Long userId = SecurityContext.getCurrentUserId();
            log.warn("Role denied for user: {}, required roles: {}", userId, roles);
            throw new BusinessException(ErrorCode.NO_AUTH_ERROR, "角色权限不足,无法执行此操作");
        }
    }

    /**
     * 校验用户是否拥有任一角色,无角色则抛异常
     * <p>
     * OR 逻辑:只需拥有任一角色即可
     * </p>
     *
     * @param roles 需要的角色(支持多个,OR 逻辑)
     * @throws BusinessException NO_AUTH_ERROR 角色不足时抛出
     *
     * 示例:
     * SecurityUtils.checkAnyRole("admin", "super_admin");  // 拥有其一即可
     */
    public static void checkAnyRole(String... roles) {
        if (!permissionHandler.hasRole(roles, Logical.OR)) {
            Long userId = SecurityContext.getCurrentUserId();
            log.warn("Role denied for user: {}, required any of: {}", userId, roles);
            throw new BusinessException(ErrorCode.NO_AUTH_ERROR, "角色权限不足,无法执行此操作");
        }
    }

    // ==================== 权限判断(返回 boolean)====================

    /**
     * 判断用户是否拥有指定权限
     * <p>
     * 不抛异常,返回 boolean,用于条件判断
     * </p>
     *
     * @param permission 权限码
     * @return true 拥有权限,false 没有权限
     *
     * 示例:
     * if (SecurityUtils.hasPermission("user:delete")) {
     *     // 显示删除按钮
     * }
     */
    public static boolean hasPermission(String permission) {
        return permissionHandler.hasPermission(new String[]{permission}, Logical.AND);
    }

    /**
     * 判断用户是否拥有任一权限
     *
     * @param permissions 权限码数组
     * @return true 拥有任一权限,false 全部没有
     *
     * 示例:
     * if (SecurityUtils.hasAnyPermission("content:edit", "content:review")) {
     *     // 显示编辑或审核按钮
     * }
     */
    public static boolean hasAnyPermission(String... permissions) {
        return permissionHandler.hasPermission(permissions, Logical.OR);
    }

    /**
     * 判断用户是否拥有指定角色
     *
     * @param role 角色
     * @return true 拥有该角色,false 没有
     *
     * 示例:
     * if (SecurityUtils.hasRole("editor")) {
     *     // 显示编辑功能
     * }
     */
    public static boolean hasRole(String role) {
        return permissionHandler.hasRole(new String[]{role}, Logical.AND);
    }

    /**
     * 判断用户是否拥有任一角色
     *
     * @param roles 角色数组
     * @return true 拥有任一角色,false 全部没有
     */
    public static boolean hasAnyRole(String... roles) {
        return permissionHandler.hasRole(roles, Logical.OR);
    }

    /**
     * 判断用户是否为管理员
     * <p>
     * 检查用户是否拥有 "admin" 角色
     * </p>
     *
     * @return true 是管理员,false 不是
     *
     * 示例:
     * if (SecurityUtils.isAdmin()) {
     *     // 显示管理员菜单
     * }
     */
    public static boolean isAdmin() {
        return hasRole("admin");
    }

    /**
     * 判断用户是否为超级管理员
     *
     * @return true 是超级管理员,false 不是
     */
    public static boolean isSuperAdmin() {
        return hasRole("super_admin");
    }

    /**
     * 判断用户是否已认证(已登录)
     *
     * @return true 已登录,false 未登录
     *
     * 示例:
     * if (SecurityUtils.isAuthenticated()) {
     *     // 用户已登录,显示个人资料
     * } else {
     *     // 用户未登录,显示登录按钮
     * }
     */
    public static boolean isAuthenticated() {
        return SecurityContext.isAuthenticated();
    }

    /**
     * 判断当前用户是否为指定用户或管理员
     * <p>
     * 用于数据级权限判断,常见于个人资料、订单等场景
     * </p>
     *
     * @param userId 要判断的用户 ID
     * @return true 是本人或管理员,false 否
     *
     * 示例:
     * if (SecurityUtils.isSelfOrAdmin(orderId)) {
     *     // 可以查看/修改此订单
     * }
     */
    public static boolean isSelfOrAdmin(Long userId) {
        if (userId == null) {
            return false;
        }
        Long currentUserId = SecurityContext.getCurrentUserId();
        return currentUserId != null && currentUserId.equals(userId) || isAdmin();
    }

    // ==================== 数据级权限校验(失败抛异常)====================

    /**
     * 校验用户是否为指定用户或管理员,否则抛异常
     * <p>
     * 用于保护个人隐私数据,防止用户查看/修改他人信息
     * 常见于:个人资料、账户设置、订单详情等
     * </p>
     *
     * @param userId 要操作的用户 ID
     * @throws BusinessException FORBIDDEN 无权限时抛出
     *
     * 示例(Controller):
     * @GetMapping("/{userId}/profile")
     * public ResponseEntity<?> getProfile(@PathVariable Long userId) {
     *     SecurityUtils.checkSelfOrAdmin(userId);  // 权限校验
     *     return ResponseEntity.ok(userService.getProfile(userId));
     * }
     *
     * 示例(Service):
     * public void updateProfile(Long userId, UpdateRequest request) {
     *     SecurityUtils.checkSelfOrAdmin(userId);  // 权限校验
     *     // 执行更新逻辑...
     * }
     */
    public static void checkSelfOrAdmin(Long userId) {
        if (!isSelfOrAdmin(userId)) {
            Long currentUserId = SecurityContext.getCurrentUserId();
            log.warn("User {} trying to access data of user {}", currentUserId, userId);
            throw new BusinessException(ErrorCode.FORBIDDEN, "无权访问他人数据");
        }
    }

    /**
     * 校验用户是否为数据所有者,否则抛异常
     * <p>
     * 通用的所有权检查方法
     * 用于评论、文章、创建的任何内容等
     * </p>
     *
     * @param ownerId 数据所有者 ID
     * @throws BusinessException FORBIDDEN 无权限时抛出
     *
     * 示例:
     * public void deleteComment(Long commentId) {
     *     Comment comment = commentRepository.findById(commentId);
     *     SecurityUtils.checkOwner(comment.getUserId());  // 只有创建者能删除
     *     commentRepository.delete(comment);
     * }
     */
    public static void checkOwner(Long ownerId) {
        if (ownerId == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "所有者ID不能为空");
        }
        Long currentUserId = SecurityContext.getCurrentUserId();
        if (currentUserId == null || !currentUserId.equals(ownerId)) {
            log.warn("User {} trying to access resource owned by {}", currentUserId, ownerId);
            throw new BusinessException(ErrorCode.FORBIDDEN, "无权操作此资源");
        }
    }

    /**
     * 校验用户是否为指定用户、管理员或超级管理员
     *
     * @param userId 要检查的用户 ID
     * @throws BusinessException FORBIDDEN 无权限时抛出
     *
     * 示例:
     * public void resetPassword(Long userId) {
     *     SecurityUtils.checkSelfOrAnyAdmin(userId);
     * }
     */
    public static void checkSelfOrAnyAdmin(Long userId) {
        Long currentUserId = SecurityContext.getCurrentUserId();
        if (currentUserId == null || (!currentUserId.equals(userId) && !isAdmin() && !isSuperAdmin())) {
            log.warn("User {} trying to perform operation for user {}", currentUserId, userId);
            throw new BusinessException(ErrorCode.FORBIDDEN, "无权执行此操作");
        }
    }

    /**
     * 强制要求用户已登录,否则抛异常
     * <p>
     * 用于需要身份验证的操作
     * </p>
     *
     * @throws BusinessException NOT_LOGIN_ERROR 未登录时抛出
     *
     * 示例:
     * public void likeArticle(Long articleId) {
     *     SecurityUtils.requireAuthenticated();
     *     // 执行点赞逻辑...
     * }
     */
    public static void requireAuthenticated() {
        if (!isAuthenticated()) {
            throw new BusinessException(ErrorCode.NOT_LOGIN_ERROR, "请先登录");
        }
    }

    /**
     * 强制要求用户未登录,否则抛异常
     * <p>
     * 用于登录、注册等接口
     * </p>
     *
     * @throws BusinessException 已登录时抛出
     */
    public static void requireNotAuthenticated() {
        if (isAuthenticated()) {
            throw new BusinessException(ErrorCode.FORBIDDEN, "请先退出当前账号");
        }
    }
}

四、使用示例

@RestController
@RequestMapping("/api/user")
public class UserController {

    /**
     * 匿名访问(无需登录)
     */
    @Anonymous
    @GetMapping("/public/info")
    public BaseResponse<String> publicInfo() {
        return ResultUtils.success("公开信息");
    }

    /**
     * 需要登录(无注解默认需要登录)
     */
    @GetMapping("/profile")
    public BaseResponse<UserVO> getProfile() {
        // 从上下文获取当前用户,无需重复查询
        UserVO user = SecurityContext.getCurrentUser();
        return ResultUtils.success(user);
    }

    /**
     * 需要单个权限
     */
    @RequiresPermission("system:user:list")
    @GetMapping("/list")
    public BaseResponse<List<UserVO>> listUsers() {
        // ...
    }

    /**
     * 需要任一权限
     */
    @RequiresPermission(value = {"system:user:add", "system:user:edit"}, logical = Logical.OR)
    @PostMapping("/save")
    public BaseResponse<Void> saveUser() {
        // ...
    }

    /**
     * 需要特定角色
     */
    @RequiresRole("admin")
    @DeleteMapping("/{id}")
    public BaseResponse<Void> deleteUser(@PathVariable Long id) {
        // ...
    }

    /**
     * 需要任一角色
     */
    @RequiresRole(value = {"admin", "manager"}, logical = Logical.OR)
    @PutMapping("/status")
    public BaseResponse<Void> updateStatus() {
        // ...
    }
}

SecurityUtils 使用示例


项目分区导航:⬅️ 02-权限校验最佳实践模型-快捷使用清单 | 03-权限校验最佳实践模型代码 | ➡️ 04-SecurityUtils 使用示例