Spring Security 6 【8-权限注解】

Spring Security 6 权限注解全面指南

下面我将详细介绍 Spring Security 6 中的各种权限注解,并提供完整的代码实现,涵盖方法级安全、表达式使用、自定义权限评估器等核心功能。

一、权限注解概览

注解描述使用场景
@PreAuthorize方法调用前进行权限检查最常用,支持SpEL表达式
@PostAuthorize方法调用后进行权限检查访问控制依赖返回结果
@Secured基于角色/权限的简单检查简单权限控制
@RolesAllowedJSR-250标准角色检查兼容Java EE标准
@PreFilter方法调用前过滤集合参数数据级权限控制
@PostFilter方法调用后过滤返回结果数据级权限控制
@EnableMethodSecurity启用方法级安全配置类必须注解

二、完整代码实现

1. 启用方法级安全

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;

@Configuration
@EnableMethodSecurity(
    prePostEnabled = true,   // 启用@PreAuthorize/@PostAuthorize
    securedEnabled = true,   // 启用@Secured
    jsr250Enabled = true     // 启用@RolesAllowed
)
public class MethodSecurityConfig {
    // 其他配置...
}

2. 服务层权限控制

import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.access.prepost.PostAuthorize;
import org.springframework.security.access.prepost.PreFilter;
import org.springframework.security.access.prepost.PostFilter;
import org.springframework.stereotype.Service;
import java.util.List;

@Service
public class ProductService {

    // 1. 基于角色的访问控制
    @PreAuthorize("hasRole('ADMIN')")
    public void deleteProduct(Long id) {
        // 只有管理员可以删除产品
    }

    // 2. 基于权限的访问控制
    @PreAuthorize("hasAuthority('PRODUCT_READ')")
    public Product getProduct(Long id) {
        // 需要PRODUCT_READ权限
        return productRepository.findById(id).orElseThrow();
    }

    // 3. 自定义权限表达式
    @PreAuthorize("hasPermission(#id, 'Product', 'read')")
    public Product getProductDetails(Long id) {
        // 自定义权限检查
        return productRepository.findDetailsById(id);
    }

    // 4. 基于返回对象的访问控制
    @PostAuthorize("returnObject.owner == authentication.name")
    public Product getMyProduct(Long id) {
        // 只能访问自己的产品
        return productRepository.findById(id).orElseThrow();
    }

    // 5. 参数过滤(输入)
    @PreFilter("hasPermission(filterObject, 'Product', 'edit')")
    public void updateProducts(List<Product> products) {
        // 只能编辑有权限的产品
        products.forEach(productRepository::save);
    }

    // 6. 结果过滤(输出)
    @PostFilter("hasPermission(filterObject, 'Product', 'read')")
    public List<Product> getAllProducts() {
        // 只返回有读取权限的产品
        return productRepository.findAll();
    }

    // 7. 多条件组合
    @PreAuthorize("hasAuthority('PRODUCT_MANAGE') or hasRole('ADMIN')")
    public void manageProduct(Product product) {
        // 需要管理权限或管理员角色
        productRepository.save(product);
    }

    // 8. 基于时间的访问控制
    @PreAuthorize("@businessHoursService.isBusinessHours()")
    public void placeOrder(Order order) {
        // 只能在营业时间下单
        orderRepository.save(order);
    }
}

3. 控制器层权限控制

import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/products")
public class ProductController {

    // 1. 简单角色检查
    @PreAuthorize("hasRole('USER')")
    @GetMapping
    public List<Product> listProducts() {
        return productService.getAllProducts();
    }

    // 2. 多角色检查
    @PreAuthorize("hasAnyRole('ADMIN', 'SUPERVISOR')")
    @PostMapping
    public Product createProduct(@RequestBody Product product) {
        return productService.createProduct(product);
    }

    // 3. 权限与参数结合
    @PreAuthorize("hasAuthority('PRODUCT_DELETE') and #userId == authentication.principal.id")
    @DeleteMapping("/{id}")
    public void deleteProduct(@PathVariable Long id, @RequestParam Long userId) {
        // 需要删除权限且用户ID匹配
        productService.deleteProduct(id);
    }

    // 4. 自定义表达式
    @PreAuthorize("@permissionEvaluator.hasAccess(#id, 'Product', 'edit')")
    @PutMapping("/{id}")
    public Product updateProduct(@PathVariable Long id, @RequestBody Product product) {
        return productService.updateProduct(id, product);
    }
}

4. 自定义权限评估器

import org.springframework.security.access.PermissionEvaluator;
import org.springframework.security.core.Authentication;
import java.io.Serializable;

@Component
public class CustomPermissionEvaluator implements PermissionEvaluator {

    private final ProductRepository productRepository;
    private final UserRepository userRepository;

    public CustomPermissionEvaluator(ProductRepository pr, UserRepository ur) {
        this.productRepository = pr;
        this.userRepository = ur;
    }

    @Override
    public boolean hasPermission(Authentication authentication, 
                                Object targetDomainObject, 
                                Object permission) {
        // 1. 基于对象实例的权限检查
        if (targetDomainObject instanceof Product product) {
            return checkProductAccess(authentication, product, (String) permission);
        }
        return false;
    }

    @Override
    public boolean hasPermission(Authentication authentication, 
                                Serializable targetId, 
                                String targetType, 
                                Object permission) {
        // 2. 基于对象ID和类型的权限检查
        if ("Product".equalsIgnoreCase(targetType)) {
            Product product = productRepository.findById((Long) targetId).orElseThrow();
            return checkProductAccess(authentication, product, (String) permission);
        }
        return false;
    }

    private boolean checkProductAccess(Authentication auth, Product product, String permission) {
        String username = auth.getName();
        User user = userRepository.findByUsername(username).orElseThrow();
        
        switch (permission) {
            case "read":
                // 所有用户可读公开产品,或自己的私有产品
                return product.isPublic() || product.getOwnerId().equals(user.getId());
                
            case "edit":
                // 产品所有者或管理员可编辑
                return product.getOwnerId().equals(user.getId()) || 
                       auth.getAuthorities().stream()
                           .anyMatch(g -> g.getAuthority().equals("ROLE_ADMIN"));
                           
            case "delete":
                // 仅管理员可删除
                return auth.getAuthorities().stream()
                           .anyMatch(g -> g.getAuthority().equals("ROLE_ADMIN"));
                           
            default:
                return false;
        }
    }
}

5. 配置自定义权限评估器

import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;

@Configuration
@EnableMethodSecurity
public class MethodSecurityConfig {

    private final CustomPermissionEvaluator permissionEvaluator;

    public MethodSecurityConfig(CustomPermissionEvaluator pe) {
        this.permissionEvaluator = pe;
    }

    @Bean
    public MethodSecurityExpressionHandler methodSecurityExpressionHandler() {
        DefaultMethodSecurityExpressionHandler handler = 
            new DefaultMethodSecurityExpressionHandler();
        handler.setPermissionEvaluator(permissionEvaluator);
        return handler;
    }
}

6. 自定义安全表达式根

import org.springframework.security.access.expression.SecurityExpressionRoot;
import org.springframework.security.core.Authentication;

public class CustomSecurityExpressionRoot extends SecurityExpressionRoot {

    private final Object target;
    
    public CustomSecurityExpressionRoot(Authentication a, Object target) {
        super(a);
        this.target = target;
    }
    
    // 自定义表达式方法
    public boolean isProductOwner() {
        if (target instanceof Product product) {
            return product.getOwnerId().equals(authentication.getName());
        }
        return false;
    }
    
    public boolean isDepartmentManager(String department) {
        User user = (User) authentication.getPrincipal();
        return user.getManagedDepartments().contains(department);
    }
}

// 注册自定义表达式根
@Bean
public MethodSecurityExpressionHandler methodSecurityExpressionHandler() {
    DefaultMethodSecurityExpressionHandler handler = 
        new DefaultMethodSecurityExpressionHandler() {
            @Override
            protected SecurityExpressionRoot createSecurityExpressionRoot(
                Authentication authentication, MethodInvocation invocation) {
                
                CustomSecurityExpressionRoot root = new CustomSecurityExpressionRoot(
                    authentication, invocation.getThis()
                );
                root.setPermissionEvaluator(getPermissionEvaluator());
                return root;
            }
        };
    handler.setPermissionEvaluator(permissionEvaluator);
    return handler;
}

// 使用自定义表达式
@PreAuthorize("isProductOwner()")
public void updateProduct(Product product) {
    // 只有产品所有者可以更新
}

三、高级权限控制场景

1. 多租户数据隔离

public class TenantAwarePermissionEvaluator implements PermissionEvaluator {

    @Override
    public boolean hasPermission(Authentication auth, 
                                Object target, 
                                Object permission) {
        User user = (User) auth.getPrincipal();
        
        if (target instanceof Product product) {
            // 检查产品是否属于用户所在租户
            return product.getTenantId().equals(user.getTenantId());
        }
        return false;
    }
}

// 在服务层使用
@PostFilter("hasPermission(filterObject, 'read')")
public List<Product> getAllProducts() {
    return productRepository.findAll();
}

2. 基于时间的访问控制

@Service
public class BusinessHoursService {

    public boolean isBusinessHours() {
        LocalTime now = LocalTime.now();
        return now.isAfter(LocalTime.of(9, 0)) && 
               now.isBefore(LocalTime.of(18, 0));
    }
}

// 在服务层使用
@PreAuthorize("@businessHoursService.isBusinessHours()")
public void placeOrder(Order order) {
    // 只能在营业时间下单
}

3. 动态权限控制

@RestController
public class PermissionController {

    @PreAuthorize("hasPermission(#request, 'dynamic')")
    @PostMapping("/execute")
    public ResponseEntity<?> executeOperation(@RequestBody OperationRequest request) {
        // 动态权限检查
        return ResponseEntity.ok(operationService.execute(request));
    }
}

// 自定义评估器中的动态权限检查
public boolean hasPermission(Authentication auth, OperationRequest request, String permission) {
    User user = (User) auth.getPrincipal();
    return user.getPermissions().stream()
        .anyMatch(p -> p.matchesOperation(request.getOperationType()));
}

4. 审计日志集成

@Aspect
@Component
public class SecurityAuditAspect {

    @Before("@annotation(org.springframework.security.access.prepost.PreAuthorize)")
    public void auditPreAuthorize(JoinPoint joinPoint) {
        String method = joinPoint.getSignature().toShortString();
        String user = SecurityContextHolder.getContext().getAuthentication().getName();
        System.out.printf("Access attempt by %s to %s%n", user, method);
    }

    @AfterThrowing(
        pointcut = "@annotation(org.springframework.security.access.prepost.PreAuthorize)",
        throwing = "ex"
    )
    public void auditAccessDenied(JoinPoint joinPoint, AccessDeniedException ex) {
        String method = joinPoint.getSignature().toShortString();
        String user = SecurityContextHolder.getContext().getAuthentication().getName();
        System.out.printf("Access DENIED for %s to %s: %s%n", user, method, ex.getMessage());
    }
}

四、权限注解最佳实践

1. 权限分层策略

层级推荐注解示例
控制器层@PreAuthorize粗粒度访问控制
服务层@PreAuthorize + @PostFilter业务逻辑级控制
领域层自定义权限评估器数据级访问控制
数据访问层一般不使用避免过度耦合

2. 表达式优化技巧

// 1. 避免重复计算
@PreAuthorize("@permissionCache.hasAccess(#id, 'Product')")
public Product getProduct(Long id) {
    // 使用缓存提高性能
}

// 2. 使用类型安全的方法引用
@PreAuthorize("hasPermission(#id, T(com.example.model.Product).class, 'read')")
public Product getProduct(Long id) {
    // 类型安全的方式引用类
}

// 3. 组合表达式
@PreAuthorize("(hasRole('ADMIN') and @systemStatus.isMaintenanceMode() == false) " +
              "or hasAuthority('BYPASS_MAINTENANCE')")
public void performAdminAction() {
    // 管理员在非维护模式或有绕过权限时可操作
}

// 4. 自定义快捷方法
@PreAuthorize("isProductOwner(#productId)")
public void updateProduct(Long productId) {
    // 使用自定义表达式简化
}

3. 安全配置建议

@Configuration
@EnableMethodSecurity
public class MethodSecurityConfig {

    @Bean
    public MethodSecurityExpressionHandler methodSecurityExpressionHandler(
        CustomPermissionEvaluator permissionEvaluator) {
        
        DefaultMethodSecurityExpressionHandler handler = 
            new DefaultMethodSecurityExpressionHandler();
        
        // 1. 设置自定义权限评估器
        handler.setPermissionEvaluator(permissionEvaluator);
        
        // 2. 注册自定义函数
        handler.setRoleHierarchy(roleHierarchy());
        
        // 3. 设置安全上下文策略
        handler.setTrustResolver(new AuthenticationTrustResolverImpl());
        
        return handler;
    }
    
    @Bean
    public RoleHierarchy roleHierarchy() {
        String hierarchy = 
            "ROLE_ADMIN > ROLE_MANAGER\n" +
            "ROLE_MANAGER > ROLE_SUPERVISOR\n" +
            "ROLE_SUPERVISOR > ROLE_USER";
        return new RoleHierarchyImpl(hierarchy);
    }
}

4. 测试策略

@SpringBootTest
@WithMockUser(
    username = "testUser",
    roles = {"USER"},
    authorities = {"PRODUCT_READ"}
)
class ProductServiceTest {

    @Autowired
    private ProductService productService;

    @Test
    void testGetProductWithAccess() {
        Product product = productService.getProduct(1L);
        assertNotNull(product);
    }

    @Test
    @WithMockUser(
        username = "testUser",
        roles = {"USER"},
        authorities = {"PRODUCT_READ"}
    )
    void testGetProductWithoutAccess() {
        // 测试权限不足的情况
        assertThrows(AccessDeniedException.class, () -> {
            productService.deleteProduct(1L);
        });
    }

    @Test
    @WithMockUser(
        username = "productOwner",
        authorities = {"PRODUCT_EDIT"}
    )
    void testUpdateOwnProduct() {
        Product product = new Product(1L, "productOwner");
        assertDoesNotThrow(() -> productService.updateProduct(product));
    }
}

五、权限模型设计建议

1. RBAC (基于角色的访问控制)

// 角色权限分配
@PreAuthorize("hasRole('PRODUCT_MANAGER')")
public void manageProducts() {
    // 产品经理角色可访问
}

// 角色继承配置
@Bean
public RoleHierarchy roleHierarchy() {
    String hierarchy = 
        "ROLE_ADMIN > ROLE_MANAGER\n" +
        "ROLE_MANAGER > ROLE_USER";
    return new RoleHierarchyImpl(hierarchy);
}

2. ABAC (基于属性的访问控制)

// 使用自定义评估器
public boolean hasPermission(Authentication auth, Product product, String permission) {
    User user = (User) auth.getPrincipal();
    
    // 基于属性判断
    return product.getDepartment().equals(user.getDepartment()) &&
           product.getSensitivityLevel() <= user.getClearanceLevel();
}

// 在服务层使用
@PreAuthorize("hasPermission(#product, 'edit')")
public void updateProduct(Product product) {
    // 基于属性的访问控制
}

3. PBAC (基于策略的访问控制)

@Service
public class PolicyAccessService {

    private final PolicyEngine policyEngine;
    
    public boolean checkAccess(User user, Object resource, String action) {
        AccessRequest request = new AccessRequest(user, resource, action);
        return policyEngine.evaluate(request).isAllowed();
    }
}

// 在权限评估器中使用
public boolean hasPermission(Authentication auth, Object resource, String action) {
    User user = (User) auth.getPrincipal();
    return policyAccessService.checkAccess(user, resource, action);
}

六、生产环境注意事项

1. 性能优化

// 1. 权限缓存
@Cacheable(value = "permissions", key = "{#userId, #resource, #action}")
public boolean hasPermission(Long userId, String resource, String action) {
    // 昂贵的权限检查逻辑
}

// 2. 批量权限检查
@PostFilter("@permissionService.hasBatchAccess(authentication, filterObject, 'read')")
public List<Product> getAllProducts() {
    // 批量检查权限
}

// 3. 异步权限验证
@Async
public CompletableFuture<Boolean> checkAccessAsync(User user, Resource resource) {
    // 异步执行复杂的权限检查
}

2. 安全审计

@Configuration
@EnableMethodSecurity
public class MethodSecurityConfig {

    @Bean
    public MethodSecurityExpressionHandler methodSecurityExpressionHandler() {
        DefaultMethodSecurityExpressionHandler handler = 
            new DefaultMethodSecurityExpressionHandler();
        
        // 添加审计拦截器
        handler.setMethodSecurityMetadataSources(
            new DelegatingMethodSecurityMetadataSources(
                new PrePostAnnotationSecurityMetadataSource(
                    new ExpressionBasedAnnotationAttributeFactory(handler)
                ),
                new AuditMethodSecurityMetadataSource()
            )
        );
        
        return handler;
    }
}

// 自定义元数据源记录审计
public class AuditMethodSecurityMetadataSource 
    extends AbstractMethodSecurityMetadataSource {
    
    @Override
    public Collection<ConfigAttribute> getAttributes(Method method, Class<?> targetClass) {
        // 记录方法安全配置
        auditService.logMethodSecurityConfig(method, targetClass);
        return null; // 不修改实际配置
    }
}

3. 动态权限更新

@RestController
public class PermissionAdminController {

    @PreAuthorize("hasRole('PERMISSION_ADMIN')")
    @PostMapping("/permissions/refresh")
    public ResponseEntity<?> refreshPermissions() {
        // 刷新权限缓存
        permissionEvaluator.refreshCache();
        return ResponseEntity.ok("Permissions refreshed");
    }
}

// 在权限评估器中实现
@Component
public class RefreshablePermissionEvaluator implements PermissionEvaluator {
    
    private volatile PermissionCache permissionCache;
    
    public void refreshCache() {
        // 重新加载权限数据
        this.permissionCache = loadPermissions();
    }
    
    @Override
    public boolean hasPermission(Authentication auth, Object target, Object permission) {
        return permissionCache.checkAccess(auth, target, permission);
    }
}

七、总结与最佳实践

1. 权限注解选择指南

场景推荐注解示例
简单角色检查@Secured@Secured("ROLE_ADMIN")
复杂表达式@PreAuthorize@PreAuthorize("hasRole('ADMIN') and #user.id == principal.id")
结果过滤@PostFilter@PostFilter("hasPermission(filterObject, 'read')")
JEE兼容@RolesAllowed@RolesAllowed({"USER", "ADMIN"})
返回对象检查@PostAuthorize@PostAuthorize("returnObject.owner == principal.username")

2. 最佳实践原则

  1. 最小权限原则

    // 精确到具体操作
    @PreAuthorize("hasAuthority('PRODUCT_EDIT')")
    
  2. 分层控制

    • 控制器层:基本角色检查
    • 服务层:业务逻辑权限
    • 数据层:数据所有权验证
  3. 防御性编程

    @PostAuthorize("returnObject != null ? returnObject.owner == principal.name : true")
    public Product getProduct(Long id) {
        // 处理可能的null返回值
    }
    
  4. 避免过度使用

    • 不要在DAO层使用安全注解
    • 避免在领域模型中使用安全依赖
  5. 性能考虑

    • 对频繁调用的方法添加权限缓存
    • 批量数据使用批量权限检查
    • 避免在权限表达式中进行复杂计算

3. 安全审计建议

// 在权限评估器中添加审计
public boolean hasPermission(Authentication auth, Object target, Object permission) {
    boolean result = // 实际权限检查逻辑
    
    // 记录审计日志
    auditService.logAccessAttempt(
        auth.getName(), 
        target, 
        permission, 
        result
    );
    
    return result;
}

通过合理使用 Spring Security 6 的权限注解,您可以实现从 URL 级别到方法级别再到数据级别的精细化访问控制。关键点包括:

  1. 灵活的选择

    • 根据场景选择合适的注解
    • 结合使用多种注解实现多层防护
  2. 强大的扩展

    • 自定义权限评估器
    • 自定义安全表达式
    • 集成外部权限服务
  3. 生产级特性

    • 权限缓存优化
    • 动态权限更新
    • 全面的审计跟踪

这些实践已在大型企业应用中验证,可支持复杂的权限管理需求,同时保持系统的安全性和可维护性。

Spring Security 是一个开源的安全框架,用于保护基于Spring框架构建的应用程序。MyBatis-Plus 是基于 MyBatis 的增强工具,用于简化 MyBatis 操作和提高开发效率。 将 Spring Security 与 MyBatis-Plus 整合主要包括以下几个步骤: 1. 首先,在 Spring Boot 项目的 pom.xml 文件中添加 Spring Security 和 MyBatis-Plus 的依赖。 ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>latest version</version> </dependency> ``` 2. 接下来,在项目的配置文件 application.properties 或 application.yml 中配置数据库连接和 MyBatis-Plus 的相关配置。 3. 创建一个用户实体类和对应的 Mapper 接口,使用 MyBatis-Plus 提供的注解和方法完成数据访问操作。 4. 创建一个自定义的 UserDetailsService 实现类,用于从数据库中获取用户信息,实现 loadUserByUsername 方法。 5. 创建一个自定义的 PasswordEncoder 实现类,用于加密和校验用户密码。 6. 配置 Spring Security,创建一个继承自 WebSecurityConfigurerAdapter 的类,并重写 configure 方法,设置用户认证规则、登录配置和访问控制规则。 7. 在 Spring Boot 启动类上使用 @MapperScan 注解,扫描 Mapper 接口。 通过以上步骤,就可以实现 Spring Security 与 MyBatis-Plus 的整合。在实际应用中,还可以根据具体需求进行一些其他配置和扩展,例如添加角色权限控制、自定义登录页面和处理逻辑等。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值