授权服务(@uniboot/auth)
与框架无关的 OAuth 2.0 登录状态机与路由权限守卫,将 登录 一文中的授权中心跳转、换票、路由拦截沉淀为可复用能力。账号资料、忘记密码等由 @uniboot/admin 负责。
| 能力 | 导出 | 说明 |
|---|---|---|
| OAuth 状态机 | createOAuthAuthService | state 防伪、code 换票、401 刷新、解析 expiresIn |
| 路由权限守卫 | setupAuthPermissionGuard | 未登录重定向、OAuth 回调、清 query、动态路由、白名单 |
| 登录 API | createAuthApi | 本地登录 / 登出(可拆 login / logout 客户端) |
| 页面组件 | AuthLoginPage | 本地登录表单(不走 SSO 整页跳转时用) |
类型以 XxxLike 描述宿主 router / user,不直接依赖具体版本的 vue-router。peerDependencies:vue、vue-i18n、uniboot-ui。dependencies:@uniboot/request。
Auth 模板接入
三服务地址均需配置(与 用户服务 一致):
| 环境变量 | 含义 | 鉴权侧用法 |
|---|---|---|
APP_PUBLIC_API_URL | 业务系统 | 默认 request;经网关聚合 OAuth 时,换票 / 刷新也绑此客户端 |
APP_PUBLIC_AUTH_SERVICE_URL | 授权服务 | Bundle → authHttp(登出、直连授权能力) |
APP_PUBLIC_ADMIN_SERVICE_URL | 用户服务 | Bundle → adminHttp(见 admin 文档) |
APP_PUBLIC_CLIENT_ID | OAuth 客户端 ID | createOAuthAuthService.clientId(必填,未配置会抛错) |
模板职责拆分:
| 文件 | 职责 |
|---|---|
src/utils/request.ts | 创建客户端;bindOAuthHttp(request) 尽早绑定 |
src/setup/auth-runtime.ts | HTTP 公共配置、OAuth、setupPermissionGuard、401 刷新 |
src/router/guard/permission.ts | 调用 setupPermissionGuard |
src/install/plugins/uniboot-admin.ts | setupUnibootAdmin(见 用户服务) |
// src/utils/request.ts(节选)
const request = createHttpClient({ baseURL, ...httpCommonOptions })
bindOAuthHttp(request) // 首屏并行 401 前即可刷新
export const { adminApi, userApi, roleApi, authHttp, adminHttp } =
createUnibootAdminHttpBundle({
authServiceUrl,
adminServiceUrl,
http: httpCommonOptions, // 与业务 request 共用同一 refreshToken 函数引用
})// src/setup/auth-runtime.ts(节选)
oauthAuthService = createOAuthAuthService({
authServiceUrl: config.authServiceUrl,
appBase: config.appBase,
redirectUri: config.redirectUri,
clientId: config.clientId, // 必填
http, // 须能打到 /oauth/token 与 /oauth/refresh_token(网关场景 = 业务 request)
tokenPath: '/oauth/token',
refreshTokenPath: '/oauth/refresh_token',
// authorizePath: '/oauth/authorize', // 标准授权端点;默认 '/login'
// getAuthorizeLang: () => (locale === 'zh-CN' ? 'zh_CN' : 'en_US'),
})
setupAuthPermissionGuard({
router,
oauth: oauthAuthService,
useAuthServiceLogin: Boolean(config.authServiceUrl?.trim()),
user: createAuthPermissionUserBridge(),
// 可选:本地 token 已过期时仍允许用 URL 中的 code 换票
// isAccessTokenValid: () => Date.now() < expiresTimestamp,
// 可选:无菜单但需改初始密码时不要踢 403
// resolveEmptyRoutesFallback: () => INDEX_ROUTE_NAME,
// loginPath / whiteList / dynamicRoutes / progress / feedback ...
})refreshHttpAccessToken(HTTP 401 拦截器)与守卫共用同一个 oauthAuthService。多个 createHttpClient(业务 / auth / admin)应传入同一个 refreshToken 函数引用,由 @uniboot/request 跨实例合并并发刷新。须在 createRouter 之后、app.use(router) 之前注册守卫。
createOAuthAuthService
把「统一授权中心登录」拆成纯函数,中间状态默认写入 sessionStorage(可换 OAuthStorageLike)。
参数
| 名称 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| authServiceUrl | 授权中心地址(拼授权跳转 URL) | string | — |
| appBase | 应用基础路径,用于推导 redirectUri | string | — |
| redirectUri | 回调地址;未传按 ${origin}${appBase} 推导 | string | 按 appBase 推导 |
| clientId | OAuth 客户端 ID(必填,空则抛错) | string | — |
| scope | 授权范围 | string | 'user_info' |
| http | 换票 / 刷新用的 HTTP 客户端 | object | — |
| tokenPath | 换票路径 | string | '/oauth/token' |
| refreshTokenPath | 刷新路径 | string | '/oauth/refresh_token' |
| authorizePath | 授权入口路径 | string | '/login' |
| state | 防伪校验值 | string | OAUTH_FIXED_STATE('STATE') |
| allowFixedStateFallback | session 无 state 时是否回退到固定 state | boolean | true |
| getAuthorizeLang | 追加授权 URL 的 lang | function | — |
| storage | 中间状态存储 | object | sessionStorage |
| getOrigin | 推导 redirectUri 时取源 | function | () => location.origin |
http 绑哪个客户端?
- 经业务网关转发
/oauth/token、/oauth/refresh_token:传业务request(Auth 模板默认)。 - 直连授权服务:传 Bundle 的
authHttp(baseURL = APP_PUBLIC_AUTH_SERVICE_URL)。 - 登出一般走
authHttp(见createAuthApi.logoutHttp),与换票客户端可以不同。
返回值
| 名称 | 说明 | 类型 |
|---|---|---|
| buildAuthorizeHref | 生成授权中心 URL,并写入 state | function |
| rememberReturnPath | 记忆登录前地址 | function |
| takeAndClearReturnPath | 取出并清除返回地址 | function |
| getLoginState | 读取已写入的 state(可回退固定值) | function |
| exchangeAuthorizationCode | code 换令牌对(含可选 expiresIn) | function |
| refreshAccessToken | refresh token 换新令牌对 | function |
| clearLoginSessionMarkers | 清理 state / 返回地址 | function |
OAuthTokenPair:{ accessToken, refreshToken?, expiresIn? }(expiresIn 为秒)。
另导出工具:stripOAuthCallbackFromBrowserUrl、isLikelyOAuthAuthorizeCallback、omitOAuthCallbackQuery、OAUTH_CALLBACK_QUERY_KEYS。
实现细节
- 换票 / 刷新统一:
data传参 +withToken: false+isTransformResponse: false+errorTip: false,避免默认解包失败或全局 tip 与守卫双重提示。 - 未显式传
state时用固定值'STATE';allowFixedStateFallback默认开启,兼容换票后清 session、用户浏览器后退再登录。 - 令牌解析兼容
access_token/accessToken、expires_in/expiresIn、顶层或嵌套在result/data。
setupAuthPermissionGuard
组装 router.beforeEach:访问拦截 → 授权中心 → 回调换票 → 动态路由。
参数
| 名称 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| router | 满足 AuthPermissionRouterLike 即可 | object | — |
| user | token / 用户信息 / 路由表适配器 | object | — |
| dynamicRoutes | 首页路由、路由名、首个可用路由查找 | object | — |
| loginPath | 登录页路径 | string | — |
| indexPath | 首页路径 | string | — |
| error403Path | 无权限页路径 | string | — |
| whiteList | 免登录白名单 | array | — |
| title | document.title 回退 | string / function | — |
| useAuthServiceLogin | 是否整页跳转授权中心 | boolean | false |
| oauth | createOAuthAuthService 实例 | object | — |
| isAccessTokenValid | 本地 token 是否仍可用;返回 false 时即使有字符串也会走换票 | function | 有 token 即视为可用 |
| resolveEmptyRoutesFallback | 无可用菜单时的兜底路由名(如强制改密) | function | — |
| afterUserLoaded | 拉完用户信息后的可选重定向 | function | — |
| tabs | 动态路由挂载后设置多标签路由名 | object | — |
| progress | 进度条钩子 | object | — |
| feedback | 错误提示 | object | — |
| redirectTo | 跳转授权中心 | function | window.location.href |
| isExternal | 外链判断,动态挂载时跳过 | function | 匹配 http(s): / mailto: / tel: |
AuthPermissionUserLike 额外可选:setRefreshToken、setExpiresIn(expiresInSeconds)。
执行流程
- OAuth 回调:URL 有
code且本地无可用 token(isAccessTokenValid)→ 校验error/state→ 换票写 token / refresh / expiresIn → 清地址栏 OAuth query →takeAndClearReturnPath跳回。 - 已登录仍带 OAuth query:
code+state同时存在时 Router replace 清掉,避免敏感参数残留。 - 白名单:放行;若目标是
loginPath且启用了useAuthServiceLogin,记忆redirect后跳转授权中心。 - 已登录:有用户信息则放行;否则
loadUserInfo→ 可选afterUserLoaded→ 挂动态路由;无菜单时先问resolveEmptyRoutesFallback,仍无则清登录态去 403。 - 未登录且未启用 OAuth:跳转本地登录页并带
redirect。 - 未登录且启用 OAuth:记忆地址后整页跳转授权中心,
next(false)。
createAuthApi
本地表单登录或 SSO 登出。注意默认路径语义不同:login 偏业务网关,logout 偏授权服务,必要时拆两个 HTTP 客户端。
参数
| 名称 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| http | 本地密码登录所用 HTTP | object | — |
| logoutHttp | 登出所用 HTTP;默认与 http 相同 | object | http |
| paths | 部分覆盖 defaultAuthApiPaths | object | login: '/auth/login'、logout: '/oauth/logout' |
| logoutMethod | 登出方法 | string | 'get' |
| getToken | 登出时手动带 Bearer(配合内部 withToken: false) | function | — |
方法
| 名称 | 说明 | 类型 |
|---|---|---|
| login | withToken/errorTip/transform 均关闭,便于宿主自行解包 | function |
| logout | 默认 GET;不过期刷新;可带手动 Authorization | function |
import { createAuthApi } from '@uniboot/auth'
import { authHttp } from '@/utils/request'
import { getToken } from '@/utils/auth'
const authApi = createAuthApi({
http: authHttp, // 若本地登录走网关,这里应传业务 request
logoutHttp: authHttp,
getToken,
})
export function logout() {
return authApi.logout()
}useLoginAccount({ api }) 把 LoginPage 表单值映射为登录请求(trim 用户名并透传验证码)。
AuthLoginPage
不启用 useAuthServiceLogin、在前端渲染登录表单时使用。内部已接 useLoginAccount;写 token、跳转仍由宿主在 onFinish 中完成。Auth 模板默认 SSO,不引用该组件。
属性
| 名称 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| api | createAuthApi 实例 | object | — |
| logo | Logo URL | string | — |
| illustration | 插画 URL | string | — |
| onFinish | 登录成功回调;result 为 api.login 返回值 | function | — |
| showCaptcha | 是否展示验证码 | boolean | false |
| getCaptcha | 获取验证码 | function | — |
| initialValues | 初始表单值 | object | — |
事件 / 插槽
| 名称 | 说明 |
|---|---|
language-change | 语言切换 |
#actions / #footer | 表单下方 / 左侧底部 |
i18n
mergeUnibootAuthLocales(i18n) 合并 authLocaleZhCn / authLocaleEn 到 zh_CN/zh-CN、en_US/en-US。命名空间为 unibootAuth.login.*。Auth 模板在 uniboot-admin 安装插件里与 admin 文案一并处理;账号域更多文案见 @uniboot/admin。