Spring-Security-文檔筆記之認(rèn)證機(jī)制

1. 用戶名密碼認(rèn)證

用戶名密碼讀取方式有三:

  • 表單
  • Basic認(rèn)證
  • Digest認(rèn)證

存儲(chǔ)機(jī)制:

  • 內(nèi)存存儲(chǔ)
  • JDBC存儲(chǔ)
  • 自定義存儲(chǔ) UserDetailsService
  • LDAP存儲(chǔ)
1.1 表單登錄

用戶是如何被重定向到登錄表單的:


image.png
  1. 用戶向/private發(fā)起未認(rèn)證請求.
  2. FilterSecurityInterceptor通過拋出AccessDeniedException表示拒絕這個(gè)請求.
  3. 因?yàn)橛脩粑凑J(rèn)證, ExceptionTranslationFilter開始認(rèn)證過程并通過AuthenticationEntryPoint發(fā)送一個(gè)重定向到登錄頁面的響應(yīng).
  4. 瀏覽器重定向到登錄頁面.
  5. 顯示登錄頁面.

用戶提交用戶名密碼后的處理過程:


image.png
  1. 當(dāng)用戶提交用戶名密碼后, UserPasswordAuthenticationFilter創(chuàng)建一個(gè)UsernamePasswordAuthenticationToken(從請求中解析用戶名密碼)
  2. token被傳遞給AuthenticationManager進(jìn)行認(rèn)證.
  3. 后續(xù)過程同AbstractAuthenticationProcessingFilter(UserPasswordAuthenticationFilter繼承自AbstractAuthenticationProcessingFilter)

Spring Security 的表單登錄功能默認(rèn)開啟, 但是如果提供了任何基于servlet的配置, 則需要顯式配置:

// java
protected void configure(HttpSecurity http) {
    http
        // ...
        .formLogin(withDefaults());
}

// xml
<http>
    <form-login />
</http>

自定義登錄頁面

// java
protected void configure(HttpSecurity http) throws Exception {
    http
        // ...
        .formLogin(form -> form
            .loginPage("/login")
            .permitAll()
        );
}

// xml
<http>
    <intercept-url pattern="/login" access="permitAll" />
    <form-login login-page="/login" />
</http>

登錄表單

  • 表單通過/login POST請求提交登錄數(shù)據(jù)
  • 表單需要包含一個(gè)CSRF token.
  • 用戶名參數(shù)名為 username
  • 密碼參數(shù)為 password

以上都可以自行配置, 如自定義登錄頁面, 則需要提供一個(gè)get方式的login請求, 以跳轉(zhuǎn)到登錄頁面.
java配置見FormLoginConfigurer. XML配置見<http>

1.2 Basic驗(yàn)證

Basic驗(yàn)證使用的是BasicAuthenticationEntryPoint.

配置:

// java
protected void configure(HttpSecurity http) {
    http
        // ...
        .httpBasic(withDefaults());
}

// xml
<http>
    <http-basic />
</http>
1.3 UserDetailsService

UserDetailsService通過使用DaoAuthenticationProvider來獲取用戶名,密碼及其他擴(kuò)展信息.

1.4 PasswordEncoder

Spring Security加密支持. 常用實(shí)現(xiàn)類為BCryptPasswordEncoder.

1.5 DaoAuthenticationProvider

AuthenticationProvider的實(shí)現(xiàn)類, 通過UserDetailsServicePasswordEncoder支持驗(yàn)證用戶名和密碼.

image.png
  1. 讀取用戶名和密碼到UsernamePasswordAuthenticationToken中, 并將其傳遞給ProviderManager.
  2. ProviderManager選擇調(diào)用DaoAuthenticationProvider.
  3. DaoAuthenticationProvider 通過UserDetailsService獲取UserDetails.
  4. 調(diào)用PasswordEncoder驗(yàn)證密碼.
  5. 如果驗(yàn)證成功, 返回UsernamePasswordAuthenticationToken(其principal為UserDetails對象). 并將其存儲(chǔ)到SecurityContextHolder中.

2. Session Management

與Session相關(guān)的功能由SessionManagementFilterSessionAuthenticationStrategy實(shí)現(xiàn). 功能包括防止Session固定會(huì)話攻擊, 檢測session超時(shí)和限制session并發(fā)數(shù).

2.1 檢測超時(shí)

session失效后可以重定向到session無效頁面.

<http>
  <session-management invalid-session-url="/invalidSession.html"/>
</http>

如果使用以上配置檢測session超時(shí), 如果用戶退出后又不關(guān)閉瀏覽器重新登錄叠必,可能會(huì)報(bào)錯(cuò). 這是因?yàn)閏ookie沒有被清除,即使用戶已經(jīng)注銷牌捷,會(huì)話cookie也會(huì)被重新提交亚脆。因此需要在logout時(shí)顯式地刪除JSESSIONID cookie.

<http>
    <logout delete-cookies="JSESSIONID" />
</http>

**注意: **如果在代理后面運(yùn)行應(yīng)用程序禀忆,那么還可以通過配置代理服務(wù)器來刪除會(huì)話cookie房轿。

2.2 session并發(fā)控制

限制用戶的登錄行為.
首先: 需要在web.xml中添加session事件監(jiān)聽器.

<listener>
  <listener-class>
    org.springframework.security.web.session.HttpSessionEventPublisher
  </listener-class>
</listener>

然后: 添加并發(fā)數(shù)量限制

// 當(dāng)error-if-maximum-exceeded為false時(shí), 第二次登錄將會(huì)剔掉第一次登錄. 
// 如果為true, 第二次登錄將會(huì)被拒絕. 如果是表單登錄,則會(huì)進(jìn)入認(rèn)證失敗頁面, 如果是rememberme, 則會(huì)返回401. 
<http>
  <session-management>
    <concurrency-control max-sessions="1" error-if-maximum-exceeded="true" />
  </session-management>
</http>
2.3 Session固定保護(hù)

session固定會(huì)話攻擊是利用服務(wù)器的session不變機(jī)制, 攻擊者欺騙用戶登錄(此時(shí)登錄所攜帶的sessionId為攻擊者設(shè)置的sessionId), 用戶登錄后, 攻擊者設(shè)置的sessionId對應(yīng)的會(huì)話合法了, 然后就可以利用這個(gè)sessionId冒充用戶以達(dá)到目的.
解決辦法就是用戶登錄后就修改session信息.
Spring Security通過session-fixation-protection屬性控制session策略.

  • none: 不做任何改變.
  • newSession: 創(chuàng)建一個(gè)全新的session, 不會(huì)復(fù)制session數(shù)據(jù), 但與spring security相關(guān)的屬性會(huì)被復(fù)制到新session中.
  • migrateSession: 創(chuàng)建一個(gè)新的session, 然后將舊session的數(shù)據(jù)復(fù)制到新的session中. servlet3.0及之前的默認(rèn)策略.
  • changeSessionId: 修改sessionId. servlet3.1及以后版本的默認(rèn)策略, 這里使用的是servlet的固定會(huì)話攻擊防護(hù)機(jī)制(HttpServletRequest#changeSessionId)

當(dāng)發(fā)生session固定攻擊時(shí), spring 容器會(huì)發(fā)布一個(gè)SessionFixationProtectionEvent. 如果使用changeSessionId機(jī)制, HttpSessionIdListener也會(huì)收到通知.

2.4 SessionManagementFilter

工作流程:

  1. 根據(jù)當(dāng)前SecurityContextHolder的內(nèi)容檢查SecurityContextRepository的內(nèi)容愈涩,以確定用戶在當(dāng)前請求期間是否已通過身份驗(yàn)證.
  2. 如果包含SecurityContext, 則不做任何事情.如果不包含, 但是當(dāng)前的 SecurityContext中包含一個(gè)匿名的Authentication對象, 它則假設(shè)用戶已被前面的filter進(jìn)行驗(yàn)證過, 它將調(diào)用SessionAuthenticationStrategy.
  3. 如果當(dāng)前用戶沒有經(jīng)過身份驗(yàn)證肠骆,篩選器將檢查是否請求了無效的會(huì)話ID(例如由于超時(shí))蚌本,如果設(shè)置了InvalidSessionStrategy盔粹,則將調(diào)用配置的InvalidSessionStrategy。最常見的行為就是重定向到一個(gè)固定的URL魂毁,這封裝在標(biāo)準(zhǔn)實(shí)現(xiàn)SimpleRedirectInvalidSessionStrategy中玻佩。
2.5 SessionAuthenticationStrategy

它被SessionManagementFilterAbstractAuthenticationProcessingFilter使用. 因此,如果使用定制的表單登錄類席楚,需要將它注入到這兩個(gè)類中咬崔。

<http>
<custom-filter position="FORM_LOGIN_FILTER" ref="myAuthFilter" />
<session-management session-authentication-strategy-ref="sas"/>
</http>

<beans:bean id="myAuthFilter" class=
"org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter">
    <beans:property name="sessionAuthenticationStrategy" ref="sas" />
    ...
</beans:bean>

<beans:bean id="sas" class=
"org.springframework.security.web.authentication.session.SessionFixationProtectionStrategy" />
2.6 并發(fā)控制

除了前面的配置, 還需要配置ConcurrentSessionFilterFilterChainProxy. 它有兩個(gè)參數(shù): SessionRegistry, SessionInformationExpiredStrategy.

<http>
<custom-filter position="CONCURRENT_SESSION_FILTER" ref="concurrencyFilter" />
<custom-filter position="FORM_LOGIN_FILTER" ref="myAuthFilter" />

<session-management session-authentication-strategy-ref="sas"/>
</http>

<beans:bean id="redirectSessionInformationExpiredStrategy"
class="org.springframework.security.web.session.SimpleRedirectSessionInformationExpiredStrategy">
<beans:constructor-arg name="invalidSessionUrl" value="/session-expired.htm" />
</beans:bean>

<beans:bean id="concurrencyFilter"
class="org.springframework.security.web.session.ConcurrentSessionFilter">
<beans:constructor-arg name="sessionRegistry" ref="sessionRegistry" />
<beans:constructor-arg name="sessionInformationExpiredStrategy" ref="redirectSessionInformationExpiredStrategy" />
</beans:bean>

<beans:bean id="myAuthFilter" class=
"org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter">
<beans:property name="sessionAuthenticationStrategy" ref="sas" />
<beans:property name="authenticationManager" ref="authenticationManager" />
</beans:bean>

<beans:bean id="sas" class="org.springframework.security.web.authentication.session.CompositeSessionAuthenticationStrategy">
<beans:constructor-arg>
    <beans:list>
    <beans:bean class="org.springframework.security.web.authentication.session.ConcurrentSessionControlAuthenticationStrategy">
        <beans:constructor-arg ref="sessionRegistry"/>
        <beans:property name="maximumSessions" value="1" />
        <beans:property name="exceptionIfMaximumExceeded" value="true" />
    </beans:bean>
    <beans:bean class="org.springframework.security.web.authentication.session.SessionFixationProtectionStrategy">
    </beans:bean>
    <beans:bean class="org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy">
        <beans:constructor-arg ref="sessionRegistry"/>
    </beans:bean>
    </beans:list>
</beans:constructor-arg>
</beans:bean>

<beans:bean id="sessionRegistry"
    class="org.springframework.security.core.session.SessionRegistryImpl" />
2.7. 類圖
image.png

3. Remeber-Me 認(rèn)證

網(wǎng)站可以記住用戶身份, 當(dāng)用戶再次訪問網(wǎng)站時(shí)可自動(dòng)登錄. 這是通過cookie機(jī)制來實(shí)現(xiàn)的. Spring Security提供了兩種實(shí)現(xiàn): 一是基于cookie的實(shí)現(xiàn), 二是持久存儲(chǔ)的實(shí)現(xiàn)(數(shù)據(jù)庫). 這兩種實(shí)現(xiàn)都需要UserDetailsService.

3.1 基于hash的token方式

在用戶身份認(rèn)證成功之后會(huì)發(fā)送一個(gè)cookie到瀏覽器. 其格式如下:

base64(username + ":" + expirationTime + ":" +
md5Hex(username + ":" + expirationTime + ":" password + ":" + key))

username:          用戶名
password:          密碼
expirationTime:    token過期時(shí)間, 單位為毫秒
key:               防止remember-me令牌被修改的私鑰

啟用remeber-me

<http>
    <remember-me key="myAppKey"/>
</http>
3.2 持久化token

使用這種方式需要提供數(shù)據(jù)源.

<http>
<remember-me data-source-ref="someDataSource"/>
</http>

數(shù)據(jù)庫表建表SQL:

create table persistent_logins (username varchar(64) not null,
                                series varchar(64) primary key,
                                token varchar(64) not null,
                                last_used timestamp not null)
3.3 RemeberMe接口及實(shí)現(xiàn)

UsernamePasswordAuthenticationFilter具有rememberme功能, 它是通過AbstractAuthenticationProcessingFilter中的鉤子實(shí)現(xiàn), 這個(gè)鉤子調(diào)用RememberMeServices.

// RememberMeServices
Authentication autoLogin(HttpServletRequest request, HttpServletResponse response);

void loginFail(HttpServletRequest request, HttpServletResponse response);

void loginSuccess(HttpServletRequest request, HttpServletResponse response,
    Authentication successfulAuthentication);

UsernamePasswordAuthenticationFilter只會(huì)調(diào)用loginFailloginSuccess方法. autoLogin是在RememberMeAuthenticationFilter中調(diào)用.

PersistentTokenBasedRememberMeServices
PersistentTokenBasedRememberMeServices還需要一個(gè)UserDetailsService, 以獲取用戶信息進(jìn)行驗(yàn)證并生成RemeberMeAuthenticationToken. 另外它還依賴PersistentTokenRepository以用于存儲(chǔ)token. 它默認(rèn)有兩種實(shí)現(xiàn):

  • InMemoryTokenRepositoryImpl
  • JdbcTokenRepositoryImpl
3.4 RememberMeAuthenticationFilter類圖
image.png

4. 匿名認(rèn)證

4.1 介紹

“匿名身份驗(yàn)證”的用戶和未經(jīng)身份驗(yàn)證的用戶之間并沒有真正的概念上的區(qū)別。匿名身份驗(yàn)證的好處是,所有URI模式都可以應(yīng)用安全性, 還可保證SecutiryContextHolder總是包含一個(gè)Authentication對象而不是null.

4.2 配置
<http>
  <anonymous/>
</http>

三個(gè)類提供了身份認(rèn)證功能:

  • AnonymousAuthenticationToken
    用于存儲(chǔ)為匿名用戶授權(quán)的權(quán)限(GrantedAuthority)
  • AnonymousAuthenticationProvider
  • AnonymousAuthenticationFilter
<bean id="anonymousAuthFilter"
    class="org.springframework.security.web.authentication.AnonymousAuthenticationFilter">
<property name="key" value="foobar"/>
<property name="userAttribute" value="anonymousUser,ROLE_ANONYMOUS"/>
</bean>

<bean id="anonymousAuthenticationProvider"
    class="org.springframework.security.authentication.AnonymousAuthenticationProvider">
<property name="key" value="foobar"/>
</bean>

key在provider和filter之間共享,

4.3 AuthenticationTrustResolver
4.4 spring mvc中獲取匿名Authentication對象.

使用@CurrentSecurityContext.

@GetMapping("/")
public String method(@CurrentSecurityContext SecurityContext context) {
    return context.getAuthentication().getName();
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末垮斯,一起剝皮案震驚了整個(gè)濱河市郎仆,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌兜蠕,老刑警劉巖扰肌,帶你破解...
    沈念sama閱讀 222,681評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異熊杨,居然都是意外死亡曙旭,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,205評論 3 399
  • 文/潘曉璐 我一進(jìn)店門晶府,熙熙樓的掌柜王于貴愁眉苦臉地迎上來桂躏,“玉大人,你說我怎么就攤上這事川陆〖料埃” “怎么了?”我有些...
    開封第一講書人閱讀 169,421評論 0 362
  • 文/不壞的土叔 我叫張陵较沪,是天一觀的道長鳞绕。 經(jīng)常有香客問我,道長尸曼,這世上最難降的妖魔是什么们何? 我笑而不...
    開封第一講書人閱讀 60,114評論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮骡苞,結(jié)果婚禮上垂蜗,老公的妹妹穿的比我還像新娘。我一直安慰自己解幽,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,116評論 6 398
  • 文/花漫 我一把揭開白布烘苹。 她就那樣靜靜地躺著躲株,像睡著了一般。 火紅的嫁衣襯著肌膚如雪镣衡。 梳的紋絲不亂的頭發(fā)上霜定,一...
    開封第一講書人閱讀 52,713評論 1 312
  • 那天,我揣著相機(jī)與錄音廊鸥,去河邊找鬼望浩。 笑死,一個(gè)胖子當(dāng)著我的面吹牛惰说,可吹牛的內(nèi)容都是我干的磨德。 我是一名探鬼主播,決...
    沈念sama閱讀 41,170評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼典挑!你這毒婦竟也來了酥宴?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 40,116評論 0 277
  • 序言:老撾萬榮一對情侶失蹤您觉,失蹤者是張志新(化名)和其女友劉穎拙寡,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體琳水,經(jīng)...
    沈念sama閱讀 46,651評論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡肆糕,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,714評論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了在孝。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片诚啃。...
    茶點(diǎn)故事閱讀 40,865評論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖浑玛,靈堂內(nèi)的尸體忽然破棺而出绍申,到底是詐尸還是另有隱情,我是刑警寧澤顾彰,帶...
    沈念sama閱讀 36,527評論 5 351
  • 正文 年R本政府宣布极阅,位于F島的核電站,受9級特大地震影響涨享,放射性物質(zhì)發(fā)生泄漏筋搏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,211評論 3 336
  • 文/蒙蒙 一厕隧、第九天 我趴在偏房一處隱蔽的房頂上張望奔脐。 院中可真熱鬧,春花似錦吁讨、人聲如沸髓迎。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,699評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽排龄。三九已至,卻和暖如春翎朱,著一層夾襖步出監(jiān)牢的瞬間橄维,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,814評論 1 274
  • 我被黑心中介騙來泰國打工拴曲, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留争舞,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,299評論 3 379
  • 正文 我出身青樓澈灼,卻偏偏與公主長得像竞川,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,870評論 2 361

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

  • 在Web應(yīng)用程序中的身份驗(yàn)證 現(xiàn)在讓我們來看看你在Web應(yīng)用程序中使用Spring Security的情況(不啟用...
    kuisasa閱讀 1,257評論 0 1
  • 原理Spring Security是一個(gè)功能強(qiáng)大且高度可定制的身份驗(yàn)證和訪問控制框架流译。它是用于保護(hù)基于Spring...
    MlLance閱讀 580評論 2 1
  • 本文基于 Spring Security 5.3版本官方文檔 一逞怨、簡介 Spring Security是一個(gè)提供身...
    慵懶的陽光丶閱讀 612評論 0 0
  • Spring Security 是一個(gè)基于 Spring AOP 和 Servlet 過濾器的安全框架,它提供了安...
    聰明的奇瑞閱讀 17,124評論 3 38
  • 簡介 Spring Security:是一個(gè)提供身份驗(yàn)證福澡,授權(quán)和保護(hù)以防止常見攻擊的框架叠赦。 憑借對命令式和反應(yīng)式應(yīng)...
    呼呼菜菜閱讀 3,793評論 1 7