@jxstjh/auth-client
v0.2.4
Published
匹配4a认证的客户端工具包
Readme
AuthClient
面向 4A 单点登录的前端客户端 SDK。
接入方式
npm 安装
npm install @jxstjh/auth-clientimport {
AuthClient,
AuthMethod,
LoginType,
} from '@jxstjh/auth-client'浏览器 script 引入
可以使用 npm CDN,也可以将构建生成的 dist/index.js 部署到自己的静态资源服务器。生产环境建议固定版本号,避免自动升级引入不兼容变化:
<!-- CDN:请将版本号替换为实际发布版本 -->
<script src="https://unpkg.com/@jxstjh/[email protected]/dist/index.js"></script>
<!-- 或使用自行部署的文件 -->
<!-- <script src="/assets/auth-client/index.js"></script> -->
<script>
window.ac = new window.AuthClient({
SSOServer: 'https://sso.example.com',
resourceCode: '由认证中心分配的资源code',
method: window.AuthMethod.CUSTOM,
})
window.ac.on(window.ACEvents.ERROR, function (error) {
console.error(error.msg)
})
window.ac.on(window.ACEvents.INITED, function (result) {
if (result === false) {
// /web/login 返回空响应,表示当前未登录。
return
}
console.log(result.umsAdmin, result.tokenInfo, result.permissionList)
})
</script>script 文件会将 AuthClient、AuthMethod、ACEvents、LoginType 等公开成员挂载到 window。SDK 的认证功能运行在浏览器中;CommonJS 环境可以加载包入口用于构建和分析,但不能在没有 DOM 的服务端执行浏览器认证流程。
SSO 自动跳转模式
未登录时自动跳转到统一认证中心;已有登录缓存或携带有效 tmpTicket 时触发 inited 事件。
import {
ACEvents,
AuthClient,
AuthMethod,
} from '@jxstjh/auth-client'
import type { AuthClientError } from '@jxstjh/auth-client'
const ac = new AuthClient({
SSOServer: 'https://sso.example.com', // 替换为实际认证中心地址
resourceCode: '由认证中心分配的资源code',
method: AuthMethod.SSO,
})
ac.on(ACEvents.ERROR, (error: AuthClientError) => {
console.error(error.msg, error.code, error.detail)
})
ac.on(ACEvents.INITED, ({ umsAdmin, tokenInfo, permissionList }) => {
// 在这里初始化用户状态、权限和路由
console.log(umsAdmin, tokenInfo, permissionList)
})method 默认值就是 AuthMethod.SSO,可以省略。
自定义登录页
使用自定义登录页时,应先正常启动应用并展示登录页面,不要等待 inited 事件后才创建应用。inited(false) 表示 /web/login 返回空响应、当前未登录;对象结果表示恢复了已有登录状态或票据验证成功。
import {
ACEvents,
AuthClient,
AuthMethod,
} from '@jxstjh/auth-client'
import type { AuthClientError } from '@jxstjh/auth-client'
const ac = new AuthClient({
SSOServer: 'https://sso.example.com', // 替换为实际认证中心地址
resourceCode: '由认证中心分配的资源code',
method: AuthMethod.CUSTOM,
})
ac.on(ACEvents.INITED, (result) => {
if (result === false) {
// 当前未登录,继续展示自定义登录页。
return
}
const { umsAdmin, tokenInfo, permissionList } = result
// 恢复已有登录状态。
console.log(umsAdmin, tokenInfo, permissionList)
})
ac.on(ACEvents.ERROR, (error: AuthClientError) => {
console.error(error.msg)
})
// 此处直接启动 Vue、React 或其他前端应用,由路由决定展示登录页或首页。账号密码登录
login() 返回 Promise<LoginResult>。接入方需要根据 result.step 展示下一步界面,并始终使用 try/catch 处理业务错误、HTTP 4xx/5xx 和网络异常。
import type { LoginOption, LoginResult } from '@jxstjh/auth-client'
// ac 是上文通过 new AuthClient(...) 创建的实例。
let credentials: LoginOption = {
username: '',
password: '',
}
// 以下变量仅表示接入方自己的页面状态,不是 SDK 属性。
let currentStep: LoginResult['step'] | 'idle' = 'idle'
let promptMessage = ''
let captchaImage = ''
let maskedPhone = ''
let dynamicCodeHelpUrl = ''
async function login(extra: Partial<LoginOption> = {}) {
try {
// SDK 方法:提交账号密码,或继续图片验证码/动态口令步骤。
const result = await ac.login({
...credentials,
...extra,
})
await handleLoginStep(result)
} catch (error: any) {
// 后端业务提示优先读取 msg;HTTP 错误还可能包含 status。
console.error(error?.msg || error?.message || String(error))
}
}
async function handleLoginStep(result: LoginResult) {
currentStep = result.step
switch (result.step) {
case 'success': {
const { umsAdmin, tokenInfo, permissionList } = result.data
// 保存用户状态并进入系统
console.log(umsAdmin, tokenInfo, permissionList)
break
}
case 'captcha':
// 接入方操作:将图片绑定到页面的 <img>,并显示验证码输入框。
captchaImage = result.data.img || ''
promptMessage = result.data.message || ''
// 用户填写后再次调用 SDK:
// await ac.login({ ...credentials, loginCode: '用户填写的图片验证码' })
break
case 'dynamic_code':
// 接入方操作:显示动态口令输入框和帮助链接。
promptMessage = result.data.message || ''
dynamicCodeHelpUrl = result.data.helpUrl || ''
// 用户填写后再次调用 SDK:
// await ac.login({ ...credentials, dynamicCode: '用户填写的动态口令' })
break
case 'dual_factor':
// 临时 Token 由 SDK 内部管理,接入方不要保存或回传 Token。
// 接入方操作:展示脱敏手机号,并显示短信验证码输入框。
maskedPhone = result.data.phone
promptMessage = result.data.message || ''
// 此时 SDK 已保存双因子临时 Token,可以展示“获取验证码”按钮。
// 用户点击获取验证码时调用 ac.sendDualFactorSmsCode();
// 用户填写后调用 ac.loginByDualFactorSms({ smsCode })。
break
case 'weak_password':
// 检测到当前密码安全强度较低,需要修改后继续登录。
window.location.href = result.data.redirectUrl
break
}
}各步骤对应的 SDK 公开 API:
| result.step | 接入方操作 | 下一次调用的 SDK API |
| --- | --- | --- |
| success | 保存登录结果并进入系统 | 无,登录已完成 |
| captcha | 展示图片验证码输入框 | ac.login({ username, password, loginCode }) |
| dynamic_code | 展示动态口令输入框 | ac.login({ username, password, dynamicCode }) |
| dual_factor | 展示脱敏手机号和短信验证码输入框 | 用户点击获取验证码时调用 ac.sendDualFactorSmsCode(),然后调用 ac.loginByDualFactorSms({ smsCode }) |
| weak_password | 检测到弱密码,提示用户修改后继续登录 | 跳转至 result.data.redirectUrl |
注意:
- 密码由 SDK 内部加密,接入方传入用户输入的原始密码即可。
- 图片验证码 ID 由 SDK 内部维护,接入方只需要再次传入
loginCode。 - 继续图片验证码或动态口令步骤时,仍需传入原来的
username和password。
双因子短信验证
sendDualFactorSmsCode() 依赖 SDK 内部保存的双因子临时 Token,因此只能在账号密码登录返回 dual_factor 后调用,不能在建立双因子登录状态前直接调用。
完整流程是:
login({ username, password })
→ 返回 dual_factor
→ 接入方使用 result.data.phone 展示脱敏手机号和“获取验证码”按钮
→ 用户点击按钮,调用 sendDualFactorSmsCode()
→ 接入方展示短信验证码输入框
→ 用户填写验证码
→ loginByDualFactorSms({ smsCode })
→ 返回 success收到 dual_factor 后,接入方展示脱敏手机号和“获取验证码”按钮。用户点击按钮时调用 sendDualFactorSmsCode();用户填写验证码后调用 loginByDualFactorSms() 继续完成登录:
// 只能在 login() 返回 dual_factor 后调用:
// SDK 使用此时已保存的双因子临时 Token 发送验证码。
await ac.sendDualFactorSmsCode()
// SDK API:用户填写验证码后提交,继续完成登录。
const result = await ac.loginByDualFactorSms({
smsCode: '用户填写的短信验证码',
})
await handleLoginStep(result)手机号短信登录
普通手机号登录与账号密码登录的双因子步骤是两个不同流程。普通手机号登录使用 sendPhoneSmsCode(phone),不依赖 dual_factor 状态:
// SDK API:向指定手机号发送验证码。
await ac.sendPhoneSmsCode('13800000000')
// SDK API:提交手机号和短信验证码。
const result = await ac.loginByPhone({
smsMob: '13800000000',
smsCode: '用户填写的短信验证码',
})
await handleLoginStep(result)
if (result.step === 'dynamic_code') {
// 接入方展示动态口令输入框;用户填写后再次调用同一个 SDK API。
const finalResult = await ac.loginByPhone({
smsMob: '13800000000',
smsCode: '原短信验证码',
dynamicCode: '用户填写的动态口令',
})
await handleLoginStep(finalResult)
}完整流程:
sendPhoneSmsCode(phone)
→ 用户填写短信验证码
→ loginByPhone({ smsMob, smsCode })
├─ success → 登录完成
└─ dynamic_code
→ 接入方展示动态口令输入框
→ loginByPhone({ smsMob, smsCode, dynamicCode })
→ success原 sendSmsCode(phone?) 和 loginBySms() 为兼容已有接入保留,新代码请使用语义明确的 sendDualFactorSmsCode()、loginByDualFactorSms() 或 sendPhoneSmsCode(phone)。
登录辅助信息
// SDK 方法:获取认证中心为当前资源配置的登录方式 ID。
const loginMethods = await ac.getAvailableLoginMethods()
// SDK 方法:获取认证中心配置的密码规则说明。
const passwordRule = await ac.getPasswordRule()登录方式 ID:
LoginType.PASSWORD:账号密码LoginType.SMS:手机号短信LoginType.DINGTALK:钉钉LoginType.GZT:赣政通
错误处理
请求失败时,Promise 会被拒绝。后端返回 JSON 错误体时会尽量保留原始字段,并补充 HTTP 状态:
try {
await ac.login(credentials)
} catch (error: any) {
console.error(error.code)
console.error(error.msg)
console.error(error.status)
console.error(error.statusText)
}全局 error 事件用于接收 SDK 初始化、配置、票据校验等错误,不能替代每个异步登录方法自身的 try/catch。
退出登录
// SDK 方法:通知认证中心退出当前登录。
await ac.serverLogout()
// SDK 方法:清理当前浏览器中的用户、Token 和权限缓存。
ac.clientLogout()站点统计工具
import { SiteAnalysis } from '@jxstjh/auth-client'
const siteAnalysis = new SiteAnalysis({
siteAnalysisServer: 'https://analytics.example.com', // 替换为实际统计服务地址
appId: '申请的 appId',
interval: 30,
production: process.env.NODE_ENV === 'production',
})
router.afterEach(() => {
siteAnalysis.recordPage()
})
siteAnalysis.setLoginName(loginName)