授权
@PreAuthorize(类比 Gate / Policy)
java
@RestController
@RequestMapping("/api/posts")
public class PostController {
@GetMapping
public List<Post> index() {
return postService.findAll();
}
@PostMapping
@PreAuthorize("hasRole('ADMIN')") // 类比 $this->authorize('admin-only')
public Post create(@RequestBody Post post) {
return postService.save(post);
}
@PutMapping("/{id}")
@PreAuthorize("@postSecurity.canEdit(#id)") // 类比 $this->authorize('update', $post)
public Post update(@PathVariable Long id, @RequestBody Post post) {
return postService.update(id, post);
}
@DeleteMapping("/{id}")
@PreAuthorize("hasRole('ADMIN') or @postSecurity.isOwner(#id)")
// ⬆ 角色判断 ⬆ 自定义方法
public void delete(@PathVariable Long id) {
postService.delete(id);
}
}启用方法安全
java
@Configuration
@EnableMethodSecurity // Spring Boot 3+ 启用 @PreAuthorize
public class SecurityConfig {
// 其他 Security 配置
}Spring Boot 3 用
@EnableMethodSecurity,Spring Boot 2 用@EnableGlobalMethodSecurity(prePostEnabled = true)。
常用表达式
| 表达式 | 含义 | 类比 |
|---|---|---|
hasRole('ADMIN') | 当前用户有 ADMIN 角色 | $user->hasRole('admin') |
hasAnyRole('ADMIN', 'EDITOR') | 有任一角色 | $user->hasAnyRole(['admin', 'editor']) |
hasAuthority('POST_DELETE') | 有某个权限 | $user->can('delete', $post) |
#id | 引用方法参数 | 可直接在表达式中使用方法参数 |
@bean.method(#param) | 调用 Spring Bean 的方法 | 调用自定义权限检查 |
isAuthenticated() | 已登录 | auth()->check() |
permitAll() | 允许所有人 | 不验证 |
denyAll() | 拒绝所有人 | 始终返回 false |
自定义权限检查
java
@Component("postSecurity") // Bean 名 postSecurity,在 @PreAuthorize 中引用
public class PostSecurity {
public boolean canEdit(Long postId) {
// 获取当前用户
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
String email = auth.getName();
// 检查是否是文章作者
Post post = postMapper.selectById(postId);
return post != null && post.getAuthorEmail().equals(email);
}
public boolean isOwner(Long postId) {
// 略
return true;
}
}
// 使用
@PreAuthorize("@postSecurity.canEdit(#id)")
public Post update(@PathVariable Long id, @RequestBody Post post) { ... }和 Laravel Policy 的对比:
Laravel:
php// App\Policies\PostPolicy public function update(User $user, Post $post) { return $user->id === $post->user_id; } // 在控制器中 $this->authorize('update', $post);Spring Boot:
java@Component("postSecurity") public class PostSecurity { public boolean canEdit(Long postId) { // 需要手动获取用户和查询数据库 return ...; } } // 在控制器中 @PreAuthorize("@postSecurity.canEdit(#id)")
角色与权限模型
java
// 角色 → 权限(多对多)
@TableName("roles")
public class Role {
private Long id;
private String name; // ROLE_ADMIN, ROLE_EDITOR
}
@TableName("permissions")
public class Permission {
private Long id;
private String code; // POST_CREATE, POST_DELETE
}
// UserDetails 实现
@Service
public class CustomUserDetailsService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String email) {
User user = userMapper.selectByEmail(email);
List<GrantedAuthority> authorities = new ArrayList<>();
// 添加角色
authorities.add(new SimpleGrantedAuthority("ROLE_" + user.getRole()));
// 添加权限
authorities.add(new SimpleGrantedAuthority("POST_CREATE"));
return new org.springframework.security.core.userdetails.User(
user.getEmail(), user.getPassword(), authorities);
}
}
// 使用
@PreAuthorize("hasRole('ADMIN')") // 检查角色
@PreAuthorize("hasAuthority('POST_CREATE')") // 检查权限⚠️ 常见坑
1. hasRole 和 hasAuthority 的区别
hasRole('ADMIN')实际检查的是ROLE_ADMIN(自动拼接ROLE_前缀)。hasAuthority('POST_DELETE')检查的是精确字符串,无前缀。如果存角色时加了
ROLE_前缀就用hasAuthority,没加就用hasRole。建议角色用hasRole,权限用hasAuthority。
2. @EnableMethodSecurity 的重要性
不加这个注解,
@PreAuthorize完全不生效,方法会被无权限地执行。
3. 方法参数引用用 # 号
@PreAuthorize("@postSecurity.canEdit(#id)"中的#id引用的是方法参数id,参数名必须在编译时保留(IDE 设置或加-parameters编译参数)。
4. 表达式中的逻辑运算
java@PreAuthorize("hasRole('ADMIN') or (hasRole('EDITOR') and @postSecurity.isOwner(#id))")支持
and/or逻辑组合。
5. ⭐ 和 Laravel Policy 的差异
- Laravel:
$this->authorize('update', $post),自动进行模型绑定- Spring:
@PreAuthorize("@postSecurity.canEdit(#id)"),需要自己写 Bean 方法查询数据库Spring 的方式更底层,没有 Eloquent 那样的 ORM 绑定魔法。好处是更灵活,坏处是代码量更大。