权限验证使用示例.md 5.4 KB

Spring Security + JWT 权限验证使用示例

1. 权限验证注解使用

在 Controller 方法上使用 @PreAuthorize 注解进行权限校验:

@RestController
@RequestMapping("/api/exam")
public class ExamController {

    // 示例1: 检查是否有特定权限
    @PreAuthorize("hasAuthority('exam:list')")
    @PostMapping("/page")
    public RestResult<?> getExamsByCondition(@RequestBody ExamQueryVO queryVO) {
        // 只有拥有 'exam:list' 权限的用户才能访问
        // ...
    }

    // 示例2: 检查是否有多个权限之一(OR关系)
    @PreAuthorize("hasAnyAuthority('exam:create', 'exam:add')")
    @PostMapping("/create")
    public RestResult<?> createExam(@RequestBody ExamCreateVO createVO) {
        // 拥有 'exam:create' 或 'exam:add' 权限的用户可以访问
        // ...
    }

    // 示例3: 检查是否同时拥有多个权限(AND关系)
    @PreAuthorize("hasAuthority('exam:update') and hasAuthority('exam:verify')")
    @PostMapping("/update")
    public RestResult<?> updateExam(@RequestBody ExamUpdateVO updateVO) {
        // 必须同时拥有 'exam:update' 和 'exam:verify' 权限
        // ...
    }

    // 示例4: 复杂表达式
    @PreAuthorize("hasAuthority('exam:delete') or hasRole('ADMIN')")
    @PostMapping("/delete/{id}")
    public RestResult<?> deleteExam(@PathVariable String id) {
        // 拥有 'exam:delete' 权限或者是 ADMIN 角色的用户可以访问
        // ...
    }
}

2. 权限配置说明

2.1 数据库配置

doctor_menu 表结构:

CREATE TABLE doctor_menu (
    id VARCHAR(50) PRIMARY KEY,
    name VARCHAR(100),
    perms VARCHAR(255),  -- 权限标识,如 'exam:list,exam:create'
    parent_id VARCHAR(50),
    status VARCHAR(10),
    ...
);

示例数据:

-- 医生菜单权限
INSERT INTO doctor_menu (id, name, perms, status) VALUES
('1', '检查管理', 'exam:list,exam:view', '1'),
('2', '报告管理', 'report:list,report:create', '1'),
('6', '申请医生', 'application:create,application:cancel', '1');

-- 医生角色配置
UPDATE doctors SET doctor_role = '1,2,6' WHERE id = 1;

2.2 权限标识命名规范

建议使用 模块:操作 的格式:

  • exam:list - 检查列表查询
  • exam:view - 检查详情查看
  • exam:create - 创建检查
  • exam:update - 更新检查
  • exam:delete - 删除检查
  • report:list - 报告列表
  • report:create - 创建报告

3. 登录流程

3.1 登录请求

POST /auth/login
Headers:
  Content-Type: application/json
  platform: doctor  # 可选: doctor/manage,不传则返回所有权限

Body:
{
  "username": "doctor001",
  "password": "123456"
}

3.2 登录响应

{
  "code": 200,
  "message": "登录成功",
  "data": {
    "token": "eyJhbGciOiJIUzUxMiJ9...",  // JWT token(包含权限)
    "userInfo": { ... },
    "doctorInfo": {
      "id": 1,
      "doctorRole": "1,2,6",
      "permissions": [
        "exam:list",
        "exam:view",
        "report:list",
        "report:create",
        "application:create",
        "application:cancel"
      ]
    }
  }
}

3.3 JWT Token 结构

Token 中包含的 claims:

{
  "username": "doctor001",
  "userId": 1,
  "permissions": "exam:list,exam:view,report:list,report:create"  // 逗号分隔
}

4. 后续请求

4.1 请求携带 Token

GET /api/exam/page
Headers:
  Authorization: Bearer eyJhbGciOiJIUzUxMiJ9...
  Content-Type: application/json

4.2 权限验证流程

1. JwtAuthenticationFilter 拦截请求
   ↓
2. 从 Authorization header 提取 token
   ↓
3. JwtUtil.parseUserFromToken(token) 解析 token
   ↓
4. 从 token claims 中提取 permissions(逗号分隔字符串)
   ↓
5. 转换为 List<String> permissions
   ↓
6. 创建 LoginUser(user, permissions)
   ↓
7. 设置到 SecurityContext
   ↓
8. @PreAuthorize 注解检查权限
   ↓
9. 有权限 → 执行方法;无权限 → 403 Forbidden

5. 权限不足处理

5.1 响应示例

{
  "code": 403,
  "message": "权限不足",
  "data": null
}

5.2 全局异常处理

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(AccessDeniedException.class)
    public RestResult<?> handleAccessDeniedException(AccessDeniedException e) {
        return RestResult.error(403, "权限不足");
    }
}

6. 优势

6.1 Token 长度优化

  • 使用逗号分隔字符串存储权限,而不是 JSON 数组
  • 减小 token 大小,提高传输效率

6.2 性能优化

  • 权限存储在 token 中,避免每次请求查询数据库
  • JwtAuthenticationFilter 直接从 token 解析权限
  • UserDetailsService 只在登录时调用一次

6.3 灵活性

  • 支持 platform 参数区分医生端/管理端权限
  • 支持 doctor 和 manage 权限合并
  • 权限自动去重

7. 注意事项

  1. Token 有效期: 默认 24 小时,可在 application.yml 配置
  2. 权限变更: 需要重新登录才能生效(因为权限在 token 中)
  3. Token 刷新: 如需长期有效,建议实现 refresh token 机制
  4. 权限命名: 统一使用 模块:操作 格式,便于管理

8. Spring Security 配置检查

确保 SecurityConfig 已启用方法级别安全:

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)  // 启用 @PreAuthorize
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    // ...
}