feat(device): 设备状态查询增加 Challenge-Response 签名认证
- Android 端先获取挑战值,用 TEE 私钥签名后查询设备状态 - 服务端新增挑战值接口,校验时效、防重放并验签后才返回状态
This commit is contained in:
@@ -12,6 +12,7 @@ import com.secure.demo.repository.EncryptedPhotoRepository;
|
||||
import com.secure.demo.service.DeviceBindingService;
|
||||
import com.secure.demo.service.DeviceBindingService.RecoveryResponse;
|
||||
import com.secure.demo.service.KeyManagementService;
|
||||
import com.secure.demo.controller.model.StatusChallengeRequest;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
@@ -119,12 +120,31 @@ public class DeviceController {
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取设备状态查询用的挑战值(Challenge-Response 第一步)。
|
||||
*
|
||||
* 设备端先调用本接口拿到挑战值,使用 TEE 私钥对 challenge 签名后,
|
||||
* 再调用 POST /api/device/status 完成认证并返回状态。
|
||||
*/
|
||||
@GetMapping("/device/challenge")
|
||||
public ApiResponse<Map<String, Object>> deviceStatusChallenge() {
|
||||
String challenge = deviceBindingService.generateStatusChallenge();
|
||||
Map<String, Object> body = new java.util.LinkedHashMap<>();
|
||||
body.put("challenge", challenge);
|
||||
return ApiResponse.ok(body);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询设备注册/绑定状态(设备端,无登录,App 启动时调用以复用已有状态)。
|
||||
* 本接口要求设备签名认证(Challenge-Response,基于 TEE 私钥)。
|
||||
*
|
||||
* 用途:App 每次重启后,先查询「该 SN 是否已注册/已绑定」,避免重复注册与绑定。
|
||||
* 认证流程:
|
||||
* 1. 设备先 GET /api/device/challenge 获取挑战值
|
||||
* 2. 设备用 TEE 私钥对 challenge 签名
|
||||
* 3. 本接口验签通过后才返回状态(认证失败前不泄露任何绑定信息)
|
||||
*
|
||||
* Request: GET /api/device/status?sn=SN-DEMO-001
|
||||
* Request: POST /api/device/status
|
||||
* body = { "sn": "...", "challenge": "...", "signature": "..." }
|
||||
* Response: data = {
|
||||
* "registered": true, // 该 SN 是否已注册
|
||||
* "bound": true, // 是否已绑定用户
|
||||
@@ -134,12 +154,15 @@ public class DeviceController {
|
||||
* "publicKeyBase64": "..." // 已注册时返回(供 App 判断公钥是否轮换)
|
||||
* }
|
||||
*/
|
||||
@GetMapping("/device/status")
|
||||
public ApiResponse<Map<String, Object>> deviceStatus(@RequestParam("sn") String sn) {
|
||||
if (sn == null || sn.isBlank()) {
|
||||
@PostMapping("/device/status")
|
||||
public ApiResponse<Map<String, Object>> deviceStatus(@RequestBody StatusChallengeRequest req) {
|
||||
if (req.getSn() == null || req.getSn().isBlank()) {
|
||||
throw new IllegalArgumentException("sn required");
|
||||
}
|
||||
Device device = deviceBindingService.findDeviceBySn(sn);
|
||||
// 设备签名认证(时效 + 防重放 + RSA 验签);失败直接抛 SecurityException
|
||||
deviceBindingService.verifyStatusChallenge(req.getSn(), req.getChallenge(), req.getSignature());
|
||||
|
||||
Device device = deviceBindingService.findDeviceBySn(req.getSn());
|
||||
boolean registered = device != null;
|
||||
boolean bound = registered && device.getUserId() != null && !device.getUserId().isBlank();
|
||||
boolean active = registered && device.isActive();
|
||||
|
||||
Reference in New Issue
Block a user