修复本地开发不走统一登陆
This commit is contained in:
Vendored
-1
@@ -31,7 +31,6 @@ declare module 'vue' {
|
|||||||
ElCollapse: typeof import('element-plus/es')['ElCollapse']
|
ElCollapse: typeof import('element-plus/es')['ElCollapse']
|
||||||
ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem']
|
ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem']
|
||||||
ElCollapseTransition: typeof import('element-plus/es')['ElCollapseTransition']
|
ElCollapseTransition: typeof import('element-plus/es')['ElCollapseTransition']
|
||||||
ElColorPicker: typeof import('element-plus/es')['ElColorPicker']
|
|
||||||
ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider']
|
ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider']
|
||||||
ElContainer: typeof import('element-plus/es')['ElContainer']
|
ElContainer: typeof import('element-plus/es')['ElContainer']
|
||||||
ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
|
ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
|
||||||
|
|||||||
@@ -394,7 +394,7 @@ const handleCommand = async (command) => {
|
|||||||
const tabsStore = useTabsStore();
|
const tabsStore = useTabsStore();
|
||||||
tabsStore.resetTabs();
|
tabsStore.resetTabs();
|
||||||
|
|
||||||
router.push('/login');
|
// clearToken 内部已跳认证中心登出页,本地登录页已下线
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Vendored
+6
@@ -26,6 +26,12 @@ declare module '@/stores/auth' {
|
|||||||
|
|
||||||
interface ImportMetaEnv {
|
interface ImportMetaEnv {
|
||||||
readonly VITE_API_BASE_URL: string;
|
readonly VITE_API_BASE_URL: string;
|
||||||
|
// 统一认证中心(本地登录已停用)
|
||||||
|
readonly VITE_AUTH_BASE?: string;
|
||||||
|
readonly VITE_AUTH_CLIENT_ID?: string;
|
||||||
|
readonly VITE_AUTH_REDIRECT_URI?: string;
|
||||||
|
readonly VITE_AUTH_POST_LOGOUT_URI?: string;
|
||||||
|
readonly VITE_AUTH_MODE?: string;
|
||||||
// 添加其他环境变量...
|
// 添加其他环境变量...
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+5
-2
@@ -14,6 +14,7 @@ import router from './router'
|
|||||||
import { loadAndAddDynamicRoutes } from './router'
|
import { loadAndAddDynamicRoutes } from './router'
|
||||||
import { createPinia } from 'pinia'
|
import { createPinia } from 'pinia'
|
||||||
import { useAuthStore } from './stores/auth'
|
import { useAuthStore } from './stores/auth'
|
||||||
|
import { redirectToAuthorize } from '@/utils/authClient'
|
||||||
// import { initTheme } from './utils/theme'
|
// import { initTheme } from './utils/theme'
|
||||||
// 导入全局组件
|
// 导入全局组件
|
||||||
import UmoEditor from '@/views/components/UmoEditor.vue';
|
import UmoEditor from '@/views/components/UmoEditor.vue';
|
||||||
@@ -53,8 +54,10 @@ if (authStore.isLoggedIn) {
|
|||||||
// 检查是否因为 token 无效而导致路由加载失败
|
// 检查是否因为 token 无效而导致路由加载失败
|
||||||
const token = localStorage.getItem('token');
|
const token = localStorage.getItem('token');
|
||||||
if (!token) {
|
if (!token) {
|
||||||
authStore.clearToken();
|
// 本地登录已停用:直接回认证中心重新登录(不再跳 #/login)
|
||||||
window.location.href = '#/login';
|
redirectToAuthorize().catch((err) => {
|
||||||
|
console.error('跳转统一认证失败:', err);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
+31
-34
@@ -1,6 +1,10 @@
|
|||||||
import { createRouter, createWebHashHistory } from "vue-router";
|
import { createRouter, createWebHashHistory } from "vue-router";
|
||||||
import { convertMenusToRoutes } from "./dynamicRoutes";
|
import { convertMenusToRoutes } from "./dynamicRoutes";
|
||||||
import { isSSOEnabled, redirectToAuthorize, ensureUserInfo } from "@/utils/authClient";
|
import { redirectToAuthorize, ensureUserInfo } from "@/utils/authClient";
|
||||||
|
|
||||||
|
// 本地登录已停用的历史路径:命中后统一改送认证中心,避免书签/后端回跳落到 404
|
||||||
|
const LEGACY_LOCAL_LOGIN_PATHS = ["/login", "/register", "/forget"];
|
||||||
|
|
||||||
|
|
||||||
// 静态子路由:需要在 Main 框架内显示的页面
|
// 静态子路由:需要在 Main 框架内显示的页面
|
||||||
const staticMainChildren = [
|
const staticMainChildren = [
|
||||||
@@ -127,26 +131,9 @@ const staticMainChildren = [
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
// 静态路由:登录页独立、home 导航门户独立、404 页面独立
|
// 静态路由:home 导航门户独立、404 页面独立
|
||||||
|
// 注意:本地账号密码登录入口(/login、/register、/forget)已下线,登录一律走统一认证中心
|
||||||
const staticRoutes = [
|
const staticRoutes = [
|
||||||
{
|
|
||||||
path: "/login",
|
|
||||||
name: "Login",
|
|
||||||
component: () => import("@/views/login/index.vue"),
|
|
||||||
meta: { requiresAuth: false }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "/register",
|
|
||||||
name: "Register",
|
|
||||||
component: () => import("@/views/login/register.vue"),
|
|
||||||
meta: { requiresAuth: false }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "/forget",
|
|
||||||
name: "ForgetPassword",
|
|
||||||
component: () => import("@/views/login/forget.vue"),
|
|
||||||
meta: { requiresAuth: false }
|
|
||||||
},
|
|
||||||
// 统一认证中心回跳页(接收 code 换取令牌,页面自身不展示业务内容)
|
// 统一认证中心回跳页(接收 code 换取令牌,页面自身不展示业务内容)
|
||||||
{
|
{
|
||||||
path: "/auth/callback",
|
path: "/auth/callback",
|
||||||
@@ -310,42 +297,52 @@ function findFirstValidRoute(routes) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 跳转统一认证中心;地址未配置时给出提示,避免白屏 */
|
||||||
|
async function gotoAuthorize() {
|
||||||
|
try {
|
||||||
|
await redirectToAuthorize();
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
window.alert(e?.message || "统一认证跳转失败,请检查认证中心配置");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
router.beforeEach(async (to, from, next) => {
|
router.beforeEach(async (to, from, next) => {
|
||||||
const token = localStorage.getItem("token");
|
const token = localStorage.getItem("token");
|
||||||
const publicPaths = ["/login", "/register", "/forget", "/auth/callback"];
|
|
||||||
|
|
||||||
if (publicPaths.includes(to.path)) {
|
// 认证中心回跳页:始终放行,由页面自身完成 code 换令牌
|
||||||
|
if (to.path === "/auth/callback") {
|
||||||
|
next();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 已下线的本地登录入口:已登录回首页,未登录送认证中心
|
||||||
|
if (LEGACY_LOCAL_LOGIN_PATHS.includes(to.path)) {
|
||||||
if (token) {
|
if (token) {
|
||||||
if (!dynamicRoutesAdded) {
|
if (!dynamicRoutesAdded) {
|
||||||
await loadAndAddDynamicRoutes();
|
await loadAndAddDynamicRoutes();
|
||||||
}
|
}
|
||||||
next({ path: "/home" });
|
next({ path: "/home" });
|
||||||
} else {
|
} else {
|
||||||
next();
|
await gotoAuthorize();
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 未登录:统一走认证中心(本地登录已停用)
|
||||||
if (!token) {
|
if (!token) {
|
||||||
// 统一认证模式:跳认证中心登录(未开启时走原有本地登录页)
|
await gotoAuthorize();
|
||||||
if (isSSOEnabled()) {
|
|
||||||
await redirectToAuthorize();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
next({ path: "/login", query: { redirect: to.path } });
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 统一认证模式:补全用户信息(旧会话可能只有 token 没有 userInfo,
|
// 补全用户信息(旧会话可能只有 token 没有 userInfo,或迁移后 userInfo 缺失 id,
|
||||||
// 或迁移后 userInfo 缺失 id,会导致菜单等接口报「用户ID不存在」)。
|
// 会导致菜单等接口报「用户ID不存在」)。
|
||||||
// 令牌失效时 ensureUserInfo 会清空登录态并返回 null,此时重新登录。
|
// 令牌失效时 ensureUserInfo 会清空登录态并返回 null,此时重新登录。
|
||||||
if (isSSOEnabled()) {
|
|
||||||
const info = await ensureUserInfo();
|
const info = await ensureUserInfo();
|
||||||
if (!info) {
|
if (!info) {
|
||||||
await redirectToAuthorize();
|
await gotoAuthorize();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (!dynamicRoutesAdded) {
|
if (!dynamicRoutesAdded) {
|
||||||
await loadAndAddDynamicRoutes();
|
await loadAndAddDynamicRoutes();
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
import { ref, reactive } from 'vue'
|
import { ref, reactive } from 'vue'
|
||||||
import { getCurrentUser } from '@/api/login'
|
import { getCurrentUser } from '@/api/login'
|
||||||
import { isSSOEnabled, logoutSSO } from '@/utils/authClient'
|
import { logoutSSO } from '@/utils/authClient'
|
||||||
|
|
||||||
// 用户信息类型
|
// 用户信息类型
|
||||||
const defaultUser = {
|
const defaultUser = {
|
||||||
@@ -94,11 +94,10 @@ export const useAuthStore = defineStore('auth', () => {
|
|||||||
Object.assign(user, defaultUser)
|
Object.assign(user, defaultUser)
|
||||||
localStorage.removeItem('token')
|
localStorage.removeItem('token')
|
||||||
localStorage.removeItem('userInfo')
|
localStorage.removeItem('userInfo')
|
||||||
// 统一认证模式:吊销令牌并跳认证中心完成单点登出(所有调用点自动生效)
|
// 统一认证:吊销令牌并跳认证中心完成单点登出(所有调用点自动生效,
|
||||||
if (isSSOEnabled()) {
|
// 调用方不要再 router.push('/login'),本地登录页已下线)
|
||||||
logoutSSO()
|
logoutSSO()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// 检查认证状态
|
// 检查认证状态
|
||||||
function checkAuth() {
|
function checkAuth() {
|
||||||
|
|||||||
@@ -1,30 +1,47 @@
|
|||||||
/**
|
/**
|
||||||
* 统一认证中心(UAC)客户端 —— OIDC Authorization Code + PKCE
|
* 统一认证中心(UAC)客户端 —— OIDC Authorization Code + PKCE
|
||||||
*
|
*
|
||||||
* 接入方式:在 .env 中配置
|
* 本项目已停用本地账号密码登录(/backend/login、/backend/register 等入口均已下线),
|
||||||
* VITE_AUTH_MODE=sso 开启统一认证(不配置则走原有 /backend/login)
|
* 登录、登出、令牌续期一律走认证中心。
|
||||||
* VITE_AUTH_BASE=https://api.yunzer.cn/auth
|
*
|
||||||
|
* 环境变量(见 .env.development / .env.production):
|
||||||
|
* VITE_AUTH_BASE=https://api.yunzer.cn/auth 认证中心地址,缺省回退到 ${VITE_API_BASE_URL}/auth
|
||||||
* VITE_AUTH_CLIENT_ID=yz-backend
|
* VITE_AUTH_CLIENT_ID=yz-backend
|
||||||
* VITE_AUTH_REDIRECT_URI=https://back.yunzer.cn/#/auth/callback
|
* VITE_AUTH_REDIRECT_URI=https://back.yunzer.cn/#/auth/callback
|
||||||
|
* VITE_AUTH_POST_LOGOUT_URI=https://back.yunzer.cn/#/
|
||||||
*
|
*
|
||||||
* 说明:token 仍存在 localStorage(沿用现有 utils/request.js 与 stores/auth.js),
|
* 说明:token 仍存在 localStorage(沿用现有 utils/request.js 与 stores/auth.js),
|
||||||
* 因此接入后业务代码无需改动即可带上 Authorization 头。
|
* 因此业务代码无需改动即可带上 Authorization 头。
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const AUTH_BASE = import.meta.env.VITE_AUTH_BASE || "";
|
// 未显式配置时回退到「接口地址 + /auth」,避免跳转到空地址或前端自身域名
|
||||||
|
const API_BASE = (import.meta.env.VITE_API_BASE_URL || "").replace(/\/+$/, "");
|
||||||
|
const AUTH_BASE = (import.meta.env.VITE_AUTH_BASE || (API_BASE ? `${API_BASE}/auth` : "")).replace(
|
||||||
|
/\/+$/,
|
||||||
|
""
|
||||||
|
);
|
||||||
const CLIENT_ID = import.meta.env.VITE_AUTH_CLIENT_ID || "yz-backend";
|
const CLIENT_ID = import.meta.env.VITE_AUTH_CLIENT_ID || "yz-backend";
|
||||||
const REDIRECT_URI =
|
const REDIRECT_URI =
|
||||||
import.meta.env.VITE_AUTH_REDIRECT_URI || `${window.location.origin}/#/auth/callback`;
|
import.meta.env.VITE_AUTH_REDIRECT_URI || `${window.location.origin}/#/auth/callback`;
|
||||||
const POST_LOGOUT_URI =
|
const POST_LOGOUT_URI =
|
||||||
import.meta.env.VITE_AUTH_POST_LOGOUT_URI || `${window.location.origin}/#/login`;
|
import.meta.env.VITE_AUTH_POST_LOGOUT_URI || `${window.location.origin}/#/`;
|
||||||
|
|
||||||
const ACCESS_KEY = "token"; // 与现有代码保持一致
|
const ACCESS_KEY = "token"; // 与现有代码保持一致
|
||||||
const REFRESH_KEY = "auth_refresh_token";
|
const REFRESH_KEY = "auth_refresh_token";
|
||||||
const SID_KEY = "auth_sid";
|
const SID_KEY = "auth_sid";
|
||||||
|
|
||||||
/** 是否启用统一认证(默认关闭,保持原有登录方式) */
|
/**
|
||||||
|
* 是否启用统一认证。
|
||||||
|
* 本地登录已停用,此处恒为 true;保留函数是为了兼容既有调用点,
|
||||||
|
* 后续清理时可直接删除所有分支判断。
|
||||||
|
*/
|
||||||
export function isSSOEnabled() {
|
export function isSSOEnabled() {
|
||||||
return import.meta.env.VITE_AUTH_MODE === "sso" && !!AUTH_BASE;
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 认证中心地址是否可用(排查配置缺失用) */
|
||||||
|
export function hasAuthBase() {
|
||||||
|
return !!AUTH_BASE;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------- PKCE
|
// ---------------------------------------------------------------- PKCE
|
||||||
@@ -76,14 +93,26 @@ export function getAccessToken() {
|
|||||||
|
|
||||||
// ---------------------------------------------------------------- 主流程
|
// ---------------------------------------------------------------- 主流程
|
||||||
|
|
||||||
/** 跳转认证中心登录(带 PKCE 与 state) */
|
/**
|
||||||
|
* 跳转认证中心登录(带 PKCE 与 state)。
|
||||||
|
*
|
||||||
|
* 入口取认证中心登录页而不是直接打 authorize:
|
||||||
|
* GET {AUTH_BASE}/login?client_id=xxx&redirect=<base64url(authorize URL)>
|
||||||
|
* 认证中心侧约定:
|
||||||
|
* - 未登录 → 展示登录页,登录成功后 302 回 redirect 指向的 authorize;
|
||||||
|
* - 已登录 → 立即 302 回 authorize,由 authorize 识别会话后签发 code;
|
||||||
|
* 最终仍由 authorize 校验 redirect_uri 白名单并携带 code 回跳本端。
|
||||||
|
*/
|
||||||
export async function redirectToAuthorize() {
|
export async function redirectToAuthorize() {
|
||||||
|
if (!AUTH_BASE) {
|
||||||
|
throw new Error("统一认证地址未配置:请检查 VITE_AUTH_BASE 或 VITE_API_BASE_URL");
|
||||||
|
}
|
||||||
const { verifier, challenge } = await generatePKCE();
|
const { verifier, challenge } = await generatePKCE();
|
||||||
sessionStorage.setItem("pkce_verifier", verifier);
|
sessionStorage.setItem("pkce_verifier", verifier);
|
||||||
const state = randomString(24);
|
const state = randomString(24);
|
||||||
sessionStorage.setItem("oidc_state", state);
|
sessionStorage.setItem("oidc_state", state);
|
||||||
|
|
||||||
const params = new URLSearchParams({
|
const authorizeURL = `${AUTH_BASE}/authorize?${new URLSearchParams({
|
||||||
client_id: CLIENT_ID,
|
client_id: CLIENT_ID,
|
||||||
redirect_uri: REDIRECT_URI,
|
redirect_uri: REDIRECT_URI,
|
||||||
response_type: "code",
|
response_type: "code",
|
||||||
@@ -91,8 +120,13 @@ export async function redirectToAuthorize() {
|
|||||||
state,
|
state,
|
||||||
code_challenge: challenge,
|
code_challenge: challenge,
|
||||||
code_challenge_method: "S256",
|
code_challenge_method: "S256",
|
||||||
});
|
}).toString()}`;
|
||||||
window.location.href = `${AUTH_BASE}/authorize?${params.toString()}`;
|
|
||||||
|
// redirect 参数由认证中心按 base64.RawURLEncoding 解析(无 = 填充)
|
||||||
|
const redirectParam = base64url(new TextEncoder().encode(authorizeURL));
|
||||||
|
window.location.href =
|
||||||
|
`${AUTH_BASE}/login?client_id=${encodeURIComponent(CLIENT_ID)}` +
|
||||||
|
`&redirect=${redirectParam}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 处理认证中心回跳:用 code 换令牌 */
|
/** 处理认证中心回跳:用 code 换令牌 */
|
||||||
@@ -153,6 +187,7 @@ export async function refreshAccessToken() {
|
|||||||
|
|
||||||
/** 单点登出:吊销令牌后回认证中心登出页 */
|
/** 单点登出:吊销令牌后回认证中心登出页 */
|
||||||
export async function logoutSSO() {
|
export async function logoutSSO() {
|
||||||
|
if (!AUTH_BASE) return;
|
||||||
const token = getAccessToken();
|
const token = getAccessToken();
|
||||||
try {
|
try {
|
||||||
await fetch(`${AUTH_BASE}/revoke`, {
|
await fetch(`${AUTH_BASE}/revoke`, {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { isSSOEnabled, refreshAccessToken, clearTokens, redirectToAuthorize } from '@/utils/authClient';
|
import { refreshAccessToken, clearTokens, redirectToAuthorize } from '@/utils/authClient';
|
||||||
|
|
||||||
// 统一认证模式下 401 后正在跳转登录的标志,避免并发请求同时触发跳转造成死循环
|
// 统一认证模式下 401 后正在跳转登录的标志,避免并发请求同时触发跳转造成死循环
|
||||||
let ssoRedirecting = false;
|
let ssoRedirecting = false;
|
||||||
@@ -51,9 +51,8 @@ service.interceptors.response.use(
|
|||||||
const bizMsg = error.response.data?.msg || error.response.data?.message;
|
const bizMsg = error.response.data?.msg || error.response.data?.message;
|
||||||
switch (error.response.status) {
|
switch (error.response.status) {
|
||||||
case 401:
|
case 401:
|
||||||
// 统一认证模式:先用 refresh_token 静默换取新令牌并重放原请求,
|
// 统一认证:先用 refresh_token 静默换取新令牌并重放原请求,
|
||||||
// 刷新失败(令牌过期/被吊销)才真正登出。
|
// 刷新失败(令牌过期/被吊销/旧本地登录遗留 token)才真正登出。
|
||||||
if (isSSOEnabled()) {
|
|
||||||
try {
|
try {
|
||||||
const newToken = await refreshAccessToken();
|
const newToken = await refreshAccessToken();
|
||||||
error.config.headers['Authorization'] = `Bearer ${newToken}`;
|
error.config.headers['Authorization'] = `Bearer ${newToken}`;
|
||||||
@@ -64,18 +63,13 @@ service.interceptors.response.use(
|
|||||||
localStorage.removeItem('userInfo');
|
localStorage.removeItem('userInfo');
|
||||||
if (!ssoRedirecting) {
|
if (!ssoRedirecting) {
|
||||||
ssoRedirecting = true;
|
ssoRedirecting = true;
|
||||||
redirectToAuthorize();
|
redirectToAuthorize().catch((err) => {
|
||||||
|
console.error('跳转统一认证失败:', err);
|
||||||
|
ssoRedirecting = false;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return Promise.reject(new Error('token无效,请重新登录'));
|
return Promise.reject(new Error('token无效,请重新登录'));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
console.error('未授权,请重新登录');
|
|
||||||
localStorage.removeItem('token');
|
|
||||||
localStorage.removeItem('userInfo');
|
|
||||||
if (window.location.hash !== '#/login') {
|
|
||||||
window.location.href = '#/login';
|
|
||||||
}
|
|
||||||
return Promise.reject(new Error('token无效'));
|
|
||||||
case 404:
|
case 404:
|
||||||
console.error('请求的资源不存在');
|
console.error('请求的资源不存在');
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -409,7 +409,7 @@ async function handleLogout() {
|
|||||||
localStorage.removeItem("tenant");
|
localStorage.removeItem("tenant");
|
||||||
sessionStorage.removeItem("tenant");
|
sessionStorage.removeItem("tenant");
|
||||||
menuStore.resetMenus();
|
menuStore.resetMenus();
|
||||||
router.push("/login");
|
// clearToken 内部已跳认证中心登出页,本地登录页已下线
|
||||||
}
|
}
|
||||||
|
|
||||||
// 加载模块列表
|
// 加载模块列表
|
||||||
|
|||||||
@@ -60,7 +60,6 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted } from "vue";
|
import { ref, onMounted } from "vue";
|
||||||
import { useRouter } from "vue-router";
|
|
||||||
import { ElMessage, ElMessageBox } from "element-plus";
|
import { ElMessage, ElMessageBox } from "element-plus";
|
||||||
import { getUserInfo } from "@/api/user";
|
import { getUserInfo } from "@/api/user";
|
||||||
import { useAuthStore } from "@/stores/auth";
|
import { useAuthStore } from "@/stores/auth";
|
||||||
@@ -76,7 +75,6 @@ import BindPhoneDialog from "./components/BindPhoneDialog.vue";
|
|||||||
import BindEmailDialog from "./components/BindEmailDialog.vue";
|
import BindEmailDialog from "./components/BindEmailDialog.vue";
|
||||||
import WechatBindCard from "./components/WechatBindCard.vue";
|
import WechatBindCard from "./components/WechatBindCard.vue";
|
||||||
|
|
||||||
const router = useRouter();
|
|
||||||
const authStore = useAuthStore();
|
const authStore = useAuthStore();
|
||||||
|
|
||||||
const userInfo = ref(null);
|
const userInfo = ref(null);
|
||||||
@@ -179,8 +177,8 @@ const handleLogout = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// clearToken 内部会吊销令牌并跳认证中心登出页,此处不再跳本地登录页
|
||||||
authStore.clearToken();
|
authStore.clearToken();
|
||||||
router.push("/login");
|
|
||||||
ElMessage.success("已退出登录");
|
ElMessage.success("已退出登录");
|
||||||
}).catch(() => {});
|
}).catch(() => {});
|
||||||
};
|
};
|
||||||
|
|||||||
Vendored
+10
-1
@@ -2,9 +2,18 @@
|
|||||||
|
|
||||||
interface ImportMetaEnv {
|
interface ImportMetaEnv {
|
||||||
readonly VITE_API_BASE_URL: string
|
readonly VITE_API_BASE_URL: string
|
||||||
|
/** 统一认证中心地址,如 https://api.yunzer.cn/auth */
|
||||||
|
readonly VITE_AUTH_BASE?: string
|
||||||
|
/** 统一认证客户端 ID,默认 yz-backend */
|
||||||
|
readonly VITE_AUTH_CLIENT_ID?: string
|
||||||
|
/** 授权回跳地址,必须与认证中心登记的 redirect_uris 完全一致 */
|
||||||
|
readonly VITE_AUTH_REDIRECT_URI?: string
|
||||||
|
/** 单点登出后回跳地址 */
|
||||||
|
readonly VITE_AUTH_POST_LOGOUT_URI?: string
|
||||||
|
/** 兼容标记:本地登录已停用,该值不再决定登录方式 */
|
||||||
|
readonly VITE_AUTH_MODE?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ImportMeta {
|
interface ImportMeta {
|
||||||
readonly env: ImportMetaEnv
|
readonly env: ImportMetaEnv
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+24
-42
@@ -5,17 +5,15 @@ runmode = dev
|
|||||||
# 启用请求体复制(允许多次读取请求体)
|
# 启用请求体复制(允许多次读取请求体)
|
||||||
copyrequestbody = true
|
copyrequestbody = true
|
||||||
|
|
||||||
# 服务器超时配置(支持大文件上传)
|
# ==================== 服务器超时 / 请求体大小 ====================
|
||||||
# 0 表示不设置超时限制
|
# 0 表示不设置超时限制
|
||||||
ServerTimeOut = 0
|
ServerTimeOut = 0
|
||||||
# 最大请求体大小(字节),0 表示不限制
|
|
||||||
MaxMemory = 0
|
|
||||||
|
|
||||||
# 最大请求体大小(用于普通请求,10MB)
|
# 最大请求体大小(字节)。本地开发建议统一为 10MB,避免误传大文件打爆内存。
|
||||||
maxmemory = 10485760
|
maxmemory = 10485760
|
||||||
|
|
||||||
# 数据库配置
|
# ==================== 数据库配置 ====================
|
||||||
# MySQL - 远程连接配置
|
# MySQL - 远程连接配置(本地开发连线上测试库)
|
||||||
mysqluser = root
|
mysqluser = root
|
||||||
mysqlpass = Lzq920103
|
mysqlpass = Lzq920103
|
||||||
# mysqlurls = 10.31.100.4:3306
|
# mysqlurls = 10.31.100.4:3306
|
||||||
@@ -25,7 +23,7 @@ mysqldb = go-platform
|
|||||||
# ORM配置
|
# ORM配置
|
||||||
orm = mysql
|
orm = mysql
|
||||||
|
|
||||||
# 配置静态文件目录
|
# ==================== 静态文件目录 ====================
|
||||||
# 映射 /static 路径到前端 dist 目录
|
# 映射 /static 路径到前端 dist 目录
|
||||||
# StaticDir = /static:../front/dist
|
# StaticDir = /static:../front/dist
|
||||||
|
|
||||||
@@ -33,55 +31,45 @@ orm = mysql
|
|||||||
StaticDir = /uploads:../uploads
|
StaticDir = /uploads:../uploads
|
||||||
|
|
||||||
# ==================== 租户自有域名绑定 / 自动 HTTPS ====================
|
# ==================== 租户自有域名绑定 / 自动 HTTPS ====================
|
||||||
# 租户把自己的子域名 CNAME 到这个主机名(该主机名需先 A 记录指向本服务器)
|
# 本地开发不涉及,留空即可
|
||||||
# 例:customdomain_cname_target = sites.yunzer.com.cn
|
|
||||||
customdomain_cname_target =
|
customdomain_cname_target =
|
||||||
|
|
||||||
# 允许的 A 记录目标 IP(多个用英文逗号分隔)。根域通常不能用 CNAME,只能 A 记录。
|
|
||||||
# 例:customdomain_a_ips = 1.2.3.4,5.6.7.8
|
|
||||||
customdomain_a_ips =
|
customdomain_a_ips =
|
||||||
|
|
||||||
# Let's Encrypt 账号邮箱(证书到期提醒、账号注册用)
|
|
||||||
acme_email =
|
acme_email =
|
||||||
|
|
||||||
# ACME 目录地址。留空使用 Let's Encrypt 正式环境。
|
|
||||||
# 联调时建议先填 staging 避免触发正式环境频控:
|
|
||||||
# acme_directory = https://acme-staging-v02.api.letsencrypt.org/directory
|
|
||||||
acme_directory =
|
acme_directory =
|
||||||
|
|
||||||
# 证书落盘根目录。签发后生成 {ssl_cert_dir}/{域名}/fullchain.pem 与 privkey.pem,
|
|
||||||
# Nginx 扑底站点按 SNI 读取该目录。Go 进程需要对此目录有写权限。
|
|
||||||
ssl_cert_dir = /www/wwwroot/ssl-certs
|
ssl_cert_dir = /www/wwwroot/ssl-certs
|
||||||
|
|
||||||
# ==================== 统一认证中心(UAC)基础配置 ====================
|
# ==================== 统一认证中心(UAC)基础配置 ====================
|
||||||
# 全局 JWT 鉴权模式:off=不启用(等同改造前)/ warn=观察模式只记日志不拦截 / on=真正拦截401
|
# 全局 JWT 鉴权模式:off=不启用 / warn=观察模式只记日志不拦截 / on=真正拦截401
|
||||||
# 上线步骤:先 warn 跑一段时间核对日志,确认白名单无遗漏后再改 on
|
# 本地开发建议 off 或 warn,避免调试接口被 401 拦截
|
||||||
auth_enforce = warn
|
auth_enforce = warn
|
||||||
# 额外免鉴权路径前缀(在内置白名单之外追加),多个用英文逗号分隔
|
# 额外免鉴权路径前缀(在内置白名单之外追加),多个用英文逗号分隔
|
||||||
auth_whitelist =
|
auth_whitelist =
|
||||||
|
|
||||||
# JWT 签发者(统一认证中心地址)
|
# JWT 签发者(统一认证中心地址)
|
||||||
jwt_issuer = https://api.yunzer.cn/auth
|
jwt_issuer = https://api.yunzer.cn/auth
|
||||||
# HS256 主密钥:留空则沿用内置默认密钥(兼容历史 token),生产环境务必配置
|
# HS256 主密钥:留空则沿用内置默认密钥(兼容历史 token)
|
||||||
# 注意:修改后所有已签发的旧 token 立即失效,需在业务低峰期操作
|
|
||||||
jwt_secret = peaceandlove
|
jwt_secret = peaceandlove
|
||||||
# HS256 轮换密钥(用于平滑换密钥),格式:kid1:secret1,kid2:secret2
|
# HS256 轮换密钥(用于平滑换密钥),格式:kid1:secret1,kid2:secret2
|
||||||
jwt_secrets =
|
jwt_secrets =
|
||||||
|
|
||||||
# RS256 密钥对(P1 认证中心启用;未配置时自动回落 HS256,不影响启动)
|
# RS256 密钥对(认证中心签发用私钥,业务端验签只用公钥)
|
||||||
# 生成命令:
|
#
|
||||||
# openssl genpkey -algorithm RSA -out jwt_rsa_private.pem -pkeyopt rsa_keygen_bits:2048
|
# 本地开发对接线上认证中心(https://api.yunzer.cn/auth)时:
|
||||||
# openssl rsa -pubout -in jwt_rsa_private.pem -out jwt_rsa_public.pem
|
# - token 由线上用「线上私钥」签发(jwtutil 固定 Alg=RS256,见 services/auth/token.go)
|
||||||
# 私钥文件不要提交到代码仓库
|
# - 本地业务接口必须用「线上公钥」验签,否则一律 401「无效的token」
|
||||||
jwt_rsa_private_key_file = E:\Demo\ssh\jwt_rsa_private.pem
|
# - conf/jwt_rsa_public.pem 即线上 /auth/jwks.json 还原出的公钥(kid=rsa-1)
|
||||||
jwt_rsa_public_key_file = E:\Demo\ssh\jwt_rsa_public.pem
|
#
|
||||||
|
# 注意:不要在这里启用与上面公钥不配对的私钥,否则本地签发的 RS256 token 会验签失败。
|
||||||
|
# 仅当需要在本地自建认证中心并签发 RS256 时才配置私钥,且公钥必须与它配对。
|
||||||
|
jwt_rsa_private_key_file =
|
||||||
|
jwt_rsa_public_key_file = E:\Demos\DemoOwns\Go\yunzerwebsiteallinone\go\conf\jwt_rsa_public.pem
|
||||||
jwt_rsa_kid = rsa-1
|
jwt_rsa_kid = rsa-1
|
||||||
|
|
||||||
# ==================== 微信公众号(服务号) ====================
|
# ==================== 微信公众号(服务号) ====================
|
||||||
# 服务号 AppSecret / EncodingAESKey 入库加密密钥:任意随机串即可,代码内部做 SHA-256 派生。
|
# 服务号 AppSecret / EncodingAESKey 入库加密密钥:任意随机串即可,代码内部做 SHA-256 派生。
|
||||||
# 注意:更换该值后,历史已保存的公众号密钥将无法解密,需在「通知设置 → 微信配置」重新保存一次。
|
# 注意:更换该值后,历史已保存的公众号密钥将无法解密,需在「通知设置 → 微信配置」重新保存一次。
|
||||||
wechat_mp_secret_key = 8f3c2a1d94b74e28a6c5f0187d3e9b4c72a5d0e6f13b8c47d92a6e5f0b7c3148
|
wechat_mp_secret_key = 8f3c2a1d94b74e28a6c5f0187d3e9b4c72a5d0e6f13b8c47d92a6e5f0b7c3148
|
||||||
# 微信服务器回调地址基址(默认回落 payment_callback_base,即 https://api.yunzer.cn):
|
# 微信服务器回调地址基址(默认回落 payment_callback_base):
|
||||||
# wechat_mp_callback_base = https://api.yunzer.cn
|
# wechat_mp_callback_base = https://api.yunzer.cn
|
||||||
|
|
||||||
# ==================== 支付模块 ====================
|
# ==================== 支付模块 ====================
|
||||||
@@ -89,15 +77,9 @@ wechat_mp_secret_key = 8f3c2a1d94b74e28a6c5f0187d3e9b4c72a5d0e6f13b8c47d92a6e5f0
|
|||||||
# 注意:更换该值后,历史已保存的渠道参数将无法解密,需在各渠道配置页重新保存一次。
|
# 注意:更换该值后,历史已保存的渠道参数将无法解密,需在各渠道配置页重新保存一次。
|
||||||
payment_secret_key = f656fe85ed6a1caba9f19966d8cdde46114709969bea5875cde7664d4ad9f535
|
payment_secret_key = f656fe85ed6a1caba9f19966d8cdde46114709969bea5875cde7664d4ad9f535
|
||||||
# 渠道异步回调的对外基址:最终回调地址 = {payment_callback_base}/api/payment/callback/{渠道}
|
# 渠道异步回调的对外基址:最终回调地址 = {payment_callback_base}/api/payment/callback/{渠道}
|
||||||
# TODO: 填你自己服务器的公网 HTTPS 域名(如 https://api.yunzer.com.cn),不是申请来的。
|
|
||||||
# 要求:公网可访问、HTTPS、无鉴权无重定向;微信 JSAPI 还需在商户平台「支付授权目录」登记同一域名。
|
|
||||||
# 本地开发收不到渠道回调,留空即可(回调地址退化为相对路径 /api/payment/callback/{渠道},由网关补齐域名)。
|
# 本地开发收不到渠道回调,留空即可(回调地址退化为相对路径 /api/payment/callback/{渠道},由网关补齐域名)。
|
||||||
payment_callback_base = https://api.yunzer.cn
|
payment_callback_base =
|
||||||
|
|
||||||
# 渠道证书落盘根目录(各渠道目录的父目录,实际路径 = {payment_cert_dir}/{渠道}/xxx.pem)。
|
# 渠道证书落盘根目录(各渠道目录的父目录,实际路径 = {payment_cert_dir}/{渠道}/xxx.pem)。
|
||||||
# 留空时按进程工作目录下的 certs/payment 处理。
|
# 本地开发如需测试支付证书,填本地绝对路径;否则留空,按进程工作目录下的 certs/payment 处理。
|
||||||
# 历史数据里 cert_json 存的是相对路径(相对「当初上传时」的进程工作目录),
|
payment_cert_dir =
|
||||||
# 若服务的工作目录与当初不一致(例如上传时是 /www/wwwroot/api.yunzer.cn,现在跑在 /app),
|
|
||||||
# 把这里配成当初上传用的绝对目录即可继续读到旧证书,无需重新上传:
|
|
||||||
payment_cert_dir = /www/wwwroot/api.yunzer.cn/certs/payment/wechat
|
|
||||||
# payment_cert_dir =
|
|
||||||
Reference in New Issue
Block a user