授权
Laravel 用
Gate::authorize()、$this->authorize()、@can做权限控制;Spring Security 用@PreAuthorize方法级注解 +@EnableMethodSecurity。思想一致:在方法上声明「谁能调」,框架拦截校验。
三个角色/权限源
先明确「当前用户有哪些角色」从哪来。在 UserDetailsService 里赋值:
return org.springframework.security.core.userdetails.User
.withUsername(user.getUsername())
.password(user.getPassword())
.roles(user.getRole()) // "ADMIN" / "USER"
.build();roles("ADMIN") 实际赋予的权限名是 ROLE_ADMIN(框架自动加前缀)。
启用方法级安全
@Configuration
@EnableMethodSecurity // 开启 @PreAuthorize 等注解(默认已启用,加着保险)
public class MethodSecurityConfig { }常用授权注解
| 注解 | 作用 | 类比 Laravel |
|---|---|---|
@PreAuthorize("hasRole('ADMIN')") | 调用前校验角色 | Gate::authorize('admin') |
@PreAuthorize("hasAuthority('post.delete')") | 校验权限点 | can('delete', $post) |
@PreAuthorize("#id == authentication.principal.id") | 校验资源归属 | 策略类 |
@PostAuthorize | 方法返回后校验 | 少用 |
@Secured | 只支持角色 | 老注解,少用 |
@Service
public class PostService {
@PreAuthorize("hasRole('ADMIN')")
public void deleteAnyPost(Long id) { ... }
@PreAuthorize("hasRole('ADMIN') or #dto.authorId == authentication.principal.id")
public void updatePost(PostDto dto) { ... }
}三种角色写法
| SpEL | 匹配 |
|---|---|
hasRole('ADMIN') | 用户权限含 ROLE_ADMIN |
hasAnyRole('ADMIN','EDITOR') | 任意一个 |
hasAuthority('post.delete') | 用户权限含 post.delete(角色也可以当 authority 用) |
Laravel 里 roles 和 permissions 的关系同理:角色是大集合,权限点是细粒度动作。
控制器 + 注解联动
@RestController
@RequestMapping("/api/posts")
public class PostController {
@DeleteMapping("/{id}")
@PreAuthorize("hasRole('ADMIN')")
public void delete(@PathVariable Long id) {
service.delete(id);
}
}访问无权限 → 抛 AccessDeniedException → Spring Security 返回 403。
403 vs 401 要分清
- 401:没登录(未认证)→ Spring Security 过滤器链直接拦
- 403:登录了但没权限(未授权)→
@PreAuthorize抛AccessDeniedExceptionLaravel 里同理:auth中间件 401/redirect 登录,Gate拒绝 403。
基于对象的授权:资源归属校验
Laravel 用授权策略(Policy)做「只能改自己的帖子」;Spring 在 @PreAuthorize 里写 SpEL:
@PreAuthorize("#post.authorId == authentication.principal.id")
public Post update(Long id, PostDto dto) {
...
}#post引用方法参数(参数名需编译期保留,或@Param("post")指定)authentication.principal是当前登录的 UserDetails,.id取 ID
TIP
Laravel 里你写 Policy::update($user, $post) 返回 bool;Spring 在 SpEL 里做同样判断。复杂规则可以抽一个 Bean 方法:@PreAuthorize("@postPolicy.canUpdate(authentication, #post)"),规则集中在 PostPolicy 类里,更易测试。
常见权限场景对照
| 需求 | Laravel | Spring Security |
|---|---|---|
| 管理员专属 | Gate::authorize('admin') | @PreAuthorize("hasRole('ADMIN')") |
| 多重角色 | Gate::any([...]) | hasAnyRole('A','B') |
| 只能改自己的 | $this->authorize('update', $post) | #post.authorId == authentication.principal.id |
| 匿名可访问 | 中间件跳过 | permitAll() |
| 登录即可 | ->middleware('auth') | .authenticated() |
| 逻辑或 | Gate::any() | hasRole('A') or hasRole('B') |
| 控制器/服务层都可用 | 都行 | 方法级注解,任意 Bean 方法都可标注 |
授权失败返回自定义 JSON
默认 403 是框架样式。想返回统一结构,加异常处理器:
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(AccessDeniedException.class)
public ApiResponse<Void> handleDenied(AccessDeniedException ex) {
return ApiResponse.of(403, "没有权限执行此操作", null);
}
}TIP
注意 AccessDeniedException 可能被 Spring Security 的过滤器链提前拦截(在进入 Controller 前)。若要完全统一,需要自定义 AccessDeniedHandler,见 Spring Security 深入。
小结
| 记住四句话 |
|---|
授权用 @PreAuthorize + SpEL 表达式,方法级拦截 |
角色 hasRole('ADMIN') 匹配 ROLE_ADMIN |
资源归属用 #参数 == authentication.principal.xxx |
| 403 是「登录了但没权限」,和 401「没登录」是两回事 |