Spring Security(01)

一直以來我都想寫一寫Spring Security系列的文章,但是整個(gè)Spring Security體系強(qiáng)大卻又繁雜。陸陸續(xù)續(xù)從最開始的guides接觸它义屏,項(xiàng)目中看了一些源碼,到最近這個(gè)月為了寫一寫這個(gè)系列的文章,閱讀了好幾遍文檔犁河,最終打算嘗試一下,寫一個(gè)較為完整的系列文章魄梯。

較為簡單或者體量較小的技術(shù)桨螺,完全可以參考著demo直接上手,但系統(tǒng)的學(xué)習(xí)一門技術(shù)則不然酿秸。以我的認(rèn)知灭翔,一般的文檔大致有兩種風(fēng)格:Architecture First和Code First。前者致力于讓讀者先了解整體的架構(gòu)辣苏,方便讓我們有一個(gè)宏觀的把控肝箱,而后者以特定的demo配合講解,可以讓讀者在解決問題的過程中順便掌握一門技術(shù)稀蟋。關(guān)注過我博客或者公眾號的朋友會發(fā)現(xiàn)煌张,我之前介紹技術(shù)的文章,大多數(shù)是Code First退客,提出一個(gè)需求骏融,介紹一個(gè)思路链嘀,解決一個(gè)問題,分析一下源碼档玻,大多如此怀泊。而學(xué)習(xí)一個(gè)體系的技術(shù),我推薦Architecture First误趴,正如本文標(biāo)題所言霹琼,這篇文章是我Spring Security系列的第一篇,主要是根據(jù)Spring Security文檔以及源碼凉当,選擇性地整理而成的一個(gè)架構(gòu)概覽枣申,配合自己的一些講解方便大家理解。寫作本系列文章時(shí)纤怒,參考版本為Spring Security 4.2.3.RELEASE糯而。


接下來介紹核心組件,這一節(jié)主要介紹一些在Spring Security中常見且核心的Java類泊窘,它們之間的依賴熄驼,構(gòu)建起了整個(gè)框架。想要理解整個(gè)架構(gòu)烘豹,最起碼得對這些類眼熟瓜贾。


1.1 SecurityContextHolder

SecurityContextHolder用于存儲安全上下文(security context)的信息。當(dāng)前操作的用戶是誰携悯,該用戶是否已經(jīng)被認(rèn)證祭芦,他擁有哪些角色權(quán)限...這些都被保存在SecurityContextHolder中。?SecurityContextHolder默認(rèn)使用?ThreadLocal?策略來存儲認(rèn)證信息憔鬼」昃ⅲ看到?ThreadLocal?也就意味著,這是一種與線程綁定的策略轴或。Spring Security在用戶登錄時(shí)自動綁定認(rèn)證信息到當(dāng)前線程昌跌,在用戶退出時(shí),自動清除當(dāng)前線程的認(rèn)證信息照雁。但這一切的前提蚕愤,是你在web場景下使用Spring Security,而如果是Swing界面饺蚊,Spring也提供了支持,?SecurityContextHolder的策略則需要被替換污呼,鑒于我的初衷是基于web來介紹Spring Security裕坊,所以這里以及后續(xù),非web的相關(guān)的內(nèi)容都一筆帶過悟狱。

獲取當(dāng)前用戶的信息

因?yàn)樯矸菪畔⑹桥c線程綁定的苹享,所以可以在程序的任何地方使用靜態(tài)方法獲取用戶信息得问。一個(gè)典型的獲取當(dāng)前登錄用戶的姓名的例子如下所示:

getAuthentication()返回了認(rèn)證信息膏萧,再次getPrincipal()返回了身份信息,UserDetails便是Spring對身份信息封裝的一個(gè)接口。Authentication和UserDetails的介紹在下面的小節(jié)具體講解椒楣,本節(jié)重要的內(nèi)容是介紹SecurityContextHolder這個(gè)容器。

1.2 Authentication

先看看這個(gè)接口的源碼長什么樣:

<1> Authentication是spring security包中的接口凤壁,直接繼承自Principal類吩屹,而Principal是位于java.security包中的∨《叮可以見得煤搜,Authentication在spring security中是最高級別的身份/認(rèn)證的抽象。

<2> 由這個(gè)頂級接口唧席,我們可以得到用戶擁有的權(quán)限信息列表擦盾,密碼嘲驾,用戶細(xì)節(jié)信息,用戶身份信息迹卢,認(rèn)證信息辽故。


上面1.1第一個(gè)代碼例子中,authentication.getPrincipal()返回了一個(gè)Object腐碱,我們將Principal強(qiáng)轉(zhuǎn)成了Spring Security中最常用的UserDetails誊垢,這在Spring Security中非常常見,接口返回Object症见,使用instanceof判斷類型喂走,強(qiáng)轉(zhuǎn)成對應(yīng)的具體實(shí)現(xiàn)類。接口詳細(xì)解讀如下:

getAuthorities()谋作,權(quán)限信息列表芋肠,默認(rèn)是GrantedAuthority接口的一些實(shí)現(xiàn)類,通常是代表權(quán)限信息的一系列字符串遵蚜。

getCredentials()帖池,密碼信息,用戶輸入的密碼字符串谬晕,在認(rèn)證過后通常會被移除碘裕,用于保障安全。

getDetails()攒钳,細(xì)節(jié)信息帮孔,web應(yīng)用中的實(shí)現(xiàn)接口通常為 WebAuthenticationDetails,它記錄了訪問者的ip地址和sessionId的值不撑。

getPrincipal()文兢,敲黑板!;烂省姆坚!最重要的身份信息,大部分情況下返回的是UserDetails接口的實(shí)現(xiàn)類实愚,也是框架中的常用接口之一兼呵。UserDetails接口將會在下面的小節(jié)重點(diǎn)介紹。

Spring Security是如何完成身份認(rèn)證的腊敲?

1.? 用戶名和密碼被過濾器獲取到击喂,封裝成Authentication,通常情況下是UsernamePasswordAuthenticationToken這個(gè)實(shí)現(xiàn)類。

2.? AuthenticationManager身份管理器負(fù)責(zé)驗(yàn)證這個(gè)Authentication

3.? 認(rèn)證成功后碰辅,AuthenticationManager身份管理器返回一個(gè)被填充滿了信息的(包括上面提到的權(quán)限信息懂昂,身份信息,細(xì)節(jié)信息没宾,但密碼通常會被移除)Authentication實(shí)例凌彬。

4.? SecurityContextHolder安全上下文容器將第3步填充了信息的Authentication沸柔,通過SecurityContextHolder.getContext().setAuthentication(…)方法,設(shè)置到其中铲敛。

這是一個(gè)抽象的認(rèn)證流程褐澎,而整個(gè)過程中,如果不糾結(jié)于細(xì)節(jié)原探,其實(shí)只剩下一個(gè)?AuthenticationManager?是我們沒有接觸過的了乱凿,這個(gè)身份管理器我們在后面的小節(jié)介紹。將上述的流程轉(zhuǎn)換成代碼咽弦,便是如下的流程:

注意:上述這段代碼只是為了讓大家了解Spring Security的工作流程而寫的,不是什么源碼胁出。在實(shí)際使用中型型,整個(gè)流程會變得更加的復(fù)雜,但是基本思想全蝶,和上述代碼如出一轍闹蒜。


1.3 AuthenticationManager

初次接觸Spring Security的朋友相信會被AuthenticationManager,ProviderManager抑淫,AuthenticationProvider...這么多相似的Spring認(rèn)證類搞得暈頭轉(zhuǎn)向绷落,但只要稍微梳理一下就可以理解清楚它們的聯(lián)系和設(shè)計(jì)者的用意。

AuthenticationManager(接口)是認(rèn)證相關(guān)的核心接口始苇,也是發(fā)起認(rèn)證的出發(fā)點(diǎn)砌烁,因?yàn)樵趯?shí)際需求中,我們可能會允許用戶使用用戶名+密碼登錄催式,同時(shí)允許用戶使用郵箱+密碼函喉,手機(jī)號碼+密碼登錄,甚至荣月,可能允許用戶使用指紋登錄(還有這樣的操作管呵?沒想到吧),所以說AuthenticationManager一般不直接認(rèn)證哺窄,AuthenticationManager接口的常用實(shí)現(xiàn)類ProviderManager內(nèi)部會維護(hù)一個(gè)List<AuthenticationProvider>列表捐下,存放多種認(rèn)證方式,實(shí)際上這是委托者模式的應(yīng)用(Delegate)萌业。也就是說坷襟,核心的認(rèn)證入口始終只有一個(gè):AuthenticationManager,不同的認(rèn)證方式:用戶名+密碼(UsernamePasswordAuthenticationToken)咽白,郵箱+密碼啤握,手機(jī)號碼+密碼登錄則對應(yīng)了三個(gè)AuthenticationProvider。這樣一來四不四就好理解多了晶框?熟悉shiro的朋友可以把AuthenticationProvider理解成Realm排抬。在默認(rèn)策略下懂从,只需要通過一個(gè)AuthenticationProvider的認(rèn)證,即可被認(rèn)為是登錄成功蹲蒲。

只保留了關(guān)鍵認(rèn)證部分的ProviderManager源碼:

ProviderManager中的List番甩,會依照次序去認(rèn)證,認(rèn)證成功則立即返回届搁,若認(rèn)證失敗則返回null缘薛,下一個(gè)AuthenticationProvider會繼續(xù)嘗試認(rèn)證,如果所有認(rèn)證器都無法認(rèn)證成功卡睦,則ProviderManager會拋出一個(gè)ProviderNotFoundException異常宴胧。

到這里,如果不糾結(jié)于AuthenticationProvider的實(shí)現(xiàn)細(xì)節(jié)以及安全相關(guān)的過濾器表锻,認(rèn)證相關(guān)的核心類其實(shí)都已經(jīng)介紹完畢了:身份信息的存放容器SecurityContextHolder恕齐,身份信息的抽象Authentication,身份認(rèn)證器AuthenticationManager及其認(rèn)證流程瞬逊。姑且在這里做一個(gè)分隔線显歧。下面來介紹下AuthenticationProvider接口的具體實(shí)現(xiàn)。


1.4 DaoAuthenticationProvider

AuthenticationProvider最最最常用的一個(gè)實(shí)現(xiàn)便是DaoAuthenticationProvider确镊。顧名思義士骤,Dao正是數(shù)據(jù)訪問層的縮寫,也暗示了這個(gè)身份認(rèn)證器的實(shí)現(xiàn)思路蕾域。由于本文是一個(gè)Overview拷肌,姑且只給出其UML類圖:

按照我們最直觀的思路,怎么去認(rèn)證一個(gè)用戶呢束铭?用戶前臺提交了用戶名和密碼廓块,而數(shù)據(jù)庫中保存了用戶名和密碼,認(rèn)證便是負(fù)責(zé)比對同一個(gè)用戶名契沫,提交的密碼和保存的密碼是否相同便是了带猴。在Spring Security中。提交的用戶名和密碼懈万,被封裝成了UsernamePasswordAuthenticationToken拴清,而根據(jù)用戶名加載用戶的任務(wù)則是交給了UserDetailsService,在DaoAuthenticationProvider中会通,對應(yīng)的方法便是retrieveUser口予,雖然有兩個(gè)參數(shù),但是retrieveUser只有第一個(gè)參數(shù)起主要作用涕侈,返回一個(gè)UserDetails沪停。還需要完成UsernamePasswordAuthenticationToken和UserDetails密碼的比對,這便是交給additionalAuthenticationChecks方法完成的,如果這個(gè)void方法沒有拋異常木张,則認(rèn)為比對成功众辨。比對密碼的過程,用到了PasswordEncoder和SaltSource舷礼,密碼加密和鹽的概念相信不用我贅述了鹃彻,它們?yōu)楸U习踩O(shè)計(jì),都是比較基礎(chǔ)的概念妻献。

如果你已經(jīng)被這些概念搞得暈頭轉(zhuǎn)向了蛛株,不妨這么理解DaoAuthenticationProvider:它獲取用戶提交的用戶名和密碼,比對其正確性育拨,如果正確谨履,返回一個(gè)數(shù)據(jù)庫中的用戶信息(假設(shè)用戶信息被保存在數(shù)據(jù)庫中)。


1.5 UserDetails與UserDetailsService

上面不斷提到了UserDetails這個(gè)接口至朗,它代表了最詳細(xì)的用戶信息屉符,這個(gè)接口涵蓋了一些必要的用戶信息字段,具體的實(shí)現(xiàn)類對它進(jìn)行了擴(kuò)展锹引。

它和Authentication接口很類似,比如它們都擁有username唆香,authorities嫌变,區(qū)分他們也是本文的重點(diǎn)內(nèi)容之一。Authentication的getCredentials()與UserDetails中的getPassword()需要被區(qū)分對待躬它,前者是用戶提交的密碼憑證腾啥,后者是用戶正確的密碼,認(rèn)證器其實(shí)就是對這兩者的比對冯吓。Authentication中的getAuthorities()實(shí)際是由UserDetails的getAuthorities()傳遞而形成的倘待。還記得Authentication接口中的getUserDetails()方法嗎?其中的UserDetails用戶詳細(xì)信息便是經(jīng)過了AuthenticationProvider之后被填充的组贺。

UserDetailsService和AuthenticationProvider兩者的職責(zé)常常被人們搞混凸舵,關(guān)于他們的問題在文檔的FAQ和issues中屢見不鮮。記住一點(diǎn)即可失尖,敲黑板0⊙佟!掀潮!UserDetailsService只負(fù)責(zé)從特定的地方(通常是數(shù)據(jù)庫)加載用戶信息菇夸,僅此而已,記住這一點(diǎn)仪吧,可以避免走很多彎路庄新。UserDetailsService常見的實(shí)現(xiàn)類有JdbcDaoImpl,InMemoryUserDetailsManager,前者從數(shù)據(jù)庫加載用戶择诈,后者從內(nèi)存中加載用戶械蹋,也可以自己實(shí)現(xiàn)UserDetailsService,通常這更加靈活吭从。


1.6 架構(gòu)概覽圖

為了更加形象的理解上述我介紹的這些核心類朝蜘,附上一張按照我的理解,所畫出Spring Security的一張非典型的UML圖:

如果對Spring Security的這些概念感到理解不能涩金,不用擔(dān)心谱醇,因?yàn)檫@是Architecture First導(dǎo)致的必然結(jié)果,先過個(gè)眼熟步做。后續(xù)的文章會秉持Code First的理念副渴,陸續(xù)詳細(xì)地講解這些實(shí)現(xiàn)類的使用場景,源碼分析全度,以及最基本的:如何配置Spring Security煮剧,在后面的文章中可以不時(shí)翻看這篇文章,找到具體的類在整個(gè)架構(gòu)中所處的位置将鸵,這也是本篇文章的定位勉盅。另外,一些Spring Security的過濾器還未囊括在架構(gòu)概覽中顶掉,如將表單信息包裝成UsernamePasswordAuthenticationToken的過濾器膘壶,考慮到這些雖然也是架構(gòu)的一部分减宣,但是真正重寫他們的可能性較小揽思,所以打算放到后面的章節(jié)講解岭皂。




原文鏈接:https://mp.weixin.qq.com/s?__biz=MzAxODcyNjEzNQ==&mid=2247484344&idx=1&sn=ceabcd9f4dbcf596fde8f7c1c1c88375&chksm=9bd0ae20aca72736d7867e14eb149e852eaf4502b8d49404f27285e997a5aec7dc65562d558f&scene=21#wechat_redirect

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市簿透,隨后出現(xiàn)的幾起案子移袍,更是在濱河造成了極大的恐慌,老刑警劉巖老充,帶你破解...
    沈念sama閱讀 211,265評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件葡盗,死亡現(xiàn)場離奇詭異,居然都是意外死亡蚂维,警方通過查閱死者的電腦和手機(jī)戳粒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評論 2 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來虫啥,“玉大人蔚约,你說我怎么就攤上這事⊥孔眩” “怎么了苹祟?”我有些...
    開封第一講書人閱讀 156,852評論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我树枫,道長直焙,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,408評論 1 283
  • 正文 為了忘掉前任砂轻,我火速辦了婚禮奔誓,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘搔涝。我一直安慰自己厨喂,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,445評論 5 384
  • 文/花漫 我一把揭開白布庄呈。 她就那樣靜靜地躺著蜕煌,像睡著了一般。 火紅的嫁衣襯著肌膚如雪诬留。 梳的紋絲不亂的頭發(fā)上斜纪,一...
    開封第一講書人閱讀 49,772評論 1 290
  • 那天,我揣著相機(jī)與錄音文兑,去河邊找鬼盒刚。 笑死,一個(gè)胖子當(dāng)著我的面吹牛绿贞,可吹牛的內(nèi)容都是我干的伪冰。 我是一名探鬼主播,決...
    沈念sama閱讀 38,921評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼樟蠕,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了靠柑?” 一聲冷哼從身側(cè)響起寨辩,我...
    開封第一講書人閱讀 37,688評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎歼冰,沒想到半個(gè)月后靡狞,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,130評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡隔嫡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,467評論 2 325
  • 正文 我和宋清朗相戀三年甸怕,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片腮恩。...
    茶點(diǎn)故事閱讀 38,617評論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡梢杭,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出秸滴,到底是詐尸還是另有隱情武契,我是刑警寧澤,帶...
    沈念sama閱讀 34,276評論 4 329
  • 正文 年R本政府宣布,位于F島的核電站咒唆,受9級特大地震影響届垫,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜全释,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,882評論 3 312
  • 文/蒙蒙 一装处、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧浸船,春花似錦妄迁、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至项戴,卻和暖如春形帮,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背周叮。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評論 1 265
  • 我被黑心中介騙來泰國打工辩撑, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人仿耽。 一個(gè)月前我還...
    沈念sama閱讀 46,315評論 2 360
  • 正文 我出身青樓合冀,卻偏偏與公主長得像,于是被迫代替她去往敵國和親项贺。 傳聞我的和親對象是個(gè)殘疾皇子君躺,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,486評論 2 348