From b39f9d109f6a39773f76b1d05032a4ad0b3e5e4e Mon Sep 17 00:00:00 2001 From: huiyiruciduojiao <17870108997> Date: Thu, 6 Nov 2025 00:52:48 +0800 Subject: [PATCH] =?UTF-8?q?fix(qr-login):=20restore=20AuthenticationSessio?= =?UTF-8?q?n=20on=20external=20QR=20callback=20flow=20=E8=A7=A3=E5=86=B3?= =?UTF-8?q?=E6=89=AB=E7=A0=81=E7=99=BB=E5=BD=95=E5=9C=A8=E8=B7=A8=E8=AE=BE?= =?UTF-8?q?=E5=A4=87=E5=9B=9E=E8=B0=83=E5=9C=BA=E6=99=AF=E4=B8=8B=E4=B8=A2?= =?UTF-8?q?=E5=A4=B1=20AuthenticationSession=20=E7=9A=84=E9=97=AE=E9=A2=98?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 在 QR 登录流程中,浏览器端发起登录请求时会创建 AuthenticationSession, 但移动端扫码确认回调是独立请求,不携带浏览器 Cookie 和会话上下文, 导致直接持有的 AuthenticationSessionModel 引用无效并返回 null。 修复内容: - 移除直接缓存 AuthenticationSessionModel 引用的方式 - 使用 AuthenticationSessionManager + kc_session_id + tabId 恢复浏览器会话 - 确保扫码回调能够正确续接原始认证流程并完成 Broker 登录 该修复提高了跨设备扫码登录的稳定性与兼容性,避免因会话断链导致登录失败。 --- pom.xml | 2 +- .../java/top/ysit/qrlogin/core/QRSession.java | 2 ++ .../top/ysit/qrlogin/core/util/TokenUtil.java | 8 ++--- .../qrlogin/idp/QRLoginIdentityProvider.java | 10 ++++-- .../qrlogin/login/resources/js/script.js | 31 +++++++++++++++++++ 5 files changed, 45 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index 314fe92..814576f 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ 17 17 - 26.2.4 + 26.4.2 UTF-8 diff --git a/src/main/java/top/ysit/qrlogin/core/QRSession.java b/src/main/java/top/ysit/qrlogin/core/QRSession.java index 5a1ad0b..5f7b928 100644 --- a/src/main/java/top/ysit/qrlogin/core/QRSession.java +++ b/src/main/java/top/ysit/qrlogin/core/QRSession.java @@ -86,4 +86,6 @@ public class QRSession { public void setResponseUrl(String responseUrl) { this.responseUrl = responseUrl; } + + } \ No newline at end of file diff --git a/src/main/java/top/ysit/qrlogin/core/util/TokenUtil.java b/src/main/java/top/ysit/qrlogin/core/util/TokenUtil.java index 022c56d..a2c71d0 100644 --- a/src/main/java/top/ysit/qrlogin/core/util/TokenUtil.java +++ b/src/main/java/top/ysit/qrlogin/core/util/TokenUtil.java @@ -18,7 +18,7 @@ public class TokenUtil { if (token == null || token.isEmpty()) { return TokenValidationResult.invalid("token 为空或无效"); } - if (config.baseUrl == null || config.realm == null || config.clientId == null || config.clientSecret == null){ + if (config.baseUrl == null || config.realm == null || config.clientId == null || config.clientSecret == null) { return TokenValidationResult.invalid("配置错误"); } @@ -62,8 +62,6 @@ public class TokenUtil { JsonObject payload = tokenToJson(token); String email = payload.getString("email", null); String sub = json.getString("sub", null); - - return TokenValidationResult.valid(username, clientId, email, sub, scope, exp); } catch (Exception e) { @@ -82,7 +80,6 @@ public class TokenUtil { if (token == null || token.isEmpty()) { return null; } - try { // JWT token 由三部分组成,用点分隔:header.payload.signature String[] parts = token.split("\\."); @@ -103,7 +100,8 @@ public class TokenUtil { } // Base64 解码 - byte[] decodedBytes = java.util.Base64.getDecoder().decode(payload); + // 使用URL安全的Base64解码器 + byte[] decodedBytes = java.util.Base64.getUrlDecoder().decode(payload); String decodedString = new String(decodedBytes, StandardCharsets.UTF_8); // 解析为 JsonObject diff --git a/src/main/java/top/ysit/qrlogin/idp/QRLoginIdentityProvider.java b/src/main/java/top/ysit/qrlogin/idp/QRLoginIdentityProvider.java index 503e240..38f4ed0 100644 --- a/src/main/java/top/ysit/qrlogin/idp/QRLoginIdentityProvider.java +++ b/src/main/java/top/ysit/qrlogin/idp/QRLoginIdentityProvider.java @@ -12,7 +12,9 @@ import org.keycloak.models.FederatedIdentityModel; import org.keycloak.models.IdentityProviderModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; +import org.keycloak.services.managers.AuthenticationSessionManager; import org.keycloak.sessions.AuthenticationSessionModel; +import org.keycloak.sessions.RootAuthenticationSessionModel; import org.keycloak.util.JsonSerialization; import org.keycloak.utils.MediaType; import top.ysit.qrlogin.config.QRLoginConfig; @@ -71,6 +73,7 @@ public class QRLoginIdentityProvider extends AbstractIdentityProvider { if (expired) return; try { const resp = await fetch(`${statusUrl}${statusUrl.includes('?') ? '&' : '?'}timestamp=${Math.floor(Date.now() / 1000)}`); + // 检查HTTP状态码,处理404等情况 + if (!resp.ok) { + if (resp.status === 404) { + // 处理404错误,视为二维码失效 + handleQRCodeExpired(); + return; + } else { + throw new Error(`HTTP error! status: ${resp.status}`); + } + } + const data = await resp.json(); + switch (data.status) { case "CONFIRMED": const url = data.url; @@ -121,5 +133,24 @@ document.addEventListener("DOMContentLoaded", () => { } }; poll(); + const handleQRCodeExpired = () => { + expired = true; + tip.innerText = "二维码已失效,请重新开始登录流程"; + tip.style.color = "#dc3545"; + count.style.display = "none"; + + // 模糊二维码图像 + const qrImage = box.querySelector('img'); + if (qrImage) { + qrImage.style.filter = "blur(4px)"; + } + + btn.innerText = "重新开始"; + btn.style.background = "#007bff"; + btn.onclick = () => { + window.location.reload(); + }; + }; }); + });