Spring Security 教程(13)、方法安全

为什么需要方法级安全

Spring Security 配图
Spring Security 配图
Spring Security 配图

URL 级别的授权只能控制谁能访问某个页面。但在复杂的企业应用中,你还需要控制:

  • 用户 A 只能查看自己的数据,不能查看用户 B 的
  • 普通用户不能调用管理员接口
  • 特定方法需要额外的权限校验

方法级安全通过 Spring AOP 实现,在方法调用前后进行权限检查。

启用方法级安全

1
2
3
4
5
6
7
@Configuration
@EnableWebSecurity
@EnableMethodSecurity // Spring Security 5.7+ 推荐
// @EnableGlobalMethodSecurity // 旧版本使用这个
public class SecurityConfig {
// ...
}

@Secured:基于角色的简单控制

1
2
3
4
5
@Secured("ROLE_ADMIN")
public void deleteAccount(Long accountId) { ... }

@Secured({"ROLE_ADMIN", "ROLE_SUPERVISOR"})
public void updateSettings(UserSettings settings) { ... }

@Secured 是最简单的注解,只检查用户是否拥有指定角色。注意它不支持 SpEL 表达式。

@PreAuthorize:灵活的预授权检查

1
2
3
4
5
6
7
8
@PreAuthorize("hasRole('ADMIN')")
public User getUser(Long userId) { ... }

@PreAuthorize("#userId == authentication.principal.id")
public UserProfile getProfile(Long userId) { ... }

@PreAuthorize("hasAnyRole('ADMIN', 'MANAGER') and #department == authentication.principal.department")
public List<Employee> getEmployees(Long department) { ... }

@PreAuthorize 支持 SpEL 表达式,可以访问方法参数(通过 # 前缀)、Authentication 对象、Spring Bean 等。

@PostAuthorize:后授权检查

1
2
@PostAuthorize("returnObject.owner == authentication.principal.username")
public Account getAccount(Long accountId) { ... }

@PostAuthorize 在方法执行后检查返回值,适合基于返回对象进行权限控制。

@PreFilter / @PostFilter:列表过滤

1
2
3
4
5
6
@PreFilter(value = "filterObject.owner == authentication.principal.id", 
filterTarget = "accounts")
public List<Account> getFilteredAccounts(List<Account> accounts) { ... }

@PostFilter("filterObject.active == true")
public List<Account> getActiveAccounts() { ... }

@PreFilter 在方法执行前过滤输入参数列表,@PostFilter 在方法执行后过滤返回值列表。

性能考虑

method-security 配图
method-security 配图
method-security 配图

方法级安全通过 Spring AOP 代理实现,每次方法调用都会经过权限检查。对于高并发场景,建议:

  • 避免在热点路径上使用复杂表达式
  • 缓存权限检查结果
  • 合理设计角色和权限模型,减少表达式复杂度