Spring Security 注解使用详解
在 Spring Boot 项目中,Spring Security 提供了丰富的注解来简化权限控制配置,无需大量 XML 或 Java 配置类即可实现精细的权限管理。使用前需先开启注解支持,再根据业务场景选择合适的注解进行权限控制。以下是完整的注解使用指南,包含开启方式、核心注解详解、使用场景及注意事项。
开启 Spring Security 注解支持
所有 Spring Security 方法级注解都需要通过 @EnableGlobalMethodSecurity
注解开启(Spring Security 5.6+ 推荐使用 @EnableMethodSecurity
,功能更强大且支持 Lambda 表达式)。
传统方式:@EnableGlobalMethodSecurity
(适用于旧版本)
该注解用于开启全局方法级安全控制,通过属性启用不同类型的注解,需添加在配置类上(如 SecurityConfig
)。
1 | import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; |
推荐方式:@EnableMethodSecurity
(Spring Security 5.6+)
@EnableMethodSecurity
是 @EnableGlobalMethodSecurity
的替代方案,默认开启 prePostEnabled = true
,支持更简洁的配置和 Lambda 表达式,推荐在新版本中使用。
1 | import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; |
核心注解详解
Spring Security 注解主要分为权限校验注解(控制方法是否可访问)和数据过滤注解(过滤方法的入参 / 返回值),以下是常用注解的用法、场景及示例。
权限校验注解
1. @Secured
:基于角色的简单权限控制
功能:判断当前用户是否拥有指定 “角色”,仅允许拥有对应角色的用户访问方法。
特点:
- 角色必须以
ROLE_
为前缀(如ROLE_admin
、ROLE_student
),否则校验不生效; - 支持多个角色(用数组表示),满足任一角色即可访问;
- 仅支持 “角色” 判断,不支持复杂表达式(如逻辑运算、权限判断)。
- 角色必须以
使用示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14import org.springframework.security.access.annotation.Secured;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
public class TestController {
// 仅允许拥有 ROLE_student 或 ROLE_admin 角色的用户访问
public String testSecured() {
System.out.println("=== @Secured 注解生效 ===");
return "Secured 访问成功";
}
}
2. @PreAuthorize
:方法执行前的复杂权限校验
功能:在方法执行之前进行权限校验,支持 SpEL 表达式(如逻辑运算、权限判断、动态参数引用),灵活性极高。
适用场景:需要复杂权限逻辑的场景(如 “用户拥有 admin 权限 或 是资源的创建者”)。
常用 SpEL 表达式:
hasAuthority('admin')
:判断用户是否拥有admin
权限(无ROLE_
前缀);hasRole('admin')
:判断用户是否拥有ROLE_admin
角色(自动拼接ROLE_
前缀);principal
:引用当前认证用户的UserDetails
对象(如principal.username
获取用户名);#paramName
:引用方法的参数(如#userId == principal.id
判断参数与用户 ID 一致)。
使用示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
public class TestController {
// 1. 简单权限校验:拥有 admin 权限即可访问
public String testPre1() {
System.out.println("=== @PreAuthorize 简单权限校验 ===");
return "PreAuthorize 访问成功(admin 权限)";
}
// 2. 复杂逻辑:拥有 admin 权限 或 是当前资源的创建者(参数 #userId 与用户ID一致)
public String testPre2( Long userId){
System.out.println("=== @PreAuthorize 复杂逻辑校验 ===");
return "PreAuthorize 访问成功(用户ID:" + userId + ")";
}
}
3. @PostAuthorize
:方法执行后的权限校验
功能:在方法执行之后进行权限校验,方法会正常执行(即使权限不满足),但校验失败时会抛出
AccessDeniedException
,且不会返回结果。适用场景:需要根据方法的返回值判断权限的场景(如 “用户只能查看自己创建的订单”,需先查询订单再校验归属)。
注意:方法执行后才校验,若方法涉及写操作(如插入数据库),即使校验失败,写操作也已完成,需谨慎使用(建议优先用
@PreAuthorize
)。使用示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24import org.springframework.security.access.prepost.PostAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
public class OrderController {
// 模拟查询订单:方法执行后,校验订单的创建者是否为当前用户(返回值.orderCreator == 用户名)
public OrderDTO getOrder( Long orderId){
// 1. 先执行查询逻辑(即使权限不满足,也会执行)
OrderDTO order = orderService.getOrderById(orderId);
System.out.println("=== 订单查询完成,开始 PostAuthorize 校验 ===");
return order; // 2. 校验返回值:若 orderCreator 不是当前用户,抛出异常
}
// 订单DTO类(模拟)
public static class OrderDTO {
private Long orderId;
private String orderCreator; // 订单创建者(用户名)
// getter/setter
}
}
4. JSR-250 注解(@RolesAllowed
、@PermitAll
、@DenyAll
)
功能:Java 标准的权限注解(JSR-250 规范),需开启
jsr250Enabled = true
,兼容性强(非 Spring 专属)。常用注解:
@RolesAllowed({"ADMIN", "USER"})
:类似@Secured
,判断用户是否拥有指定角色(无需ROLE_
前缀,Spring 自动拼接);@PermitAll
:允许所有用户访问(包括未认证用户);@DenyAll
:拒绝所有用户访问(无论权限如何)。
使用示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21import javax.annotation.security.PermitAll;
import javax.annotation.security.RolesAllowed;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
public class TestController {
// 允许所有用户访问(包括未登录)
public String testPermitAll() {
return "所有用户可访问";
}
// 仅允许 ADMIN 或 USER 角色访问(自动拼接 ROLE_,即 ROLE_ADMIN、ROLE_USER)
public String testRolesAllowed() {
return "ADMIN/USER 角色可访问";
}
}
数据过滤注解
1. @PreFilter
:方法执行前过滤入参集合
功能:在方法执行前,对集合类型的入参(如
List
、Set
)进行过滤,只保留满足条件的元素。适用场景:需要限制方法入参范围的场景(如 “用户只能操作自己的资源 ID 列表”)。
关键参数:
value
:SpEL 表达式,判断元素是否保留(true
保留,false
过滤);filterTarget
:若方法有多个集合参数,指定要过滤的参数名(如filterTarget = "ids"
)。
使用示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17import org.springframework.security.access.prepost.PreFilter;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
public class ResourceController {
// 过滤入参:仅保留资源ID属于当前用户的元素(#element 代表集合中的每个元素)
public String batchDelete( List<Long> resourceIds){
// 此时 resourceIds 已过滤,仅包含当前用户有权删除的资源ID
resourceService.deleteByIds(resourceIds);
return "删除成功,共删除 " + resourceIds.size() + " 个资源";
}
}
2. @PostFilter
:方法执行后过滤返回值集合
功能:在方法执行后,对集合类型的返回值(如
List
、Set
)进行过滤,只返回满足条件的元素。适用场景:需要限制返回数据范围的场景(如 “用户只能查看自己的订单列表”)。
使用示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17import org.springframework.security.access.prepost.PostFilter;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
public class OrderController {
// 过滤返回值:仅返回当前用户创建的订单(returnObject 代表返回集合,#element 代表集合元素)
public List<OrderDTO> getOrderList() {
// 1. 先查询所有订单(即使包含其他用户的订单)
List<OrderDTO> allOrders = orderService.getAllOrders();
// 2. 过滤后返回:仅保留当前用户的订单
return allOrders;
}
}
注解优先级与注意事项
注解优先级
当一个方法同时添加多个权限注解时,优先级遵循:@DenyAll
(最高) > @PreAuthorize
/@PostAuthorize
> @Secured
/@RolesAllowed
> @PermitAll
(最低)
关键注意事项
- 角色与权限的区别:
hasRole('admin')
等价于hasAuthority('ROLE_admin')
(自动拼接ROLE_
前缀);@Secured
和@RolesAllowed
基于 “角色”,@PreAuthorize
可同时支持 “角色” 和 “权限”(更灵活)。
@PostAuthorize
的风险:- 方法会先执行(如写库、调用第三方接口),若校验失败,已执行的操作无法回滚,建议优先使用
@PreAuthorize
。
- 方法会先执行(如写库、调用第三方接口),若校验失败,已执行的操作无法回滚,建议优先使用
- 数据过滤注解的限制:
@PreFilter
/@PostFilter
仅对集合类型(Collection
子类)有效,对数组、Map 或单个对象无效;- 过滤逻辑基于内存(先查询所有数据再过滤),若数据量过大,会导致性能问题,建议优先通过 SQL 条件过滤数据。
- SpEL 表达式的灵活使用:
- 可引用
principal
(当前用户)、request
(HttpServletRequest)、方法参数(#paramName
)等对象; - 可调用自定义方法(如
@PreAuthorize("@userService.isOwner(#resourceId, principal.id)")
,需确保userService
是 Spring Bean)。
- 可引用
常见问题与解决方案
问题现象 | 可能原因 | 解决方案 |
---|---|---|
@Secured 注解不生效 |
1. 未开启 securedEnabled = true ; 2. 角色未加 ROLE_ 前缀 |
1. 在 @EnableMethodSecurity 中开启 securedEnabled = true ; 2. 角色名添加 ROLE_ 前缀(如 ROLE_admin ) |
@PreAuthorize 中 hasRole 不生效 |
角色名重复添加 ROLE_ 前缀(如 hasRole("ROLE_admin") ) |
去掉前缀,改为 hasRole("admin") (自动拼接 ROLE_ ) |
@PostFilter 未过滤返回值 |
1. 返回值不是 Collection 类型; 2. SpEL 表达式错误 |
1. 确保返回值是 List /Set 等集合; 2. 检查表达式(如 returnObject 改为 filterTarget ,Spring 5.2+ 推荐用 filterTarget ) |
方法级注解优先级低于 URL 级配置 | Spring Security 先执行 URL 级拦截,再执行方法级拦截 | 1. 确保 URL 级配置允许访问(如不拦截该路径); 2. 方法级注解是更细粒度的控制,建议 URL 级配置宽松,方法级配置严格 |
通过以上注解,可快速实现 Spring Boot 项目中的方法级权限控制,结合 @PreAuthorize
和 SpEL 表达式,能满足绝大多数复杂的权限场景
v1.3.10