Commit c9e4a6ed by jianan

fix(统一身份): sso 会话失效信号只认 401、/sso/ 白名单保留 Authorization、跳过老状态检查

parent d92e264c
......@@ -6,6 +6,7 @@
import { hshare } from '@/util/fiveshare';
import dataHandling from "@/util/dataHandling";
import { useH5IdleExit } from '@/util/useH5IdleExit'
import * as sso from '@/util/sso'
import tracker from '@/util/tracker/tracker.js'
const idleExit = useH5IdleExit()
......@@ -189,6 +190,13 @@
// 检查用户状态的方法 防止多端操作导致此用户不存在
async checkUserStatus() {
// Casdoor 票据会话:跳过老 storage 登录态检查。
// queryInfo/checkToken/obtainToken 都是老会话语义接口,不认 Casdoor JWT,
// 在此链路跑它们只会产生访客 token 噪音并干扰 sso 会话;
// 用户注销/禁用已由 CffpTokenFilter 每请求 resolveUserId(isActive=1) 把关。
if (sso.isSsoMode()) {
return
}
// 只有已登录用户才需要检查
if (uni.getStorageSync('loginType') == 'codelogin' && uni.getStorageSync('cffp_userId')) {
try {
......@@ -346,8 +354,22 @@
if (params.dataToken) {
uni.setStorageSync('dataToken', decodeURIComponent(params.dataToken));
}
//家办商城跳转未登录状态下
if (params.mpCffp && (params.noLogin || !params.dataToken)) {
// 显式游客入口(noLogin=1):无条件清 sso 态并降级。
// MODE_KEY 在 storage 中跨 web-view 实例残留,不清会劫持游客入口
// (拦截器按「sso 应在」发起 relogin,把游客自动升级成会话)
if (params.mpCffp && params.noLogin) {
if (sso.isSsoMode()) {
sso.clearSso()
}
uni.removeStorageSync('userIsActive')
uni.removeStorageSync('dataToken')
uni.setStorageSync('loginType', 'visitor');
}
// 老语义的未登录降级:排除两种 sso 场景——
// ①URL 带 ticket=(票据落地首包,登录态即将由 ticket.vue 建立)
// ②sso 模式已建立(reLaunch 后文档 URL 无 ticket,靠 MODE_KEY 挡住二次覆盖)
// 否则会把 loginType='sso' 覆盖成 visitor(本轮游客根因)
else if (params.mpCffp && !params.ticket && !sso.isSsoMode() && !params.dataToken) {
uni.removeStorageSync('userIsActive')
uni.removeStorageSync('dataToken')
uni.setStorageSync('loginType', 'visitor');
......
......@@ -119,10 +119,17 @@ export const interceptor = () => {
invoke(args) {
// alert(JSON.stringify(args))
// Casdoor SSO 匿名端点(票据消费带 query,精确白名单匹配不上,按前缀放行)
// 注意:csf 工作台接力 /csf/api/c/sso/ticket/issue 也命中此前缀,但它要求
// Authorization: Bearer——整体替换 header 会把它抹掉(真机 401 根因),
// 故此处必须保留调用方显式设置的 Authorization。
if (typeof args.url === 'string' && args.url.indexOf('/sso/') !== -1) {
const keepAuth = args.header && args.header.Authorization
args.header = {
'content-type': args.method === 'POST' ? 'application/json' : 'application/x-www-form-urlencoded',
}
if (keepAuth) {
args.header.Authorization = keepAuth
}
return
}
// Casdoor 票据通道(小程序 web-view):令牌只在内存,绝不走 obtainToken 游客降级
......@@ -177,9 +184,12 @@ export const interceptor = () => {
// 响应拦截器,可以对数据进行预处理
success(args) {
const bodyT001 = args && args.data && args.data.errorCode && "T001" == args.data.errorCode
// Casdoor 分支失败:sso 模式接受纯 HTTP 401;非 sso 模式严格维持老条件(仅 body T001),行为不变
// Casdoor 分支失败:sso 模式只认 HTTP 401(过滤器 Casdoor 分支的失效信号就是 401)。
// 业务层 T001 是老会话语义(如 loginVerification 不认 Casdoor JWT),
// 在 sso 模式把它当失效会形成 relogin 死循环,不再据此杀会话。
// 非 sso 模式严格维持老条件(仅 body T001),行为不变。
const unauthorized = sso.isSsoMode()
? (bodyT001 || args.statusCode === 401)
? args.statusCode === 401
: bodyT001
if(unauthorized){
if (sso.isSsoMode()) {
......
......@@ -72,7 +72,10 @@ export default function initApp() {
})
}
if (uni.getStorageSync('loginType') == 'codelogin') {
// sso 与 codelogin 同需刷新 cffp_userInfo:queryInfo 走 ThreadLocal,
// Casdoor 会话已被 CffpTokenFilter 写入同一上下文(真机验证 success:true)
var lt = uni.getStorageSync('loginType')
if (lt == 'codelogin' || lt == 'sso') {
api.queryInfo({}).then(res => {
if (!res['success']) {
uni.setStorageSync('loginType', 'visitor')
......
......@@ -14,22 +14,52 @@ const MODE_KEY = 'cffp_sso_mode'
let casdoorToken: string | null = null
let relogining = false
// sessionStorage 键:令牌随文档会话存活(web-view 销毁即清),见 setSsoToken 注释
const SS_KEY = 'cffp_sso_jwt'
/**
* 建立 SSO 会话:令牌只进内存;非敏感登录态(模式标志、isLogin、业务 userId)
* 落持久 storage——web-view 被系统回收重载后内存令牌丢失,据此识别「登录态应在」,
* 拦掉无令牌请求并交原生重走票据,绝不静默降级 obtainToken 游客链路。
* 建立 SSO 会话。
*
* 令牌存 sessionStorage 而非纯内存变量:uni-app H5 的 reLaunch 是整页导航
* (location 级),落地链路 ticket→lanch→index 必然经历文档重建,纯内存令牌
* 会在第一次落地时丢失,随后所有请求 401 → relogin → 再丢 → 死循环。
* sessionStorage 生命周期=当前文档会话:web-view 销毁/微信回收即清空,
* 不跨会话持久(区别于 localStorage),安全语义与原设计一致。
* 非敏感登录态(模式标志、isLogin、业务 userId)仍落持久 storage,
* 令牌丢失时据此识别「登录态应在」并交原生重走票据。
* loginType 写 'sso':作为第三种登录方式(codelogin/visitor/sso),让全局登录态
* UI 与「已登录才拉 queryInfo」门禁天然识别;code 登录专属链路仍显式比对 codelogin。
*/
const readSessionToken = (): string | null => {
try {
return window.sessionStorage.getItem(SS_KEY)
} catch (e) {
return null
}
}
export const setSsoToken = (token: string, userId: number | string) => {
casdoorToken = token
try {
window.sessionStorage.setItem(SS_KEY, token)
} catch (e) {
// 隐私模式等 sessionStorage 不可用:退回纯内存,行为同旧
}
uni.setStorageSync(MODE_KEY, '1')
uni.setStorageSync('isLogin', '1')
uni.setStorageSync('loginType', 'sso')
uni.setStorageSync('cffp_userId', userId)
}
export const getSsoToken = (): string | null => casdoorToken
export const getSsoToken = (): string | null => {
if (casdoorToken) {
return casdoorToken
}
const restored = readSessionToken()
if (restored) {
casdoorToken = restored
}
return restored
}
/**
* 票桥接力(referrer-portal-api design D11):用内存中的 Casdoor token 向工作台后端
......@@ -40,7 +70,9 @@ export const getSsoToken = (): string | null => casdoorToken
*/
export const issueWorkbenchTicket = (csfApiBase: string): Promise<string> =>
new Promise((resolve, reject) => {
if (!casdoorToken) {
// 经 getSsoToken 取(含 sessionStorage 恢复),文档重建后仍可接力
const token = getSsoToken()
if (!token) {
reject(new Error('无 Casdoor 令牌,无法签发工作台票据'))
return
}
......@@ -50,7 +82,7 @@ export const issueWorkbenchTicket = (csfApiBase: string): Promise<string> =>
data: {},
header: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${casdoorToken}`
'Authorization': `Bearer ${token}`
},
success: (res: any) => {
const data = res.data && res.data.code === 200 ? res.data.data : null
......@@ -65,6 +97,11 @@ export const isSsoMode = (): boolean => uni.getStorageSync(MODE_KEY) === '1'
export const clearSso = () => {
casdoorToken = null
try {
window.sessionStorage.removeItem(SS_KEY)
} catch (e) {
/* ignore */
}
uni.removeStorageSync(MODE_KEY)
uni.removeStorageSync('isLogin')
uni.removeStorageSync('cffp_userId')
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment