Spring Security 6 权限注解全面指南
下面我将详细介绍 Spring Security 6 中的各种权限注解,并提供完整的代码实现,涵盖方法级安全、表达式使用、自定义权限评估器等核心功能。
一、权限注解概览
| 注解 | 描述 | 使用场景 |
|---|---|---|
@PreAuthorize | 方法调用前进行权限检查 | 最常用,支持SpEL表达式 |
@PostAuthorize | 方法调用后进行权限检查 | 访问控制依赖返回结果 |
@Secured | 基于角色/权限的简单检查 | 简单权限控制 |
@RolesAllowed | JSR-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. 最佳实践原则
-
最小权限原则:
// 精确到具体操作 @PreAuthorize("hasAuthority('PRODUCT_EDIT')") -
分层控制:
- 控制器层:基本角色检查
- 服务层:业务逻辑权限
- 数据层:数据所有权验证
-
防御性编程:
@PostAuthorize("returnObject != null ? returnObject.owner == principal.name : true") public Product getProduct(Long id) { // 处理可能的null返回值 } -
避免过度使用:
- 不要在DAO层使用安全注解
- 避免在领域模型中使用安全依赖
-
性能考虑:
- 对频繁调用的方法添加权限缓存
- 批量数据使用批量权限检查
- 避免在权限表达式中进行复杂计算
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 级别到方法级别再到数据级别的精细化访问控制。关键点包括:
-
灵活的选择:
- 根据场景选择合适的注解
- 结合使用多种注解实现多层防护
-
强大的扩展:
- 自定义权限评估器
- 自定义安全表达式
- 集成外部权限服务
-
生产级特性:
- 权限缓存优化
- 动态权限更新
- 全面的审计跟踪
这些实践已在大型企业应用中验证,可支持复杂的权限管理需求,同时保持系统的安全性和可维护性。
3777

被折叠的 条评论
为什么被折叠?



