feat: 会话记录接入客户端 X-Device-Id(auth_sessions.device_id)
- Flutter 端每次请求已携带 X-Device-Id;auth 读取该头(截断 128)经 CreateSessionRequest 透传,user 落 auth_sessions.device_id,为多设备会话列表备数据 - 门禁:./mvnw clean test → BUILD SUCCESS,74 测试 0 失败(新增 registerForwardsDeviceIdHeaderToTheSessionRecord + 会话落库断言) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -27,13 +27,13 @@ public class SessionRepository {
|
||||
|
||||
public void insert(UUID id, UUID userId, UUID tokenFamilyId, byte[] refreshTokenHash,
|
||||
String accessTokenJti, OffsetDateTime expiresAt,
|
||||
String userAgent, String ipAddress) {
|
||||
String deviceId, String userAgent, String ipAddress) {
|
||||
jdbcClient.sql("""
|
||||
INSERT INTO identity.auth_sessions
|
||||
(id, user_id, token_family_id, refresh_token_hash, access_token_jti,
|
||||
expires_at, user_agent, ip_address)
|
||||
expires_at, device_id, user_agent, ip_address)
|
||||
VALUES (:id, :userId, :familyId, :hash, :jti, :expiresAt,
|
||||
:userAgent, CAST(:ipAddress AS inet))
|
||||
:deviceId, :userAgent, CAST(:ipAddress AS inet))
|
||||
""")
|
||||
.param("id", id)
|
||||
.param("userId", userId)
|
||||
@@ -41,6 +41,7 @@ public class SessionRepository {
|
||||
.param("hash", refreshTokenHash)
|
||||
.param("jti", accessTokenJti)
|
||||
.param("expiresAt", expiresAt)
|
||||
.param("deviceId", deviceId)
|
||||
.param("userAgent", userAgent)
|
||||
.param("ipAddress", ipAddress)
|
||||
.update();
|
||||
|
||||
@@ -49,7 +49,7 @@ public class SessionService {
|
||||
/** Opens a new session (= new token family) for a freshly authenticated user. */
|
||||
public SessionTokens create(CreateSessionRequest request) {
|
||||
return insertSession(request.getUserId(), UuidV7.generate(),
|
||||
request.getUserAgent(), request.getIpAddress());
|
||||
request.getDeviceId(), request.getUserAgent(), request.getIpAddress());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -75,7 +75,8 @@ public class SessionService {
|
||||
}
|
||||
|
||||
SessionTokens rotated = transactionTemplate.execute(status -> {
|
||||
SessionTokens tokens = insertSession(session.userId(), session.tokenFamilyId(), null, null);
|
||||
SessionTokens tokens = insertSession(session.userId(), session.tokenFamilyId(),
|
||||
null, null, null);
|
||||
if (sessionRepository.markRotated(session.id(), tokens.getSessionId()) != 1) {
|
||||
status.setRollbackOnly();
|
||||
return null;
|
||||
@@ -98,7 +99,8 @@ public class SessionService {
|
||||
sessionRepository.revokeByTokenHashAndUser(sha256(refreshToken), userId, "logout");
|
||||
}
|
||||
|
||||
private SessionTokens insertSession(UUID userId, UUID familyId, String userAgent, String ipAddress) {
|
||||
private SessionTokens insertSession(UUID userId, UUID familyId,
|
||||
String deviceId, String userAgent, String ipAddress) {
|
||||
UUID sessionId = UuidV7.generate();
|
||||
String jti = UuidV7.generate().toString();
|
||||
byte[] tokenBytes = new byte[32];
|
||||
@@ -108,7 +110,7 @@ public class SessionService {
|
||||
.plus(properties.getSession().getRefreshTtl());
|
||||
|
||||
sessionRepository.insert(sessionId, userId, familyId, sha256(refreshToken), jti,
|
||||
expiresAt, userAgent, ipAddress);
|
||||
expiresAt, deviceId, userAgent, ipAddress);
|
||||
return new SessionTokens(sessionId, userId, jti, refreshToken, expiresAt);
|
||||
}
|
||||
|
||||
|
||||
+9
-1
@@ -57,7 +57,8 @@ class SessionLifecycleIntegrationTest {
|
||||
|
||||
private Map<String, Object> createSession(String userId) throws Exception {
|
||||
String body = mockMvc.perform(internalPost("/internal/sessions")
|
||||
.content("{\"userId\":\"%s\",\"userAgent\":\"junit\",\"ipAddress\":\"127.0.0.1\"}"
|
||||
.content(("{\"userId\":\"%s\",\"deviceId\":\"junit-device\","
|
||||
+ "\"userAgent\":\"junit\",\"ipAddress\":\"127.0.0.1\"}")
|
||||
.formatted(userId)))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(0))
|
||||
@@ -91,6 +92,13 @@ class SessionLifecycleIntegrationTest {
|
||||
assertThat(storedHash).isEqualTo(expected).hasSize(32);
|
||||
// The plaintext token appears nowhere in the row.
|
||||
assertThat(new String(storedHash, StandardCharsets.ISO_8859_1)).isNotEqualTo(refreshToken);
|
||||
|
||||
String deviceId = jdbcClient.sql(
|
||||
"SELECT device_id FROM identity.auth_sessions WHERE id = :id")
|
||||
.param("id", UUID.fromString((String) session.get("sessionId")))
|
||||
.query(String.class)
|
||||
.single();
|
||||
assertThat(deviceId).isEqualTo("junit-device");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Reference in New Issue
Block a user