Skip to content

授权


@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 绑定魔法。好处是更灵活,坏处是代码量更大。

面向 PHP 开发者的 Spring Boot 文档