Spring Boot 中实现用户数据访问权限控制的完整指南

本文介绍如何在 spring boot 应用中为 rest 接口添加细粒度

安全控制,确保用户仅能访问自身数据(如 `/utente` 接口),防止 id 滥用导致的数据越权泄露。核心方案包括启用方法级安全、集成认证上下文、校验请求主体身份与资源归属一致性。

要真正实现“仅登录用户可查看自己的资料”,仅靠 @Secured 注解是不够的——它只能控制“谁有权限调用该方法”,但无法解决“用户 A 传入 ID=2 试图读取用户 B 的数据”这一典型越权问题(Insecure Direct Object Reference, IDOR)。你需要结合认证上下文感知 + 业务逻辑校验双重防护。

✅ 正确做法:三步加固

1. 启用方法级安全(基础前提)

在主配置类(如 SecurityConfig)中启用全局方法安全:

@Configuration
@EnableWebSecurity
@EnableMethodSecurity // 替代旧版 @EnableGlobalMethodSecurity
public class SecurityConfig {
    // ... 其他配置
}
⚠️ 注意:@EnableMethodSecurity 是 Spring Security 5.6+ 推荐方式,自动启用 @PreAuthorize、@PostAuthorize 等注解支持。

2. 使用 @PreAuthorize 校验调用者身份(声明式控制)

修改你的控制器方法,不接收外部传入的 idUtente,而是从认证上下文中提取当前登录用户的 ID:

@PostMapping("/utente")
public ResponseEntity getDatiProfiloUtente(
        @AuthenticationPrincipal CustomUserDetails userDetails) { // 假设你使用自定义 UserDetails

    Long currentUserId = userDetails.getId(); // 从认证对象获取真实用户 ID
    Paziente paziente = service.findPazienteById(currentUserId);

    if (paziente == null) {
        return ResponseEntity.status(HttpStatus.NOT_FOUND)
                .body(Map.of("error", "Profilo non trovato"));
    }

    Map map = new HashMap<>();
    map.put("nome", paziente.getNome());
    map.put("cognome", paziente.getCognome());
    map.put("email", paziente.getEmail());
    map.put("nTelefono", paziente.getNumeroTelefono());
    map.put("emailCaregiver", paziente.getEmailCaregiver());
    map.put("nomeCaregiver", paziente.getNomeCaregiver());
    map.put("cognomeCaregiver", paziente.getCognomeCaregiver());

    return ResponseEntity.ok(map);
}

✅ 优势:完全消除客户端传 ID 的风险;天然绑定会话身份。

3. (进阶)若必须支持“查他人”场景,用 @PostAuthorize 动态校验

例如管理员可查任意用户,但普通用户只能查自己:

@GetMapping("/utente/{id}")
@PostAuthorize("returnObject != null && " +
               "(hasRole('ADMIN') || #id == authentication.principal.id)")
public ResponseEntity getPazienteById(@PathVariable Long id) {
    return ResponseEntity.ok(service.findPazienteById(id));
}

此注解在方法执行后校验返回值和权限,确保即使方法返回了数据,也会被拦截非授权访问。

? 关键注意事项

  • 永远不要信任客户端传入的 ID:删除 @RequestBody Long idUtente 这种设计,这是 IDOR 漏洞根源。
  • 确保 UserDetails 实现包含用户唯一标识(如 id 字段),并在登录时正确注入 Authentication。
  • 前端未就绪 ≠ 安全可妥协:Postman 测试阶段更需严格模拟真实权限流,建议配合 JWT 或 Session 登录后再测试接口。
  • 补充传输层安全:生产环境务必启用 HTTPS,避免认证凭据(如 token)被窃听。

✅ 总结

真正的安全不是“加个注解就完事”,而是:

  1. 身份可信(通过 @AuthenticationPrincipal 获取真实用户)
  2. 数据归属校验(服务层确认 paziente.getUserId() == currentUser.getId())
  3. 权限分层控制(@PreAuthorize 控制入口,@PostAuthorize 控制结果)

这样,即使攻击者知道任意 ID,也无法绕过身份绑定逻辑,彻底杜绝越权访问风险。