|
@@ -7,7 +7,9 @@ import com.etotem.cfc.dto.RegisterWithIdCardDTO;
|
|
|
import com.etotem.cfc.dto.RegisterWithInviteCodeDTO;
|
|
import com.etotem.cfc.dto.RegisterWithInviteCodeDTO;
|
|
|
import com.etotem.cfc.entity.User;
|
|
import com.etotem.cfc.entity.User;
|
|
|
import com.etotem.cfc.mapper.UserMapper;
|
|
import com.etotem.cfc.mapper.UserMapper;
|
|
|
|
|
+import com.etotem.cfc.service.DeviceBindService;
|
|
|
import com.etotem.cfc.service.SsoService;
|
|
import com.etotem.cfc.service.SsoService;
|
|
|
|
|
+import com.etotem.cfc.service.TokenBlacklistService;
|
|
|
import com.etotem.cfc.service.UserService;
|
|
import com.etotem.cfc.service.UserService;
|
|
|
import com.etotem.cfc.service.VerificationCodeService;
|
|
import com.etotem.cfc.service.VerificationCodeService;
|
|
|
import io.jsonwebtoken.Claims;
|
|
import io.jsonwebtoken.Claims;
|
|
@@ -43,6 +45,12 @@ public class AuthController {
|
|
|
@Resource
|
|
@Resource
|
|
|
private SsoService ssoService;
|
|
private SsoService ssoService;
|
|
|
|
|
|
|
|
|
|
+ @Resource
|
|
|
|
|
+ private TokenBlacklistService tokenBlacklistService;
|
|
|
|
|
+
|
|
|
|
|
+ @Resource
|
|
|
|
|
+ private DeviceBindService deviceBindService;
|
|
|
|
|
+
|
|
|
/**
|
|
/**
|
|
|
* 发送验证码
|
|
* 发送验证码
|
|
|
*/
|
|
*/
|
|
@@ -230,6 +238,12 @@ public class AuthController {
|
|
|
try {
|
|
try {
|
|
|
String token = authHeader.substring(7);
|
|
String token = authHeader.substring(7);
|
|
|
Claims claims = jwtConfig.parseToken(token);
|
|
Claims claims = jwtConfig.parseToken(token);
|
|
|
|
|
+ // 退出登录(SC-C-17)后旧 token 服务端失效:黑名单命中视为无效,返回 401
|
|
|
|
|
+ // null 容忍:遗留 @InjectMocks 单测未注入该 Bean 时跳过黑名单校验。
|
|
|
|
|
+ if (tokenBlacklistService != null && tokenBlacklistService.isInvalid(token)) {
|
|
|
|
|
+ log.warn("/api/auth/verify: token 已退出登录(黑名单),返回 401");
|
|
|
|
|
+ return Result.error(401, "Token已失效,请重新登录");
|
|
|
|
|
+ }
|
|
|
Long userId = Long.parseLong(claims.getSubject());
|
|
Long userId = Long.parseLong(claims.getSubject());
|
|
|
String role = (String) claims.get("role");
|
|
String role = (String) claims.get("role");
|
|
|
|
|
|
|
@@ -249,6 +263,34 @@ public class AuthController {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ @Operation(summary = "退出登录(服务端 token 失效)")
|
|
|
|
|
+ @PostMapping("/logout")
|
|
|
|
|
+ public Result<Boolean> logout(HttpServletRequest request) {
|
|
|
|
|
+ // SC-C-17 主流程:把当前 Bearer token 记入黑名单,使其服务端失效(TC-C-17-E 异常流:旧 token 再调用应 401)。
|
|
|
|
|
+ // 幂等:无 token / 重复退出均视为成功。
|
|
|
|
|
+ if (tokenBlacklistService != null) {
|
|
|
|
|
+ tokenBlacklistService.invalidate(extractToken(request));
|
|
|
|
|
+ }
|
|
|
|
|
+ return Result.success(true);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Operation(summary = "设备/账号绑定校验(登录准入预检)")
|
|
|
|
|
+ @PostMapping("/device/verify")
|
|
|
|
|
+ public Result<DeviceVerifyResultDTO> deviceVerify(@RequestBody DeviceVerifyDTO dto) {
|
|
|
|
|
+ // SC-C-18 主流程/边界:登录链路预检——正常设备放行 / 新设备登记 / 异常设备二次验证(needVerify=true)。
|
|
|
|
|
+ if (dto == null || dto.getDeviceId() == null || dto.getDeviceId().isEmpty()) {
|
|
|
|
|
+ return Result.badRequest("deviceId 不能为空");
|
|
|
|
|
+ }
|
|
|
|
|
+ if (deviceBindService == null) {
|
|
|
|
|
+ return Result.error("设备校验服务不可用");
|
|
|
|
|
+ }
|
|
|
|
|
+ try {
|
|
|
|
|
+ return Result.success(deviceBindService.verifyDevice(dto));
|
|
|
|
|
+ } catch (RuntimeException e) {
|
|
|
|
|
+ return Result.error(e.getMessage());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
@Operation(summary = "验证密码")
|
|
@Operation(summary = "验证密码")
|
|
|
@PostMapping("/verify-password")
|
|
@PostMapping("/verify-password")
|
|
|
public Result<Boolean> verifyPassword(@RequestBody VerifyPasswordDTO dto,
|
|
public Result<Boolean> verifyPassword(@RequestBody VerifyPasswordDTO dto,
|
|
@@ -394,4 +436,13 @@ public class AuthController {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /** 从请求中提取 Bearer token(Authorization 头 → 参数兜底),无则返回 null。 */
|
|
|
|
|
+ private String extractToken(HttpServletRequest request) {
|
|
|
|
|
+ String header = request.getHeader("Authorization");
|
|
|
|
|
+ if (header != null && header.startsWith("Bearer ")) {
|
|
|
|
|
+ return header.substring(7);
|
|
|
|
|
+ }
|
|
|
|
|
+ return request.getParameter("token");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
}
|
|
}
|