手把手教你學(xué)會 基于Spring-Boot,JWT的單點登錄

最近我們組要給負(fù)責(zé)的一個管理系統(tǒng) A 集成另外一個系統(tǒng) B乘客,為了讓用戶使用更加便捷碟刺,避免多個系統(tǒng)重復(fù)登錄,希望能夠達(dá)到這樣的效果——用戶只需登錄一次就能夠在這兩個系統(tǒng)中進(jìn)行操作。很明顯這就是單點登錄(Single Sign-On)達(dá)到的效果,正好可以明目張膽的學(xué)一波單點登錄知識峭拘。

本篇主要內(nèi)容如下:

  • SSO 介紹
  • SSO 的幾種實現(xiàn)方式對比
  • 基于 JWT 的 spring boot 單點登錄實戰(zhàn)

注意: SSO 這個概念已經(jīng)出現(xiàn)很久很久了匹摇,目前各種平臺都有非常成熟的實現(xiàn)咬扇,比如OpenSSO,OpenAM廊勃,Kerberos懈贺,CAS等,當(dāng)然很多時候成熟意味著復(fù)雜经窖。本文不討論那些成熟方案的使用,也不考慮 SSO 在 CS 應(yīng)用中的使用。

什么是 SSO

單點點說就是:一次登錄后可免登陸訪問其他的可信平臺梭灿。比如我們登錄淘寶網(wǎng)后画侣,再打開天貓首頁可以發(fā)現(xiàn)已經(jīng)是登錄狀態(tài)了。SSO 是一種比較流行的服務(wù)于企業(yè)業(yè)務(wù)整合的一種解決方案堡妒。

如何實現(xiàn) SSO

我們都知道目前的 http 協(xié)議是無狀態(tài)的配乱,也就是第一次請求和第二次請求是完全獨立,不相關(guān)的皮迟,但現(xiàn)實中我們的業(yè)務(wù)邏輯都是有狀態(tài)的搬泥,這樣就引入了 cookie-session 的機制來維護狀態(tài),瀏覽器端存儲一個 sessionId,后臺存儲跟該 sessionId 相關(guān)的數(shù)據(jù)伏尼。每次向后臺發(fā)起請求時都攜帶此 sessionId 就能維持狀態(tài)了佑钾。然后就有了 cookie,瀏覽器在發(fā)送請求時自動將 cookie 中的數(shù)據(jù)放到請求中烦粒,發(fā)給服務(wù)端休溶,無需手動設(shè)置。

然后我們可以考慮考慮實現(xiàn) SSO 的核心是什么扰她?答案就是如何讓一個平臺 A 登錄后兽掰,其他的平臺也能獲取到平臺 A 的登錄信息(在 cookie-session 機制中就是 sessionId)。

方案一 共享 cookie

基于 cookie-session 機制的系統(tǒng)中徒役,登錄系統(tǒng)后會返回一個 sessionId 存儲在 cookie 中孽尽,如果我們能夠讓另外一個系統(tǒng)也能獲取到這個 cookie,不就獲取到憑證信息了忧勿,無需再次登錄杉女。剛好瀏覽器的 cookie 可以實現(xiàn)這樣的效果(詳見web 跨域及 cookie 學(xué)習(xí))。

cookie 允許同域名(或者父子域名)的不同端口中共享 cookie,這點和 http 的同域策略不一樣(http 請求只要協(xié)議鸳吸、域名熏挎、端口不完全相同便認(rèn)為跨域)。因此只需將多個應(yīng)用前臺頁面部署到相同的域名(或者父子域名)晌砾,然后共享 session 便能夠?qū)崿F(xiàn)單點登錄坎拐。架構(gòu)如下:

image

上面方案顯而易見的限制就是不僅前臺頁面需要共享 cookie,后臺也需要共享 session(可以用jwt來干掉 session养匈,但是又會引入新的問題哼勇,這里不展開).這個方案太簡單了,不作進(jìn)一步說明呕乎。

方案二 基于回調(diào)實現(xiàn)

通過上文可以知道积担,要實現(xiàn)單點登錄只需將用戶的身份憑證共享給各個系統(tǒng),讓后臺知道現(xiàn)在是誰在訪問猬仁。就能實現(xiàn)一次登錄帝璧,到處訪問的效果先誉,實在是非常方便的。在 session 機制中是共享 sessionId聋溜,然后多個后臺使用同一個 session 源即可谆膳。這里我們用一種新的基于 JWT 的 token 方式來實現(xiàn)叭爱,不了解 JWT 的可以看這篇:java-jwt 生成與校驗,簡單來說 jwt 可以攜帶無法篡改的信息(一段篡改就會校驗失敗),所以我們可以將用戶 id 等非敏感信息直接放到 jwt 中撮躁,干掉了后臺的 session。然后我們要做的就是將 jwt 共享給各個平臺頁面即可买雾。系統(tǒng)架構(gòu)如下:

image

此架構(gòu)中把曼,業(yè)務(wù)系統(tǒng) A 和業(yè)務(wù)系統(tǒng) B 之間不需要有任何聯(lián)系,他們都只和 SSO 認(rèn)證平臺打交道漓穿,因此可以任意部署嗤军,沒有同域的限制。你可能就要問了這樣要怎么共享身份憑證(也就是 jwt 字符串)晃危?這里就要通過 url 參數(shù)來進(jìn)行騷操作了叙赚。文字總結(jié)來說是這樣的:jwt 存到認(rèn)證平臺前端的 localStore(不一定是 localStore,cookie僚饭,sessionStore 都可以),然后業(yè)務(wù)平臺攜帶自己的回調(diào)地址跳轉(zhuǎn)到認(rèn)證中心的前臺震叮,認(rèn)證中心的前臺再將 ujwt 作為 url 參數(shù),跳回到那個回調(diào)地址上鳍鸵,這樣就完成了 jwt 的共享苇瓣。

文字很可能看不懂,下面是整個過程的路程圖:

image

相信通過上面的流程圖你應(yīng)該能大概看明白偿乖,jwt 是如何共享了的吧击罪,還看不懂的繼續(xù)看下來,下面上一個 spring boot 實現(xiàn)的簡易 SSO 認(rèn)證贪薪。主要有兩個系統(tǒng):SSO 認(rèn)證中心媳禁,系統(tǒng) A(系統(tǒng) A 換不同端口運行就是系統(tǒng) A、B画切、C损话、D 了).

實戰(zhàn)

實現(xiàn) SSO 認(rèn)證中心

spring boot 框架先搭起來,由于是簡易項目槽唾,除 spring boot web 基本依賴丧枪,只需要如下的額外依賴:

<pre style="margin: 0px; padding: 0px; border: 0px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-numeric: inherit; font-variant-east-asian: inherit; font-weight: 400; font-stretch: inherit; font-size: 18px; line-height: inherit; font-family: inherit; vertical-align: baseline; word-break: break-word; color: rgb(93, 93, 93); letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial;">

<dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
 <groupId>com.alibaba</groupId>
 <artifactId>fastjson</artifactId>
 <version>1.2.4</version>
</dependency>
<dependency>
 <groupId>com.auth0</groupId>
 <artifactId>java-jwt</artifactId>
 <version>3.7.0</version>
</dependency>
復(fù)制代碼

</pre>

完整的 POM 文件,請到 github 上查看.

后臺實現(xiàn)

后臺做的事情并不多庞萍,只有以下 5 個方法:

  • /login : 登錄成功后簽發(fā)一個 jwt token
  • 在 demo 中只是簡單對比用戶名密碼如果是一樣的認(rèn)為登錄成功拧烦,返回 token
  • /checkJwt : 檢查 jwt 的有效性
  • 檢查傳來的 jwt-token 是否有效,返回失效的 jwt 列表
  • /refreshjwt : 刷新 jwt
  • 判斷該 jwt 是否快要過期钝计,如果快要過期恋博,生成一個新的 jwt 返回
  • /inValid : 讓某個 jwt 失效
  • jwt 如何失效一直是一個比較麻煩的問題齐佳,各有利弊。本例中采用的是為每個 jwt 生成一個隨機的秘鑰 secret债沮,將 jwt--secret 保存到 redis 中炼吴,想要讓某個 jwt 失效,只需將該記錄在 redis 中刪除即可(這樣在解密時便無法獲取到 secret)疫衩。但是這樣讓無狀態(tài)的認(rèn)證機制變成有狀態(tài)了(記錄了 jwt 和 secret 的對應(yīng)關(guān)系)硅蹦。

總結(jié)來說 SSO 后臺主要只做了兩件事:驗證用戶名密碼返回 jwt;驗證 jwt 是否合法闷煤。具體代碼查看 github 上 sso 目錄下的代碼童芹。

前臺實現(xiàn)

前臺的邏輯較為復(fù)雜,不是那么容易理解鲤拿,不明白的多看幾遍上面的流程圖假褪。

再次回到 SSO 的重點:分享登錄狀態(tài)。要如何在前臺將登錄狀態(tài)(在這里就是 jwt 字符串)分享出去呢近顷?由于瀏覽器的限制生音,除了 cookie 外沒有直接共享數(shù)據(jù)的辦法。既然沒有直接共享窒升,那肯定是有間接的辦法的缀遍!

這個辦法就是回調(diào)。系統(tǒng) A 的前臺在跳轉(zhuǎn)到 SSO 的前臺時异剥,將當(dāng)前路徑作為 url 參數(shù)傳遞給 sso 前臺瑟由,sso 前臺在獲取到 jwt 后,再跳轉(zhuǎn)到系統(tǒng) A 傳過來的 url 路徑上冤寿,并帶上 jwt 作為 url 參數(shù)歹苦。這就完成了 jwt 的一次共享,從 sso 共享到系統(tǒng) A督怜。

打個比方:你點了個外賣殴瘦,別人要怎么把外賣給你呢?顯然你會留下的地址号杠,讓別人帶上飯送到這個地址蚪腋,然后你就能享用美食了。這和 jwt 的傳遞非常相識了姨蟋。

系統(tǒng) A 說:我要 jwt屉凯,快把它送到http://localhost:8081/test1/這個地址上。

SSO 說:好嘞眼溶,這個地址是合法的可以送 jwt 過去悠砚,這就跳轉(zhuǎn)過去:http://localhost:8081/test1/?jwt=abcdefj.asdf.asdfasf

系統(tǒng) A 說:不錯不錯,真香堂飞。

要注意這里有個坑就是:如果另外一個惡意系統(tǒng) C 安裝相同的格式跳轉(zhuǎn)到 SSO灌旧,想要獲取 jwt绑咱,這顯然是不應(yīng)該給它的。所以在回跳回去的時候要判斷一下這個回調(diào)地址是不是合法的枢泰,能不能給 jwt 給它描融,可以向后臺請求判斷也可以在 sso 前臺直接寫死合法的地址。在 demo 是沒有這個判斷過程的衡蚂。

實現(xiàn)業(yè)務(wù)系統(tǒng)

業(yè)務(wù)系統(tǒng)代碼非常簡單窿克,主要是用了一個攔截器,攔截 http 請求讳窟,提取出 token 向 sso 認(rèn)證中心驗證 token 是否有效让歼,有效放行敞恋,否則返回錯誤給前端丽啡。太簡單也不貼代碼了,到 github 上看看就明白了硬猫。

效果

上面說了一大串都是原理了补箍,其實這個難也就難在原理部分,代碼實現(xiàn)并沒有那么復(fù)雜啸蜜。這里就不貼代碼了坑雅,有需要直接到 github 上看。

這里上幾個效果圖:

  • 系統(tǒng) A 首次登陸系統(tǒng)
image

可以看到首次登陸是需要跳到 sso 認(rèn)證中心輸入用戶名密碼進(jìn)行登陸驗證的衬横。登陸成功回跳后接口請求成功裹粤。

  • 將 A 的啟動端口改為 8082 后再次啟動,當(dāng)作系統(tǒng) B
image

可以看到這次是無需登陸的蜂林,跳到認(rèn)證中心后就馬上跳回了遥诉,如果去掉 alert 一般是看不出跳轉(zhuǎn)過程的。

最后在任意一個系統(tǒng)注銷噪叙,都會讓所有的系統(tǒng)推出登陸矮锈。

可以看到,在系統(tǒng) A 登錄系統(tǒng)后睁蕾,系統(tǒng) B苞笨,系統(tǒng) C 都不再需要輸入用戶名密碼進(jìn)行登錄。如果速度足夠快甚至都注意不到調(diào)到 SSO 再跳回來的過程子眶。

歡迎工作一到五年的Java工程師朋友們加入JavaQQ群:219571750瀑凝,群內(nèi)提供免費的Java架構(gòu)學(xué)習(xí)資料(里面有高可用、高并發(fā)臭杰、高性能及分布式粤咪、Jvm性能調(diào)優(yōu)、Spring源碼硅卢,MyBatis射窒,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多個知識點的架構(gòu)資料)合理利用自己每一分每一秒的時間來學(xué)習(xí)提升自己藏杖,不要再用"沒有時間“來掩飾自己思想上的懶惰!趁年輕脉顿,使勁拼蝌麸,給未來的自己一個交代!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末艾疟,一起剝皮案震驚了整個濱河市来吩,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌蔽莱,老刑警劉巖弟疆,帶你破解...
    沈念sama閱讀 212,718評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異盗冷,居然都是意外死亡怠苔,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,683評論 3 385
  • 文/潘曉璐 我一進(jìn)店門仪糖,熙熙樓的掌柜王于貴愁眉苦臉地迎上來柑司,“玉大人,你說我怎么就攤上這事锅劝≡艹郏” “怎么了?”我有些...
    開封第一講書人閱讀 158,207評論 0 348
  • 文/不壞的土叔 我叫張陵故爵,是天一觀的道長玻粪。 經(jīng)常有香客問我,道長诬垂,這世上最難降的妖魔是什么劲室? 我笑而不...
    開封第一講書人閱讀 56,755評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮剥纷,結(jié)果婚禮上痹籍,老公的妹妹穿的比我還像新娘。我一直安慰自己晦鞋,他們只是感情好蹲缠,可當(dāng)我...
    茶點故事閱讀 65,862評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著悠垛,像睡著了一般线定。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上确买,一...
    開封第一講書人閱讀 50,050評論 1 291
  • 那天斤讥,我揣著相機與錄音,去河邊找鬼。 笑死芭商,一個胖子當(dāng)著我的面吹牛派草,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播铛楣,決...
    沈念sama閱讀 39,136評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼近迁,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了簸州?” 一聲冷哼從身側(cè)響起鉴竭,我...
    開封第一講書人閱讀 37,882評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎岸浑,沒想到半個月后搏存,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,330評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡矢洲,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,651評論 2 327
  • 正文 我和宋清朗相戀三年璧眠,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片兵钮。...
    茶點故事閱讀 38,789評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡蛆橡,死狀恐怖舌界,靈堂內(nèi)的尸體忽然破棺而出掘譬,到底是詐尸還是另有隱情,我是刑警寧澤呻拌,帶...
    沈念sama閱讀 34,477評論 4 333
  • 正文 年R本政府宣布葱轩,位于F島的核電站,受9級特大地震影響藐握,放射性物質(zhì)發(fā)生泄漏靴拱。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 40,135評論 3 317
  • 文/蒙蒙 一猾普、第九天 我趴在偏房一處隱蔽的房頂上張望袜炕。 院中可真熱鬧,春花似錦初家、人聲如沸偎窘。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,864評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽陌知。三九已至,卻和暖如春掖肋,著一層夾襖步出監(jiān)牢的瞬間仆葡,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,099評論 1 267
  • 我被黑心中介騙來泰國打工志笼, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留沿盅,地道東北人把篓。 一個月前我還...
    沈念sama閱讀 46,598評論 2 362
  • 正文 我出身青樓,卻偏偏與公主長得像腰涧,于是被迫代替她去往敵國和親纸俭。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,697評論 2 351

推薦閱讀更多精彩內(nèi)容