大家好,我是卡卡。在我之前公司的時候,很多業務都需要做私有化部署,加上專案基本都是微服務拆分的模式,一個業務往往會衍生出好幾個後台系統,而且還要區分測試後台、正式後台、日誌監控、運維工具、發佈系統之類的。時間長了,各種子系統越建越多,部署的時候也全是靠我們自己手動搞,所以後期基本就是一堆系統散落在不同伺服器上。
特別是我負責專案多的時候,手上要維護的後台實在太多了。用的時候只能把常用的先固定在瀏覽器標籤欄裡,但只要換個設備、重裝個瀏覽器,或者清理一下快取,這些標籤全沒了,結果又得去聊天記錄、文件裡一條條翻,才能把後台入口找回來。
更煩的是老闆。這個大忙人明明後台地址他自己可以收藏一下,但從來不存,每次要看點數據就來問我要後台地址和帳號密碼。問得多了我自己都煩了。
所以我後來抽空把這些零散的後台統一起來,做了一個“統一門戶管理中心”。所有子系統的入口都放在一個頁面裡,登入一次之後,使用者就能進入自己有權限的所有後台,不用再滿世界找連結,也不用每個系統都重新登入。對老闆、對運營、對我們開發來說,都省事很多,對後期擴系統的管理也輕鬆不少。
在開始設計這套東西之前,我首先想到的是用單點登入(SSO)機制。傳統 SSO 的流程是這樣的:使用者訪問 A 系統,發現沒登入,被重定向到認證中心;登入成功後再帶著 ticket/code 回來。之後使用者訪問 B 系統時,因為已經登入過認證中心,就不需要再登入一遍。
這個機制本身沒問題,但和我想做的東西不完全一樣。傳統 SSO 是“先訪問系統,再登入”。而我們的內部後台更多是“先登入門戶,再從門戶進去”。使用者不會一上來就直接訪問某個子系統,而是先打開統一入口,然後從入口進入對應的後台。
所以我就在想,既然我們的使用場景不是“從子系統開始”,那完全照搬傳統 SSO 的那套流程,其實就有點不對路。傳統 SSO 更像是面向對外的業務系統,比如使用者是直接訪問 A、B、C 這些系統的,需要 SSO 去幫他們統一一次登入。但我們這種內部後台恰好相反——大家習慣是先打開一個入口頁面,然後從這個入口去點各個後台,這一點和傳統 SSO 的預設前提完全不一樣。
同時我們公司裡面的後台系統挺多,而且技術棧也不統一。有 Java 的、有 PHP 的、有 Go 的,還有一些是第三方的自帶後台。要讓這些系統全部按照 SSO 的規範來改造一遍,不但成本高,而且每個系統都要對接 SSO 的重定向、ticket 驗證、登入態同步,工作量特別大,也容易出問題。
所以我最後的思路是:不用大廠那套很重的 SSO,也不用讓所有子系統都去支持單點登入,而是做一個輕量一點、貼合我們內部使用習慣的方式。既然大家是從門戶進的,那就乾脆以門戶為中心,把登入這件事情都集中放在門戶完成;進入子系統的時候再給子系統一段臨時授權碼,用這段授權碼去認證中心換使用者資訊,然後子系統再生成自己的 token 就好了。
這樣設計我覺得有這幾個好處:
說白了,就是把“統一入口 + 簡化授權”的方式結合起來,比傳統 SSO 更輕,更適合我們這種內部私有化部署、多後台的場景。
在確定整體方向之前,我參考過好幾種常見的授權模式。畢竟我們內部的後台系統數量多、技術棧也不統一,Java、PHP、Go 都有,而且部署環境分散,想做統一登入的話,前期選哪種方案基本上決定了後面接入會不會痛苦。
最開始想的是“要不要直接統一 token?”。也就是說,門戶登入之後生成一個 JWT,然後所有子系統都用同一個 token 驗證。這個做法看起來簡單粗暴,但問題也很明顯:所有子系統必須共享同一套密鑰,一旦某個子系統洩露密鑰,所有後台的登入安全就全完了。而且不同系統可能對 token 的字段、時效、校驗邏輯都有自己的需求,統一 token 反而會增加耦合。
後來也想過“要不要走 OAuth2 授權碼模式?”。這種是大廠常用的做法,流程很標準,但也太重了。要讓每個子系統都遵循 OAuth2 的規範、處理 redirect、處理 state、處理 token 交換等等,對我們這種內部後台來說有點過度設計了,而且成本高、實現複雜,不太現實。
再往後就是傳統的單點登入(SSO 或 CAS)那類方案。但前面也說了,那一套是“從子系統出發”的:系統 A → 認證中心 → 回 A,再訪問 B 又是另一套流程。我們的使用路徑是從門戶進入,而不是從某個子系統開始,這種場景下傳統的 SSO 流程套上去並不貼合,還會讓子系統改造量特別大。
綜合對比下來,最終我選擇了“統一門戶 + 一次性 code”的組合方式:
門戶負責展示所有系統入口,使用者先在門戶登入;進入子系統時由門戶生成一個一次性 code,子系統拿 code 去認證中心換使用者資訊,然後再在本地生成自己的 token。
這個模式兼顧了安全性、擴展性和接入成本,也不依賴某種技術棧,所有語言的後台都能很輕鬆接入,非常適合我們這種內部私有化、多後台的環境。
整個流程大概是這樣的:

使用者不會直接進入某個子系統,而是先打開統一門戶並在門戶登入。登入成功後,門戶會把使用者有權限的後台系統都展示出來。使用者點擊某個系統入口時,門戶會向認證中心申請一個一次性 code,然後帶著這個 code 把使用者重定向到對應的子系統。
子系統拿到 code 之後,會再去找認證中心驗證這個 code 並換取使用者資訊。拿到 userInfo 後,子系統自己在本地生成一個 token,後面使用者在這個子系統裡所有的請求,會通過這個本地 token 來做校驗,而不再依賴門戶。
這種模式的好處是比較明顯的:使用者體驗上等同於單點登入,登入一次即可進入多個後台;實現上又比傳統 SSO 要輕得多,不需要每個子系統都對接完整的重定向流程,也不用統一 token 格式,更適合內部系統多、語言雜、環境分散的情況。
在搭這個統一門戶之前,我先想清楚一個問題:既然它要承載公司所有內部後台,那它本身必須也得是一個標準的後台系統,有自己的模組、有自己的管理邏輯,不能只是把連結拼一拼就完了。
所以我給它拆了幾個核心板塊,每個板塊都是圍繞我們內部真實的使用場景來的。
1. 主页(系統入口總覽)
這個門戶的核心就是“把所有內部系統展示出來”。同事登入之後,首頁看到的就是自己有權限訪問的所有子系統,按業務分類排好。想進哪個後台,點一下就能跳過去,不用再到處找連結、記書籤。
2. 使用者管理
所有公司的員工帳號都在這裡統一管理,不再由各個系統自己造一套。新同事入職的時候,從這個門戶註冊或錄入一次帳戶,後面所有子系統都能通過它來識別使用者,不用每個後台再各自維護一份帳號體系。
3. 系統管理(子系統註冊)
公司內部每多一個新的私有化服務,比如日誌後台、監控平台、部署系統,都要在這裡註冊一遍。註冊之後門戶才能展示入口,才能給使用者授權,也方便我們後面做跳轉、做健康檢查。
4. 系統健康檢查
既然門戶把所有子系統都集中展示了,那系統是否在線、地址是否失效、是否宕機,這些都得有地方看。所以我加了健康檢查模組,定時去子系統的 /health 或指定心跳接口探活,一旦某個系統掛了可以第一時間看到,避免同事點進去才發現打不開。
5. 使用者授權(哪個員工能進哪些後台)
每個同事不可能看到全部系統,有的人負責數據,有的人負責審核,有的人負責活動。所以這裡需要一個授權模組,根據員工的角色或職位,把哪些子系統能用、哪些不能用,一次性配置好。不用每個系統內部都再做一套權限判斷。
6. IP 白名單控制
因為這個後台等於是公司所有內部系統的“總入口”,安全性必須要做。除了帳號密碼之外,我還加了 IP 白名單,限制只有公司網路或特定來源的機器才能訪問,避免被外部惡意撞庫。
7. 操作日誌
作為一個完整的後台,操作審計必須有。誰登入了、誰改了子系統配置、給誰授權了什麼權限,這些都得記錄下來,出了問題也能快速追蹤。
整體拆下來,這幾個模組基本能覆蓋企業裡一個統一門戶該做的事:集中入口、統一帳戶、系統註冊、權限分發、安全防護和審計。後面再結合單點登入的流程,就能構成一套完整的後台管理中心。
前面我主要講的是整體的架構和思路,這裡我們就結合後台頁面,一塊一塊講我這個門戶到底是怎麼設計的。
我們登入進來之後看到的就是首頁,也是除了管理員之外的員工主要使用的頁面。基本功能就是把對應員工有權限的所有子系統按業務分類展示出來。我們公司內部子系統比較多,有研發相關的、有審核相關的、有監控相關的,我就按業務模組分了欄目,這樣不至於全部堆一起太亂。如下圖所示:

每個系統都是一張獨立的卡片,卡片上會展示系統名稱和一句簡介,讓同事一眼能看出是誰的後台、幹什麼的。如果同事要進入某個後台,直接點這張卡片就行了。
點擊後,門戶會去幫他生成一個 code,再帶著 code 跳轉到對應子系統,
對使用者來說根本不需要關心這些,體驗就是:點一下就能進入後台,不需要再登入第二次。
這個設計能讓常用後台的人非常省心,也能避免“換設備了所有後台入口都找不到”的情況。
公司內部的後台系統多了之後,最容易亂的就是帳號體系:
每個系統自己做一套帳號密碼,權限也各管各的,部門越多越難維護。
所以在門戶裡,我專門做了一個統一的使用者管理模組,把所有員工的帳號全部集中到一處管理。
如下圖所示,使用者列表上能看到每個人的基礎資訊(用戶名、郵箱)、角色、狀態、最近登入時間、是否在線等等。新同事入職,只需要在這裡建一個帳號,他就能直接進入自己有權限的所有後台,不用再到每個系統裡重複創建。

另外,因為這個門戶本質上是所有內部後台的“總入口”,安全性要求比普通業務系統高得多,所以我在使用者體系裡加了兩個非常關鍵的功能:
① 雙因子驗證(2FA)
後台入口一旦洩露,相當於所有子系統的門都被打開了。
內部環境裡又經常出現密碼被共享、弱密碼、密碼洩露沒人知道這些情況,所以給帳號加一道 2FA 可以大幅提高安全性。
只要綁定了 2FA,就算密碼被別人拿到了,也進不來。
② 踢下線(強制下線)
這個在內部環境特別重要,比如:
管理員在後台點一下“踢下線”,當前使用者所有 token 會立刻失效,需要重新登入才能繼續。
對於內部後台來說,這個功能簡直是救命的。
總體來說,使用者管理模組就是兩個字:統一。
把帳號、權限、安全都統一起來管理,後面所有子系統都能直接復用這一套體系,再也不會出現“密碼在哪”“這個系統是誰管理的”這種混亂情況。
這個模組其實就是整個平臺的“系統字典”。
我們公司的後台系統本來就多,而且大部分都是私有化部署,像測試後台、正式後台、日誌平台、監控平台、構建發佈平台……越做越多,不統一管理的話,誰都記不住哪裡還有個後台。
所以我在門戶裡做了個非常關鍵的功能:系統註冊。
作用就是告訴門戶——“我們公司目前到底有哪些後台系統,它們長什麼樣、入口在哪、怎麼訪問”。
如下圖所示

每一個後台系統在這裡都可以獨立配置:
至於我們為什麼要做成一個獨立的系統管理模組?
因為內部後台數量一旦多起來,我們不可能靠寫死配置或靠腦子記住。
所以有了這個列表,任何系統變更都可以統一管理,比如:
門戶首頁上的所有“卡片”,都是直接從這個列表動態渲染出來的,
而不是寫死在程式碼裡。這樣後期系統再多,也不會亂。
總的來說,“系統管理”就是整個平臺的“系統資產中心”。
後台多也不怕,只要這裡登記清楚,整個門戶就能隨時擴展、隨時更新。
既然門戶已經把所有子系統都統一展示出來了,那每個系統現在是不是“在線”,我們肯定也要能看到。
不然同事一點擊某個後台,結果發現打不開,還以為是自己網路問題,其實系統早就掛了。
所以我在門戶裡做了個 健康檢查模組(如下圖所示):

/health 或 /actuator/health)比如圖裡這樣:哪個系統在線、延遲多少、哪個訪問失敗,一眼就能看出來。
這個模組對多後台、多環境特別有用。因為很多時候某些後台並不是我們負責的,有了這個頁面之後,就能快速判斷問題到底是在入口、在系統本身、還是在網路。
健康檢查探活接口大概是怎麼做的?
做法其實很簡單:
https://xxx.com/health)整個邏輯不複雜,但實際效果非常有用。
尤其是在多後台、多環境的場景下,像系統偶發超時、某個環境掛掉、或者某個服務正在發佈,都能在這裡第一時間看出來,省得點進去才發現打不開。
如果想做得更完善一點,其實還可以加上系統通知能力,比如某個後台連續探活失敗就自動發企業微信/郵件提醒。
這次的 Demo 我暫時沒寫這一塊,但如果作為正式內部平臺,這個功能非常建議做上,會更接近大廠的運維體系。
有了統一的使用者體系之後,下一步就是做權限分配,也就是控制“誰能訪問哪些後台系統”。
這個模組是整個門戶的核心之一,基本所有權限相關的邏輯都從這裡開始。
介面大概是下面這樣(如下圖所示):

左側是所有員工列表,點選某個員工後,右側會展示他當前已經擁有的後台系統權限,同時也可以繼續新增、取消某些系統的訪問權限。

整個授權過程非常直觀:
這種設計對我們的內部場景非常適合,因為每個人的職責差異都很大,比如:
所有這些都可以在這個頁面裡統一管理,而不需要讓每個子系統再做一套自己的權限體系。
這一點非常關鍵:子系統不需要再維護自己的使用者表、角色表、權限關係表等複雜邏輯,所有權限都由門戶來管理。
子系統只需要做一件事:收到 code → 向認證中心換使用者資訊 → 判斷是否允許進入即可。
授權邏輯簡單、統一、可控,也非常符合公司內部多後台場景的需求。
因為這個門戶等於所有內部系統的總入口,所以安全必須加強。我做了個 IP 白名單模組,類似公司的訪問控制:

這樣可以避免外部惡意訪問,比如撞庫攻擊、接口掃爆、弱密碼嘗試等等。
後台系統必須有操作日誌。
不管是修改了權限、添加了使用者、更新了子系統配置,還是登入、退出,都要記錄下來:

一旦出現問題,可以快速定位到具體的人和操作。
說完後台的幾個管理模組之後,接下來就要看它們底層的數據結構了。
其實整個統一門戶的平台數據結構並不複雜,真正的核心也就四張表,分別負責:使用者、系統分類、系統資訊、使用者授權。
我這裡把關鍵表結構都貼出來,並結合業務說明一下設計思路。
sys_user這是整個門戶的帳號根表,所有員工的登入資訊都在這裡統一管理。
為什麼要這樣設計呢?
因為門戶是所有後台的入口,一個員工有且只應該在門戶這裡維護一次帳號,不再讓每個子系統重複建表、重複維護帳號。
這裡會存儲:
CREATE TABLE `sys_user` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主鍵ID',
`username` varchar(64) NOT NULL COMMENT '用戶名(唯一)',
`password` varchar(255) NOT NULL COMMENT '加密後的密碼',
`email` varchar(128) DEFAULT NULL COMMENT '郵箱',
`phone` varchar(32) DEFAULT NULL COMMENT '手機號',
`google_secret` varchar(64) DEFAULT NULL COMMENT '2FA 密鑰',
`avatar` varchar(255) DEFAULT NULL COMMENT '頭像地址',
`status` tinyint(1) DEFAULT '1' COMMENT '狀態:1啟用,0禁用',
`last_login_at` datetime DEFAULT NULL COMMENT '最近登入時間',
`created_at` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '創建時間',
`updated_at` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='使用者資訊表';
sys_category首頁展示需要分組,否則幾百個系統放一起會亂成一鍋粥。
所以我們用分類表用來定義系統的業務分組,比如:
主要用於顯示,不涉及權限。
CREATE TABLE `sys_category` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主鍵ID',
`category_code` varchar(64) NOT NULL COMMENT '分類標識(英文縮寫,如 backend、deploy)',
`category_name` varchar(128) NOT NULL COMMENT '分類名稱(中文,如 後台系統)',
`sort_no` int(11) DEFAULT '0' COMMENT '排序號(越大越靠前)',
`remark` varchar(255) DEFAULT NULL COMMENT '備註說明',
`created_at` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '創建時間',
`updated_at` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `category_code` (`category_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='系統分類表';
sys_application這張表記錄 公司所有的後台系統,是統一門戶最關鍵的一張。
每個子系統都會在這裡登記:
sys_category)門戶首頁那些卡片、圖標、分組展示,全部來源於這張表。
CREATE TABLE `sys_application` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主鍵ID',
`category_id` bigint(20) unsigned DEFAULT NULL COMMENT '分類ID(關聯sys_category)',
`app_id` varchar(64) NOT NULL COMMENT '系統唯一標識(英文縮寫)',
`app_name` varchar(128) NOT NULL COMMENT '系統名稱(中文名)',
`app_desc` varchar(255) DEFAULT NULL COMMENT '系統副標題/描述',
`entry_url` varchar(255) NOT NULL COMMENT '系統入口地址',
`icon` varchar(255) DEFAULT NULL COMMENT '系統圖標地址',
`status` tinyint(1) DEFAULT '1' COMMENT '狀態:1啟用,0禁用',
`sort_no` int(11) DEFAULT '0' COMMENT '分類內排序號',
`created_at` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '創建時間',
`updated_at` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `app_id` (`app_id`),
KEY `fk_app_category` (`category_id`),
CONSTRAINT `fk_app_category` FOREIGN KEY (`category_id`) REFERENCES `sys_category` (`id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='接入系統資訊表';
sys_user_application所有權限控制都依賴這張表。
這張表的作用是:記錄某個員工可以訪問哪些後台系統。
關係是多對多:
所以用了 (user_id, app_id) 唯一索引來避免重複授權。
子系統登入時,通過門戶驗證 code,就能知道該使用者是否有權限進入。
CREATE TABLE `sys_user_application` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主鍵ID',
`user_id` bigint(20) unsigned NOT NULL COMMENT '使用者ID',
`app_id` bigint(20) unsigned NOT NULL COMMENT '系統ID',
`granted_at` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '授權時間',
`granted_by` varchar(64) DEFAULT NULL COMMENT '授權人',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_user_app` (`user_id`,`app_id`),
KEY `fk_app` (`app_id`),
CONSTRAINT `fk_app` FOREIGN KEY (`app_id`) REFERENCES `sys_application` (`id`) ON DELETE CASCADE,
CONSTRAINT `fk_user` FOREIGN KEY (`user_id`) REFERENCES `sys_user` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='使用者與系統授權關係表';
在實現門戶免登子系統之前,我們後端接口設計其實只需要準備兩個核心接口:
整個流程就是圍繞 “發 code → 用 code 換使用者資訊” 兩件事來展開的,這也是我們這套輕量化 SSO 的核心機制。
只要把這兩個接口打通,所有語言的子系統(Java / Go / PHP / Python)都能用統一方式接入,換句話說,我們這套機制並不是把所有子系統都徹底綁死在認證中心上,也不要求它們完全放棄自己的登入體系。每個子系統依然可以保留各自原有的帳號密碼登入邏輯,只是額外多封裝了一步“通過門戶授權碼免登”的邏輯。
這樣做有兩個好處:
我們最終要的不是把所有系統的登入邏輯全部遷到認證中心,而是實現一種 鬆耦合的 SSO:
——能統一就統一,不能統一也不影響各自運行。
要實現門戶免登子系統,核心就是生成一個一次性的授權碼(code),然後子系統再拿著這個 code 去認證中心換取使用者資訊。這個接口就是整個 SSO 流程的第一步。
接口地址:
POST /sso/code/create
流程簡單說就是:
使用者點擊門戶某個系統卡片 → 門戶向認證中心要一個 code → 前端把 code 拼到系統 URL → 子系統拿 code 來換使用者資訊 → 完成免登。
前端調用邏輯
當使用者在門戶首頁點擊某個系統卡片時,會觸發前端封裝的 goSystem 方法:
/** 點擊進入系統 */
const goSystem = async (item) => {
if (!item.link) {
ElMessage.warning("該系統暫未配置訪問地址");
return;
}
if (!user.id) {
ElMessage.error("使用者未登入或資訊缺失");
return;
}
try {
const res = await createSSOCode(user.id, item.code); // 調用 /sso/code/create
if (res.success && res.data?.code) {
const url = `${item.link}?code=${res.data.code}`;
window.open(url, "_blank"); // 帶 code 跳轉到子系統
} else
---
原文出處:https://juejin.cn/post/7576532425762144271