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();
+ };
+ };
});
+
});