安全层 - 权限控制完全指南 🔒


一、Java EE 标准安全注解(JSR-250)

1. @PermitAll(允许所有人)

/**
 * 允许所有用户访问,包括未登录用户
 * 通常用于:公开接口、登录接口、注册接口等
 */
@PermitAll
@GetMapping("/public/info")
public Result getPublicInfo() {
    return Result.success("公开信息");
}

// 类级别使用(所有方法都允许访问)
@RestController
@PermitAll
public class PublicController {
    // 所有方法都是公开的
}

2. @DenyAll(拒绝所有人)

/**
 * 拒绝所有用户访问,包括管理员
 * 通常用于:废弃的接口、维护中的功能
 */
@DenyAll
@GetMapping("/deprecated")
public Result deprecatedApi() {
    // 任何人调用都会返回 403 Forbidden
}

3. @RolesAllowed(角色限制)

/**
 * 只允许指定角色访问
 * 注意:这里的角色名不需要 ROLE_ 前缀
 */

// 1. 单个角色
@RolesAllowed("ADMIN")
@GetMapping("/admin/users")
public List<User> getUsers() {
    // 只有 ADMIN 角色可以访问
}

// 2. 多个角色(满足其中之一即可)
@RolesAllowed({"ADMIN", "MANAGER"})
@GetMapping("/management/reports")
public Report getReport() {
    // ADMIN 或 MANAGER 都可以访问
}

// 3. 类级别使用
@RestController
@RolesAllowed("ADMIN")
public class AdminController {
    // 所有方法都需要 ADMIN 角色
    
    // 方法级别可以覆盖类级别
    @RolesAllowed({"ADMIN", "MANAGER"})
    @GetMapping("/reports")
    public Report getReport() { }
}

4. 启用 JSR-250 注解

@Configuration
@EnableGlobalMethodSecurity(
    jsr250Enabled = true  // 启用 JSR-250 注解
)
public class SecurityConfig {
    // 配置内容...
}

二、Spring Security 安全注解

1. @Secured(简单角色控制)

/**
 * Spring Security 的角色注解
 * 注意:必须使用 ROLE_ 前缀
 */

// 1. 单个角色
@Secured("ROLE_ADMIN")
@GetMapping("/admin/dashboard")
public Dashboard getDashboard() { }

// 2. 多个角色
@Secured({"ROLE_ADMIN", "ROLE_MANAGER"})
@GetMapping("/management/data")
public Data getData() { }

// 3. 启用配置
@Configuration
@EnableGlobalMethodSecurity(
    securedEnabled = true  // 启用 @Secured 注解
)
public class SecurityConfig { }

2. @PreAuthorize(方法执行前鉴权)⭐

/**
 * 最强大灵活的权限注解
 * 支持 SpEL 表达式
 * 在方法执行前进行权限检查
 */

// 1. 基础角色检查
@PreAuthorize("hasRole('ADMIN')")
@GetMapping("/admin/users")
public List<User> getUsers() { }

// 2. 多角色检查(OR)
@PreAuthorize("hasRole('ADMIN') or hasRole('MANAGER')")
@GetMapping("/data")
public Data getData() { }

// 3. 多角色检查(AND)
@PreAuthorize("hasRole('ADMIN') and hasRole('SUPER_USER')")
@GetMapping("/sensitive")
public Data getSensitiveData() { }

// 4. 权限检查(不是角色)
@PreAuthorize("hasAuthority('USER_READ')")
@GetMapping("/users/{id}")
public User getUser(@PathVariable Long id) { }

// 5. 多权限检查
@PreAuthorize("hasAnyAuthority('USER_READ', 'USER_WRITE')")
@GetMapping("/users")
public List<User> getUsers() { }

// 6. 检查所有权限
@PreAuthorize("hasAuthority('USER_READ') and hasAuthority('USER_WRITE')")
@PostMapping("/users")
public User createUser(@RequestBody User user) { }

// 7. 访问方法参数
@PreAuthorize("#userId == authentication.principal.id")
@GetMapping("/users/{userId}/profile")
public Profile getProfile(@PathVariable Long userId) {
    // 只能查看自己的资料
}

// 8. 复杂表达式
@PreAuthorize("hasRole('ADMIN') or (#userId == authentication.principal.id and hasRole('USER'))")
@PutMapping("/users/{userId}")
public User updateUser(@PathVariable Long userId, @RequestBody User user) {
    // ADMIN 可以修改任何人,普通用户只能修改自己
}

// 9. 自定义表达式
@PreAuthorize("@permissionService.canAccess(#resourceId)")
@GetMapping("/resources/{resourceId}")
public Resource getResource(@PathVariable Long resourceId) { }

// 10. 匿名访问
@PreAuthorize("isAnonymous()")
@GetMapping("/login-page")
public String loginPage() { }

// 11. 已认证用户
@PreAuthorize("isAuthenticated()")
@GetMapping("/profile")
public Profile getProfile() { }

// 12. 完全认证(非 remember-me)
@PreAuthorize("isFullyAuthenticated()")
@PostMapping("/transfer")
public Result transfer(@RequestBody TransferRequest request) { }

// 13. IP 地址限制
@PreAuthorize("hasIpAddress('192.168.1.0/24')")
@GetMapping("/internal")
public Data getInternalData() { }

3. @PostAuthorize(方法执行后鉴权)

/**
 * 方法执行后进行权限检查
 * 可以访问返回值
 * 如果检查失败,抛出异常
 */

// 1. 检查返回对象的属性
@PostAuthorize("returnObject.owner == authentication.name")
@GetMapping("/documents/{id}")
public Document getDocument(@PathVariable Long id) {
    // 只能查看自己的文档
    // 如果返回的文档不属于当前用户,抛出异常
}

// 2. 检查返回对象是否为空
@PostAuthorize("returnObject != null")
@GetMapping("/users/{id}")
public User getUser(@PathVariable Long id) { }

// 3. 复杂检查
@PostAuthorize("returnObject.status == 'PUBLISHED' or hasRole('ADMIN')")
@GetMapping("/articles/{id}")
public Article getArticle(@PathVariable Long id) {
    // 文章必须是已发布,或者用户是管理员
}

// 4. 集合过滤(配合 @PostFilter 使用更好)
@PostAuthorize("returnObject.createdBy == authentication.name or hasRole('ADMIN')")
@GetMapping("/my-orders/{id}")
public Order getOrder(@PathVariable Long id) { }

4. @PreFilter(过滤方法参数)

/**
 * 在方法执行前过滤参数集合
 * 只能用于集合类型的参数
 */

// 1. 过滤列表参数
@PreFilter("filterObject.owner == authentication.name")
@PostMapping("/orders/batch")
public List<Order> processOrders(@RequestBody List<Order> orders) {
    // 只处理属于当前用户的订单
    // 其他订单会被自动过滤掉
}

// 2. 指定过滤目标(多个集合参数时)
@PreFilter(value = "filterObject.status == 'ACTIVE'", 
           filterTarget = "users")
@PostMapping("/process")
public Result process(@RequestBody List<User> users, 
                     @RequestBody List<Order> orders) {
    // 只过滤 users 集合
}

5. @PostFilter(过滤返回结果)

/**
 * 在方法执行后过滤返回的集合
 * 自动移除不符合条件的元素
 */

// 1. 过滤返回列表
@PostFilter("filterObject.owner == authentication.name or hasRole('ADMIN')")
@GetMapping("/orders")
public List<Order> getOrders() {
    // 返回所有订单,但自动过滤掉不属于当前用户的订单
    // 除非用户是 ADMIN
}

// 2. 复杂过滤条件
@PostFilter("filterObject.status == 'PUBLISHED' or filterObject.author == authentication.name")
@GetMapping("/articles")
public List<Article> getArticles() {
    // 只返回已发布的文章或自己创建的文章
}

// 3. 使用自定义方法
@PostFilter("@permissionService.canView(filterObject, authentication)")
@GetMapping("/documents")
public List<Document> getDocuments() { }

6. 启用 Spring Security 注解

@Configuration
@EnableGlobalMethodSecurity(
    prePostEnabled = true,   // 启用 @PreAuthorize, @PostAuthorize
    securedEnabled = true,   // 启用 @Secured
    jsr250Enabled = true     // 启用 @RolesAllowed, @PermitAll, @DenyAll
)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    // 配置内容...
}

三、SpEL 表达式完整列表

1. 内置表达式

// 角色检查
hasRole('ROLE_NAME')                    // 是否有指定角色
hasAnyRole('ROLE1', 'ROLE2')           // 是否有任意角色
hasRole('ADMIN') and hasRole('USER')   // 同时拥有多个角色

// 权限检查
hasAuthority('PERMISSION')              // 是否有指定权限
hasAnyAuthority('P1', 'P2')            // 是否有任意权限
hasAuthority('READ') and hasAuthority('WRITE')  // 同时拥有多个权限

// 认证状态
isAuthenticated()                       // 是否已认证
isAnonymous()                          // 是否匿名
isFullyAuthenticated()                 // 是否完全认证(非 remember-me)
isRememberMe()                         // 是否通过 remember-me 认证

// IP 限制
hasIpAddress('192.168.1.1')            // 指定 IP
hasIpAddress('192.168.1.0/24')         // IP 段

// 组合使用
permitAll                               // 允许所有人
denyAll                                 // 拒绝所有人

2. 访问对象属性

// 访问方法参数
@PreAuthorize("#userId == authentication.principal.id")
public User getUser(@PathVariable Long userId) { }

// 访问返回值
@PostAuthorize("returnObject.owner == authentication.name")
public Document getDocument(@PathVariable Long id) { }

// 访问认证对象
authentication.principal                // 当前用户对象
authentication.name                     // 用户名
authentication.authorities             // 权限列表
authentication.credentials             // 凭证
authentication.details                 // 详细信息

3. 自定义表达式

// 调用 Spring Bean 的方法
@PreAuthorize("@permissionService.canAccess(#resourceId)")
public Resource getResource(@PathVariable Long resourceId) { }

@PreAuthorize("@authService.hasPermission(authentication, #id, 'READ')")
public Data getData(@PathVariable Long id) { }

四、自定义权限控制

1. 自定义权限服务

@Service("permissionService")
public class PermissionService {
    
    /**
     * 检查用户是否可以访问资源
     */
    public boolean canAccess(Long resourceId) {
        // 获取当前用户
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        String username = auth.getName();
        
        // 自定义业务逻辑
        // 例如:检查数据库中的权限记录
        return checkUserResourcePermission(username, resourceId);
    }
    
    /**
     * 检查用户是否可以编辑资源
     */
    public boolean canEdit(Long resourceId) {
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        String username = auth.getName();
        
        // 检查是否是资源所有者
        Resource resource = resourceRepository.findById(resourceId);
        if (resource.getOwner().equals(username)) {
            return true;
        }
        
        // 或者检查是否有编辑权限
        return auth.getAuthorities().stream()
            .anyMatch(a -> a.getAuthority().equals("RESOURCE_EDIT"));
    }
    
    /**
     * 检查用户是否可以查看对象
     */
    public boolean canView(Object obj, Authentication authentication) {
        if (obj instanceof Document) {
            Document doc = (Document) obj;
            String username = authentication.getName();
            
            // 文档是公开的,或者是文档所有者,或者是管理员
            return doc.isPublic() 
                || doc.getAuthor().equals(username)
                || authentication.getAuthorities().stream()
                    .anyMatch(a -> a.getAuthority().equals("ROLE_ADMIN"));
        }
        return false;
    }
    
    /**
     * 检查用户是否拥有指定权限
     */
    public boolean hasPermission(Authentication auth, Long targetId, String permission) {
        String username = auth.getName();
        // 从数据库查询用户对该资源的权限
        List<String> permissions = permissionRepository
            .findByUsernameAndResourceId(username, targetId);
        return permissions.contains(permission);
    }
}

2. 使用自定义权限服务

@RestController
@RequestMapping("/api/resources")
public class ResourceController {
    
    // 1. 简单权限检查
    @PreAuthorize("@permissionService.canAccess(#id)")
    @GetMapping("/{id}")
    public Resource getResource(@PathVariable Long id) {
        return resourceService.findById(id);
    }
    
    // 2. 编辑权限检查
    @PreAuthorize("@permissionService.canEdit(#id)")
    @PutMapping("/{id}")
    public Resource updateResource(
        @PathVariable Long id,
        @RequestBody Resource resource) {
        return resourceService.update(id, resource);
    }
    
    // 3. 复杂权限检查
    @PreAuthorize("@permissionService.hasPermission(authentication, #id, 'DELETE')")
    @DeleteMapping("/{id}")
    public void deleteResource(@PathVariable Long id) {
        resourceService.delete(id);
    }
    
    // 4. 结合内置表达式
    @PreAuthorize("hasRole('ADMIN') or @permissionService.canEdit(#id)")
    @PutMapping("/{id}/publish")
    public Resource publishResource(@PathVariable Long id) {
        return resourceService.publish(id);
    }
}

3. 自定义权限评估器

/**
 * 实现 PermissionEvaluator 接口
 */
@Component
public class CustomPermissionEvaluator implements PermissionEvaluator {
    
    @Autowired
    private PermissionRepository permissionRepository;
    
    /**
     * 检查权限
     * @param authentication 认证对象
     * @param targetDomainObject 目标对象
     * @param permission 权限
     */
    @Override
    public boolean hasPermission(Authentication authentication, 
                                Object targetDomainObject, 
                                Object permission) {
        if (authentication == null || targetDomainObject == null || permission == null) {
            return false;
        }
        
        String username = authentication.getName();
        String targetType = targetDomainObject.getClass().getSimpleName();
        
        return hasPrivilege(username, targetType, permission.toString());
    }
    
    /**
     * 通过 ID 检查权限
     */
    @Override
    public boolean hasPermission(Authentication authentication, 
                                Serializable targetId, 
                                String targetType, 
                                Object permission) {
        if (authentication == null || targetId == null || permission == null) {
            return false;
        }
        
        String username = authentication.getName();
        return hasPrivilege(username, targetType, permission.toString(), targetId);
    }
    
    private boolean hasPrivilege(String username, String targetType, 
                                String permission) {
        // 查询数据库或缓存
        return permissionRepository.userHasPermission(username, targetType, permission);
    }
    
    private boolean hasPrivilege(String username, String targetType, 
                                String permission, Serializable targetId) {
        // 查询数据库或缓存
        return permissionRepository.userHasPermissionOnResource(
            username, targetType, targetId, permission);
    }
}

/**
 * 配置 PermissionEvaluator
 */
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
    
    @Autowired
    private CustomPermissionEvaluator permissionEvaluator;
    
    @Override
    protected MethodSecurityExpressionHandler createExpressionHandler() {
        DefaultMethodSecurityExpressionHandler expressionHandler = 
            new DefaultMethodSecurityExpressionHandler();
        expressionHandler.setPermissionEvaluator(permissionEvaluator);
        return expressionHandler;
    }
}

4. 使用自定义权限评估器

@RestController
@RequestMapping("/api/documents")
public class DocumentController {
    
    // 1. 检查对象权限
    @PreAuthorize("hasPermission(#document, 'WRITE')")
    @PutMapping
    public Document updateDocument(@RequestBody Document document) {
        return documentService.update(document);
    }
    
    // 2. 通过 ID 检查权限
    @PreAuthorize("hasPermission(#id, 'Document', 'DELETE')")
    @DeleteMapping("/{id}")
    public void deleteDocument(@PathVariable Long id) {
        documentService.delete(id);
    }
    
    // 3. 结合其他表达式
    @PreAuthorize("hasRole('ADMIN') or hasPermission(#id, 'Document', 'READ')")
    @GetMapping("/{id}")
    public Document getDocument(@PathVariable Long id) {
        return documentService.findById(id);
    }
}

五、编程式权限控制

1. 在代码中检查权限

@Service
public class UserService {
    
    /**
     * 方式 1: 使用 SecurityContextHolder
     */
    public void checkPermission() {
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        
        // 检查角色
        boolean isAdmin = auth.getAuthorities().stream()
            .anyMatch(a -> a.getAuthority().equals("ROLE_ADMIN"));
        
        if (!isAdmin) {
            throw new AccessDeniedException("需要管理员权限");
        }
    }
    
    /**
     * 方式 2: 注入 Authentication
     */
    public void processData(Authentication authentication) {
        String username = authentication.getName();
        
        boolean hasPermission = authentication.getAuthorities().stream()
            .anyMatch(a -> a.getAuthority().equals("DATA_PROCESS"));
        
        if (!hasPermission) {
            throw new AccessDeniedException("没有数据处理权限");
        }
    }
    
    /**
     * 方式 3: 使用 @AuthenticationPrincipal
     */
    @GetMapping("/profile")
    public Profile getProfile(
        @AuthenticationPrincipal UserDetails userDetails) {
        String username = userDetails.getUsername();
        // 处理逻辑...
    }
    
    /**
     * 方式 4: 使用 @AuthenticationPrincipal 自定义用户对象
     */
    @GetMapping("/info")
    public UserInfo getInfo(
        @AuthenticationPrincipal CustomUser user) {
        Long userId = user.getId();
        String email = user.getEmail();
        // 处理逻辑...
    }
}

2. 在 Service 层检查权限

@Service
public class DocumentService {
    
    @Autowired
    private SecurityContextHolder securityContextHolder;
    
    /**
     * 检查文档访问权限
     */
    public Document getDocument(Long id) {
        Document document = documentRepository.findById(id)
            .orElseThrow(() -> new NotFoundException("文档不存在"));
        
        // 获取当前用户
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        String currentUsername = auth.getName();
        
        // 检查权限
        if (!document.getAuthor().equals(currentUsername) 
            && !hasRole(auth, "ROLE_ADMIN")) {
            throw new AccessDeniedException("无权访问此文档");
        }
        
        return document;
    }
    
    /**
     * 检查是否有指定角色
     */
    private boolean hasRole(Authentication auth, String role) {
        return auth.getAuthorities().stream()
            .anyMatch(a -> a.getAuthority().equals(role));
    }
    
    /**
     * 检查是否有指定权限
     */
    private boolean hasAuthority(Authentication auth, String authority) {
        return auth.getAuthorities().stream()
            .anyMatch(a -> a.getAuthority().equals(authority));
    }
}

六、完整实战案例

案例 1: 多级权限系统

/**
 * 权限系统设计:
 * 1. 角色:ADMIN, MANAGER, USER
 * 2. 权限:READ, WRITE, DELETE, EXECUTE
 * 3. 资源:Document, Project, Report
 */

@RestController
@RequestMapping("/api/documents")
public class DocumentController {
    
    // 1. 公开接口
    @PermitAll
    @GetMapping("/public")
    public List<Document> getPublicDocuments() {
        return documentService.findPublicDocuments();
    }
    
    // 2. 需要登录
    @PreAuthorize("isAuthenticated()")
    @GetMapping("/my")
    public List<Document> getMyDocuments() {
        return documentService.findByCurrentUser();
    }
    
    // 3. 需要特定角色
    @PreAuthorize("hasRole('ADMIN')")
    @GetMapping("/all")
    public List<Document> getAllDocuments() {
        return documentService.findAll();
    }
    
    // 4. 需要特定权限
    @PreAuthorize("hasAuthority('DOCUMENT_READ')")
    @GetMapping("/{id}")
    public Document getDocument(@PathVariable Long id) {
        return documentService.findById(id);
    }
    
    // 5. 只能访问自己的资源
    @PreAuthorize("#userId == authentication.principal.id")
    @GetMapping("/user/{userId}")
    public List<Document> getUserDocuments(@PathVariable Long userId) {
        return documentService.findByUserId(userId);
    }
    
    // 6. 管理员或资源所有者
    @PreAuthorize("hasRole('ADMIN') or @documentService.isOwner(#id, authentication.name)")
    @PutMapping("/{id}")
    public Document updateDocument(
        @PathVariable Long id,
        @RequestBody Document document) {
        return documentService.update(id, document);
    }
    
    // 7. 复杂权限组合
    @PreAuthorize("(hasRole('MANAGER') and hasAuthority('DOCUMENT_DELETE')) or hasRole('ADMIN')")
    @DeleteMapping("/{id}")
    public void deleteDocument(@PathVariable Long id) {
        documentService.delete(id);
    }
    
    // 8. 使用自定义权限检查
    @PreAuthorize("@permissionService.canAccess(#id, 'DOCUMENT', 'WRITE')")
    @PostMapping("/{id}/publish")
    public Document publishDocument(@PathVariable Long id) {
        return documentService.publish(id);
    }
    
    // 9. 过滤返回结果
    @PostFilter("hasRole('ADMIN') or filterObject.author == authentication.name")
    @GetMapping("/draft")
    public List<Document> getDraftDocuments() {
        return documentService.findDraftDocuments();
    }
    
    // 10. 方法执行后检查
    @PostAuthorize("returnObject.isPublic or returnObject.author == authentication.name or hasRole('ADMIN')")
    @GetMapping("/{id}/detail")
    public Document getDocumentDetail(@PathVariable Long id) {
        return documentService.findByIdWithDetails(id);
    }
}

案例 2: 数据权限控制

/**
 * 数据权限服务
 */
@Service
public class DataPermissionService {
    
    @Autowired
    private UserRoleRepository userRoleRepository;
    
    @Autowired
    private DepartmentRepository departmentRepository;
    
    /**
     * 检查用户是否可以访问部门数据
     */
    public boolean canAccessDepartment(Long deptId) {
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        String username = auth.getName();
        
        // 管理员可以访问所有部门
        if (hasRole(auth, "ROLE_ADMIN")) {
            return true;
        }
        
        // 检查用户所在部门
        User user = userRepository.findByUsername(username);
        Long userDeptId = user.getDepartmentId();
        
        // 可以访问自己部门
        if (userDeptId.equals(deptId)) {
            return true;
        }
        
        // 经理可以访问下级部门
        if (hasRole(auth, "ROLE_MANAGER")) {
            return isSubDepartment(userDeptId, deptId);
        }
        
        return false;
    }
    
    /**
     * 获取用户可访问的部门列表
     */
    public List<Long> getAccessibleDepartments() {
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        String username = auth.getName();
        
        // 管理员可以访问所有部门
        if (hasRole(auth, "ROLE_ADMIN")) {
            return departmentRepository.findAll().stream()
                .map(Department::getId)
                .collect(Collectors.toList());
        }
        
        User user = userRepository.findByUsername(username);
        Long userDeptId = user.getDepartmentId();
        
        // 经理可以访问自己部门及下级部门
        if (hasRole(auth, "ROLE_MANAGER")) {
            return departmentRepository.findByParentIdOrId(userDeptId, userDeptId)
                .stream()
                .map(Department::getId)
                .collect(Collectors.toList());
        }
        
        // 普通用户只能访问自己部门
        return Collections.singletonList(userDeptId);
    }
    
    /**
     * 过滤数据(在查询时自动添加数据权限条件)
     */
    public <T> List<T> filterByDataPermission(List<T> dataList) {
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        
        // 管理员不过滤
        if (hasRole(auth, "ROLE_ADMIN")) {
            return dataList;
        }
        
        List<Long> accessibleDepts = getAccessibleDepartments();
        
        return dataList.stream()
            .filter(data -> {
                if (data instanceof DepartmentAware) {
                    DepartmentAware deptData = (DepartmentAware) data;
                    return accessibleDepts.contains(deptData.getDepartmentId());
                }
                return true;
            })
            .collect(Collectors.toList());
    }
    
    private boolean hasRole(Authentication auth, String role) {
        return auth.getAuthorities().stream()
            .anyMatch(a -> a.getAuthority().equals(role));
    }
    
    private boolean isSubDepartment(Long parentId, Long childId) {
        // 递归检查是否是下级部门
        Department dept = departmentRepository.findById(childId).orElse(null);
        if (dept == null) {
            return false;
        }
        if (dept.getParentId() == null) {
            return false;
        }
        if (dept.getParentId().equals(parentId)) {
            return true;
        }
        return isSubDepartment(parentId, dept.getParentId());
    }
}

/**
 * 使用数据权限
 */
@RestController
@RequestMapping("/api/users")
public class UserController {
    
    @Autowired
    private DataPermissionService dataPermissionService;
    
    // 只能查看有权限的部门用户
    @PreAuthorize("@dataPermissionService.canAccessDepartment(#deptId)")
    @GetMapping("/department/{deptId}")
    public List<User> getUsersByDepartment(@PathVariable Long deptId) {
        return userService.findByDepartmentId(deptId);
    }
    
    // 自动过滤返回结果
    @GetMapping("/all")
    public List<User> getAllUsers() {
        List<User> allUsers = userService.findAll();
        return dataPermissionService.filterByDataPermission(allUsers);
    }
}

案例 3: 动态权限配置

/**
 * 权限配置实体
 */
@Entity
@Table(name = "sys_permission")
public class Permission {
    @Id
    private Long id;
    
    private String resourceType;  // 资源类型:URL, BUTTON, DATA
    private String resourceCode;  // 资源编码
    private String permissionCode; // 权限编码
    private String expression;    // 权限表达式
}

/**
 * 动态权限服务
 */
@Service
public class DynamicPermissionService {
    
    @Autowired
    private PermissionRepository permissionRepository;
    
    /**
     * 检查动态权限
     */
    public boolean checkPermission(String resourceCode, String permissionCode) {
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        
        // 查询权限配置
        Permission permission = permissionRepository
            .findByResourceCodeAndPermissionCode(resourceCode, permissionCode);
        
        if (permission == null) {
            return false;
        }
        
        // 评估权限表达式
        return evaluateExpression(permission.getExpression(), auth);
    }
    
    /**
     * 评估权限表达式
     */
    private boolean evaluateExpression(String expression, Authentication auth) {
        ExpressionParser parser = new SpelExpressionParser();
        StandardEvaluationContext context = new StandardEvaluationContext();
        
        // 设置变量
        context.setVariable("authentication", auth);
        context.setVariable("username", auth.getName());
        context.setVariable("authorities", auth.getAuthorities());
        
        // 解析并评估表达式
        Expression exp = parser.parseExpression(expression);
        return Boolean.TRUE.equals(exp.getValue(context, Boolean.class));
    }
    
    /**
     * 获取用户的所有权限
     */
    public Set<String> getUserPermissions(String username) {
        // 从数据库查询用户权限
        return permissionRepository.findByUsername(username)
            .stream()
            .map(Permission::getPermissionCode)
            .collect(Collectors.toSet());
    }
}

/**
 * 使用动态权限
 */
@RestController
@RequestMapping("/api/products")
public class ProductController {
    
    @Autowired
    private DynamicPermissionService dynamicPermissionService;
    
    @GetMapping("/{id}")
    public Product getProduct(@PathVariable Long id) {
        // 动态检查权限
        if (!dynamicPermissionService.checkPermission("PRODUCT", "READ")) {
            throw new AccessDeniedException("无权查看产品");
        }
        
        return productService.findById(id);
    }
    
    @PostMapping
    public Product createProduct(@RequestBody Product product) {
        if (!dynamicPermissionService.checkPermission("PRODUCT", "CREATE")) {
            throw new AccessDeniedException("无权创建产品");
        }
        
        return productService.create(product);
    }
}

七、最佳实践建议 ✨

1. 注解选择建议

// ✅ 推荐:使用 @PreAuthorize(最灵活)
@PreAuthorize("hasRole('ADMIN') or #userId == authentication.principal.id")

// ✅ 推荐:简单场景使用 @RolesAllowed
@RolesAllowed("ADMIN")

// ⚠️ 可用:@Secured(需要 ROLE_ 前缀,不够灵活)
@Secured("ROLE_ADMIN")

// ✅ 推荐:公开接口使用 @PermitAll
@PermitAll

2. 权限粒度设计

// 粗粒度(角色级别)
@PreAuthorize("hasRole('ADMIN')")

// 中粒度(功能级别)
@PreAuthorize("hasAuthority('USER_MANAGEMENT')")

// 细粒度(操作级别)
@PreAuthorize("hasAuthority('USER_READ')")

// 数据级别(行级权限)
@PreAuthorize("@permissionService.canAccessData(#id)")

3. 性能优化

// ❌ 避免:在循环中检查权限
for (Document doc : documents) {
    if (hasPermission(doc)) {
        // 处理...
    }
}

// ✅ 推荐:批量检查或使用 @PostFilter
@PostFilter("@permissionService.canView(filterObject)")
public List<Document> getDocuments() {
    return documentService.findAll();
}

4. 错误处理

@ControllerAdvice
public class SecurityExceptionHandler {
    
    // 处理访问拒绝异常
    @ExceptionHandler(AccessDeniedException.class)
    public ResponseEntity<ErrorResponse> handleAccessDenied(
        AccessDeniedException ex) {
        return ResponseEntity
            .status(HttpStatus.FORBIDDEN)
            .body(new ErrorResponse("无权访问", ex.getMessage()));
    }
    
    // 处理认证异常
    @ExceptionHandler(AuthenticationException.class)
    public ResponseEntity<ErrorResponse> handleAuthentication(
        AuthenticationException ex) {
        return ResponseEntity
            .status(HttpStatus.UNAUTHORIZED)
            .body(new ErrorResponse("认证失败", ex.getMessage()));
    }
}

八、总结对比表

注解来源灵活性使用场景推荐指数
@PreAuthorizeSpring Security⭐⭐⭐⭐⭐复杂权限控制⭐⭐⭐⭐⭐
@PostAuthorizeSpring Security⭐⭐⭐⭐返回值检查⭐⭐⭐⭐
@PreFilterSpring Security⭐⭐⭐参数过滤⭐⭐⭐
@PostFilterSpring Security⭐⭐⭐⭐结果过滤⭐⭐⭐⭐
@RolesAllowedJSR-250⭐⭐简单角色控制⭐⭐⭐⭐
@PermitAllJSR-250公开接口⭐⭐⭐⭐⭐
@DenyAllJSR-250禁止访问⭐⭐
@SecuredSpring Security⭐⭐简单角色控制⭐⭐
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐