0%

springSecurity注解使用

Spring Security 注解使用详解

在 Spring Boot 项目中,Spring Security 提供了丰富的注解来简化权限控制配置,无需大量 XML 或 Java 配置类即可实现精细的权限管理。使用前需先开启注解支持,再根据业务场景选择合适的注解进行权限控制。以下是完整的注解使用指南,包含开启方式、核心注解详解、使用场景及注意事项。

开启 Spring Security 注解支持

所有 Spring Security 方法级注解都需要通过 @EnableGlobalMethodSecurity 注解开启(Spring Security 5.6+ 推荐使用 @EnableMethodSecurity,功能更强大且支持 Lambda 表达式)。

传统方式:@EnableGlobalMethodSecurity(适用于旧版本)

该注解用于开启全局方法级安全控制,通过属性启用不同类型的注解,需添加在配置类上(如 SecurityConfig)。

1
2
3
4
5
6
7
8
9
10
11
12
13
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;

// 开启 Web 安全 + 全局方法级安全注解
@EnableWebSecurity
@EnableGlobalMethodSecurity(
securedEnabled = true, // 开启 @Secured 注解支持
prePostEnabled = true, // 开启 @PreAuthorize/@PostAuthorize 注解支持
jsr250Enabled = true // 开启 JSR-250 注解(如 @RolesAllowed、@PermitAll)
)
public class SecurityConfig {
// 其他 Security 配置(如密码编码器、认证管理器等)
}

推荐方式:@EnableMethodSecurity(Spring Security 5.6+)

@EnableMethodSecurity@EnableGlobalMethodSecurity 的替代方案,默认开启 prePostEnabled = true,支持更简洁的配置和 Lambda 表达式,推荐在新版本中使用。

1
2
3
4
5
6
7
8
9
10
11
12
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;

@EnableWebSecurity
// 默认开启 @PreAuthorize/@PostAuthorize,如需其他注解需显式配置
@EnableMethodSecurity(
securedEnabled = true, // 开启 @Secured
jsr250Enabled = true // 开启 JSR-250 注解
)
public class SecurityConfig {
// 其他配置...
}

核心注解详解

Spring Security 注解主要分为权限校验注解(控制方法是否可访问)和数据过滤注解(过滤方法的入参 / 返回值),以下是常用注解的用法、场景及示例。

权限校验注解

1. @Secured:基于角色的简单权限控制
  • 功能:判断当前用户是否拥有指定 “角色”,仅允许拥有对应角色的用户访问方法。

  • 特点:

    • 角色必须以 ROLE_ 为前缀(如 ROLE_adminROLE_student),否则校验不生效;
    • 支持多个角色(用数组表示),满足任一角色即可访问;
    • 仅支持 “角色” 判断,不支持复杂表达式(如逻辑运算、权限判断)。
  • 使用示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    import org.springframework.security.access.annotation.Secured;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;

    @RestController
    public class TestController {
    // 仅允许拥有 ROLE_student 或 ROLE_admin 角色的用户访问
    @Secured({"ROLE_student", "ROLE_admin"})
    @GetMapping("/test/secured")
    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
    23
    import 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;

    @RestController
    public class TestController {
    // 1. 简单权限校验:拥有 admin 权限即可访问
    @PreAuthorize("hasAuthority('admin')")
    @GetMapping("/test/pre1")
    public String testPre1() {
    System.out.println("=== @PreAuthorize 简单权限校验 ===");
    return "PreAuthorize 访问成功(admin 权限)";
    }

    // 2. 复杂逻辑:拥有 admin 权限 或 是当前资源的创建者(参数 #userId 与用户ID一致)
    @PreAuthorize("hasAuthority('admin') or #userId == principal.id")
    @GetMapping("/test/pre2/{userId}")
    public String testPre2(@PathVariable 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
    24
    import 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;

    @RestController
    public class OrderController {
    // 模拟查询订单:方法执行后,校验订单的创建者是否为当前用户(返回值.orderCreator == 用户名)
    @PostAuthorize("returnObject.orderCreator == principal.username")
    @GetMapping("/order/{orderId}")
    public OrderDTO getOrder(@PathVariable 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
    21
    import javax.annotation.security.PermitAll;
    import javax.annotation.security.RolesAllowed;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;

    @RestController
    public class TestController {
    // 允许所有用户访问(包括未登录)
    @PermitAll
    @GetMapping("/test/permitAll")
    public String testPermitAll() {
    return "所有用户可访问";
    }

    // 仅允许 ADMIN 或 USER 角色访问(自动拼接 ROLE_,即 ROLE_ADMIN、ROLE_USER)
    @RolesAllowed({"ADMIN", "USER"})
    @GetMapping("/test/rolesAllowed")
    public String testRolesAllowed() {
    return "ADMIN/USER 角色可访问";
    }
    }

数据过滤注解

1. @PreFilter:方法执行前过滤入参集合
  • 功能:在方法执行前,对集合类型的入参(如 ListSet)进行过滤,只保留满足条件的元素。

  • 适用场景:需要限制方法入参范围的场景(如 “用户只能操作自己的资源 ID 列表”)。

  • 关键参数:

    • value:SpEL 表达式,判断元素是否保留(true 保留,false 过滤);
    • filterTarget:若方法有多个集合参数,指定要过滤的参数名(如 filterTarget = "ids")。
  • 使用示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    import 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;

    @RestController
    public class ResourceController {
    // 过滤入参:仅保留资源ID属于当前用户的元素(#element 代表集合中的每个元素)
    @PreFilter("filterTarget.contains(principal.resourceId)")
    @PostMapping("/resource/batchDelete")
    public String batchDelete(@RequestBody List<Long> resourceIds) {
    // 此时 resourceIds 已过滤,仅包含当前用户有权删除的资源ID
    resourceService.deleteByIds(resourceIds);
    return "删除成功,共删除 " + resourceIds.size() + " 个资源";
    }
    }
2. @PostFilter:方法执行后过滤返回值集合
  • 功能:在方法执行后,对集合类型的返回值(如 ListSet)进行过滤,只返回满足条件的元素。

  • 适用场景:需要限制返回数据范围的场景(如 “用户只能查看自己的订单列表”)。

  • 使用示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    import org.springframework.security.access.prepost.PostFilter;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;
    import java.util.List;

    @RestController
    public class OrderController {
    // 过滤返回值:仅返回当前用户创建的订单(returnObject 代表返回集合,#element 代表集合元素)
    @PostFilter("filterTarget.orderCreator == principal.username")
    @GetMapping("/order/list")
    public List<OrderDTO> getOrderList() {
    // 1. 先查询所有订单(即使包含其他用户的订单)
    List<OrderDTO> allOrders = orderService.getAllOrders();
    // 2. 过滤后返回:仅保留当前用户的订单
    return allOrders;
    }
    }

注解优先级与注意事项

注解优先级

当一个方法同时添加多个权限注解时,优先级遵循:
@DenyAll(最高) > @PreAuthorize/@PostAuthorize > @Secured/@RolesAllowed > @PermitAll(最低)

关键注意事项

  1. 角色与权限的区别
    • hasRole('admin') 等价于 hasAuthority('ROLE_admin')(自动拼接 ROLE_ 前缀);
    • @Secured@RolesAllowed 基于 “角色”,@PreAuthorize 可同时支持 “角色” 和 “权限”(更灵活)。
  2. @PostAuthorize 的风险
    • 方法会先执行(如写库、调用第三方接口),若校验失败,已执行的操作无法回滚,建议优先使用 @PreAuthorize
  3. 数据过滤注解的限制
    • @PreFilter/@PostFilter 仅对集合类型Collection 子类)有效,对数组、Map 或单个对象无效;
    • 过滤逻辑基于内存(先查询所有数据再过滤),若数据量过大,会导致性能问题,建议优先通过 SQL 条件过滤数据。
  4. 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
@PreAuthorizehasRole 不生效 角色名重复添加 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 表达式,能满足绝大多数复杂的权限场景

欢迎关注我的其它发布渠道

表情 | 预览
快来做第一个评论的人吧~
Powered By Valine
v1.3.10