授权模型
一句话定义:Spring Security 的授权体系从请求到达受保护资源的路径上,通过 URL 级、方法级、实例级 三个粒度层层拦截,形成纵深防御的访问控制模型。
定义与作用
授权(Authorization)解决"你能做什么"的问题。Spring Security 提供三个递进的授权粒度:
| 粒度 | 拦截位置 | 适用场景 | 核心组件 |
|---|---|---|---|
| URL 级别 | Servlet 过滤器链末尾(FilterSecurityInterceptor / AuthorizationFilter) | Web 资源的粗粒度控制,如 /admin/** 需管理员角色 | HttpSecurity + AccessDecisionManager |
| 方法级别 | AOP 方法拦截器(MethodSecurityInterceptor) | Service 层业务方法的细粒度控制,如删除订单需特定权限 | @EnableGlobalMethodSecurity + 注解 |
| 实例级别 | 方法级别注解中通过 SpEL 表达式引用对象属性 | 数据行级控制,如"只能查看自己的订单" | @PostAuthorize + returnObject |
三层授权的关系:
核心原理
三层拦截的执行顺序
Spring Security 的授权决策按以下顺序执行:
- URL 级别:请求进入
FilterSecurityInterceptor(Spring Security 5.x)或AuthorizationFilter(6.x),从SecurityMetadataSource获取当前 URL 所需的ConfigAttribute,调用AccessDecisionManager投票决策。 - 方法级别:若 URL 放行,请求到达 Controller/Service。带有
@PreAuthorize等方法安全注解的方法,会被MethodSecurityInterceptor(基于 Spring AOP)拦截,再次执行授权决策。 - 实例级别:在方法级别注解中,通过 SpEL 表达式(如
returnObject.owner == authentication.name)对返回对象的属性进行判断,实现数据行级过滤。
示例一:三层授权完整配置
场景说明
电商后台管理系统,要求:
/admin/**仅允许ADMIN角色访问(URL 级别)- 删除订单方法需
ORDER_DELETE权限(方法级别) - 查询订单只能返回当前用户创建的订单(实例级别)
操作前配置
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true) // 开启方法级别安全
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests(auth -> auth
.antMatchers("/admin/**").hasRole("ADMIN") // URL 级别
.antMatchers("/public/**").permitAll()
.anyRequest().authenticated()
)
.formLogin(withDefaults());
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("admin").password("{noop}admin").roles("ADMIN")
.and()
.withUser("user").password("{noop}user").roles("USER");
}
}
@Service
public class OrderService {
// 方法级别:需要 ORDER_DELETE 权限
@PreAuthorize("hasAuthority('ORDER_DELETE')")
public void deleteOrder(Long orderId) {
// 删除订单逻辑
}
// 实例级别:只能返回当前用户的订单
@PostFilter("filterObject.owner == authentication.name")
public List<Order> getMyOrders() {
return orderRepository.findAll(); // 查询全部,但会被过滤
}
}
操作后配置
通过 Admin 用户访问:
GET /admin/orders→ 200 OK(URL 级通过)orderService.deleteOrder(1L)→ 如果 Admin 拥有ORDER_DELETE权限则成功,否则 403orderService.getMyOrders()→ 返回 Admin 创建的订单列表
通过 User 用户访问:
GET /admin/orders→ 403 Forbidden(URL 级拦截)- 不会触发方法级别和实例级别拦截
示例二:仅 URL 级别 vs 方法级别配合
场景说明
对比两种配置策略:仅 URL 级别控制 和 URL + 方法级别联合控制。
操作前配置(仅 URL 级别,存在越权漏洞)
@Configuration
@EnableWebSecurity
public class UnsafeConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests(auth -> auth
.antMatchers("/api/orders/**").authenticated() // 只要登录就能访问
.anyRequest().permitAll()
)
.httpBasic(withDefaults());
}
}
@Service
public class UnsafeOrderService {
// 无方法级别安全,任何登录用户都能调用
public void cancelOrder(Long orderId) {
// 取消他人订单的漏洞!
}
}
结果分析:攻击者登录普通用户账号,调用 POST /api/orders/123/cancel,可以取消别人的订单。
操作后配置(URL + 方法级别联合防护)
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SafeConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests(auth -> auth
.antMatchers("/api/orders/**").authenticated()
.anyRequest().permitAll()
)
.httpBasic(withDefaults());
}
}
@Service
public class SafeOrderService {
// 方法级别:只能取消自己的订单
@PreAuthorize("hasRole('ADMIN') or @orderSecurityService.isOwner(#orderId, authentication.name)")
public void cancelOrder(Long orderId) {
// 安全的取消逻辑
}
}
@Component
public class OrderSecurityService {
public boolean isOwner(Long orderId, String username) {
return orderRepository.findById(orderId)
.map(order -> order.getOwner().equals(username))
.orElse(false);
}
}
结果分析:
- 普通用户调用
cancelOrder(123)时,如果订单 123 不是自己创建的 → 403 - Admin 用户 → 可以取消任何订单
- 方法级别授权提供了 URL 级别无法实现的细粒度控制
易错场景:URL 级别放行后误以为"完全安全"
场景:开发者配置了 antMatchers("/api/**").authenticated(),认为只要登录就能访问 API,于是在 Service 层不做任何权限检查。
问题:authenticated() 只验证"是否登录",不验证"是谁"。任何登录用户都能调用所有 API,包括管理员专属接口。
正确做法:
// 错误:仅验证登录
.antMatchers("/api/admin/**").authenticated()
// 正确:同时验证角色
.antMatchers("/api/admin/**").hasRole("ADMIN")
面试考点:
问:Spring Security 中
authenticated()和hasRole("ADMIN")有什么区别? 答:authenticated()只要求用户已登录(非匿名),不检查具体角色;hasRole("ADMIN")要求用户已登录且拥有ROLE_ADMIN权限。生产环境中,绝不能用authenticated()替代角色检查,必须按最小权限原则配置 URL 规则或叠加方法级别安全。
三层模型速查表
| 层级 | 拦截器 | 配置方式 | 最小权限原则 |
|---|---|---|---|
| URL 级 | FilterSecurityInterceptor | HttpSecurity.authorizeRequests() | 越精确的路径越靠前配置 |
| 方法级 | MethodSecurityInterceptor | @PreAuthorize / @Secured | 在 Service 层保护核心业务方法 |
| 实例级 | SpEL 表达式 | returnObject / filterObject | 数据所有权校验放在 @PostAuthorize / @PostFilter |