本篇內(nèi)容主要講解“Spring Security資源放行策略有哪些”,感興趣的朋友不妨來(lái)看看。本文介紹的方法操作簡(jiǎn)單快捷,實(shí)用性強(qiáng)。下面就讓小編來(lái)帶大家學(xué)習(xí)“Spring Security資源放行策略有哪些”吧!
創(chuàng)新互聯(lián)主營(yíng)長(zhǎng)海網(wǎng)站建設(shè)的網(wǎng)絡(luò)公司,主營(yíng)網(wǎng)站建設(shè)方案,成都app開發(fā),長(zhǎng)海h5重慶小程序開發(fā)公司搭建,長(zhǎng)海網(wǎng)站營(yíng)銷推廣歡迎長(zhǎng)海等地區(qū)企業(yè)咨詢Spring Security 中,到底該怎么樣給資源額外放行?
1.兩種思路
在 Spring Security 中,有一個(gè)資源,如果你希望用戶不用登錄就能訪問,那么一般來(lái)說(shuō),你有兩種配置策略:
第一種就是在 configure(WebSecurity web) 方法中配置放行,像下面這樣:
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/css/**",
"/js/**",
"/index.html",
"/img/**",
"/fonts/**",
"/favicon.ico",
"/verifyCode");
}
第二種方式是在 configure(HttpSecurity http) 方法中進(jìn)行配置:
http.authorizeRequests()
.antMatchers("/hello").permitAll()
.anyRequest().authenticated()
兩種方式大的區(qū)別在于,第一種方式是不走 Spring Security 過濾器鏈,而第二種方式走 Spring Security 過濾器鏈,在過濾器鏈中,給請(qǐng)求放行。
在我們使用 Spring Security 的時(shí)候,有的資源可以使用第一種方式額外放行,不需要驗(yàn)證,例如前端頁(yè)面的靜態(tài)資源,就可以按照第一種方式配置放行。
有的資源放行,則必須使用第二種方式,例如登錄接口。大家知道,登錄接口也是必須要暴露出來(lái)的,不需要登錄就能訪問到的,但是我們卻不能將登錄接口用第一種方式暴露出來(lái),登錄請(qǐng)求必須要走 Spring Security 過濾器鏈,因?yàn)樵谶@個(gè)過程中,還有其他事情要做。
接下來(lái)我以登錄接口為例,來(lái)和小伙伴們分析一下走 Spring Security 過濾器鏈有什么不同。
2.登錄請(qǐng)求分析
首先大家知道,當(dāng)我們使用 Spring Security,用戶登錄成功之后,有兩種方式獲取用戶登錄信息:
SecurityContextHolder.getContext().getAuthentication()
在 Controller 的方法中,加入 Authentication 參數(shù)
這兩種辦法,都可以獲取到當(dāng)前登錄用戶信息。
這兩種方式獲取到的數(shù)據(jù)都是來(lái)自 SecurityContextHolder,SecurityContextHolder 中的數(shù)據(jù),本質(zhì)上是保存在 ThreadLocal 中,ThreadLocal 的特點(diǎn)是存在它里邊的數(shù)據(jù),哪個(gè)線程存的,哪個(gè)線程才能訪問到。
這樣就帶來(lái)一個(gè)問題,當(dāng)用戶登錄成功之后,將用戶用戶數(shù)據(jù)存在 SecurityContextHolder 中(thread1),當(dāng)下一個(gè)請(qǐng)求來(lái)的時(shí)候(thread2),想從 SecurityContextHolder 中獲取用戶登錄信息,卻發(fā)現(xiàn)獲取不到!為啥?因?yàn)樗鼈z不是同一個(gè) Thread。
但實(shí)際上,正常情況下,我們使用 Spring Security 登錄成功后,以后每次都能夠獲取到登錄用戶信息,這又是怎么回事呢?
這我們就要引入 Spring Security 中的
SecurityContextPersistenceFilter 了。
小伙伴們都知道,無(wú)論是 Spring Security 還是 Shiro,它的一系列功能其實(shí)都是由過濾器來(lái)完成的,在 Spring Security 中,前面跟大家聊了
UsernamePasswordAuthenticationFilter 過濾器,在這個(gè)過濾器之前,還有一個(gè)過濾器就是
SecurityContextPersistenceFilter,請(qǐng)求在到達(dá)
UsernamePasswordAuthenticationFilter 之前都會(huì)先經(jīng)過
SecurityContextPersistenceFilter。
我們來(lái)看下它的源碼(部分):
public class SecurityContextPersistenceFilter extends GenericFilterBean {
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
HttpRequestResponseHolder holder =
new HttpRequestResponseHolder(request,
response);
SecurityContext contextBeforeChainExecution = repo.loadContext(holder);
try {
SecurityContextHolder.setContext(contextBeforeChainExecution);
chain.doFilter(holder.getRequest(), holder.getResponse());
}
finally {
SecurityContext contextAfterChainExecution = SecurityContextHolder
.getContext();
SecurityContextHolder.clearContext();
repo.saveContext(contextAfterChainExecution, holder.getRequest(),
holder.getResponse());
}
}
}
原本的方法很長(zhǎng),我這里列出來(lái)了比較關(guān)鍵的幾個(gè)部分:
SecurityContextPersistenceFilter 繼承自 GenericFilterBean,而 GenericFilterBean 則是 Filter 的實(shí)現(xiàn),所以 SecurityContextPersistenceFilter 作為一個(gè)過濾器,它里邊最重要的方法就是 doFilter 了。
在 doFilter 方法中,它首先會(huì)從 repo 中讀取一個(gè) SecurityContext 出來(lái),這里的 repo 實(shí)際上就是 HttpSessionSecurityContextRepository,讀取 SecurityContext 的操作會(huì)進(jìn)入到 readSecurityContextFromSession 方法中,在這里我們看到了讀取的核心方法 Object contextFromSession = httpSession.getAttribute(springSecurityContextKey);,這里的 springSecurityContextKey 對(duì)象的值就是 SPRING_SECURITY_CONTEXT,讀取出來(lái)的對(duì)象最終會(huì)被轉(zhuǎn)為一個(gè) SecurityContext 對(duì)象。
SecurityContext 是一個(gè)接口,它有一個(gè)唯一的實(shí)現(xiàn)類 SecurityContextImpl,這個(gè)實(shí)現(xiàn)類其實(shí)就是用戶信息在 session 中保存的 value。
在拿到 SecurityContext 之后,通過 SecurityContextHolder.setContext 方法將這個(gè) SecurityContext 設(shè)置到 ThreadLocal 中去,這樣,在當(dāng)前請(qǐng)求中,Spring Security 的后續(xù)操作,我們都可以直接從 SecurityContextHolder 中獲取到用戶信息了。
接下來(lái),通過 chain.doFilter 讓請(qǐng)求繼續(xù)向下走(這個(gè)時(shí)候就會(huì)進(jìn)入到 UsernamePasswordAuthenticationFilter 過濾器中了)。
在過濾器鏈走完之后,數(shù)據(jù)響應(yīng)給前端之后,finally 中還有一步收尾操作,這一步很關(guān)鍵。這里從 SecurityContextHolder 中獲取到 SecurityContext,獲取到之后,會(huì)把 SecurityContextHolder 清空,然后調(diào)用 repo.saveContext 方法將獲取到的 SecurityContext 存入 session 中。
至此,整個(gè)流程就很明了了。
每一個(gè)請(qǐng)求到達(dá)服務(wù)端的時(shí)候,首先從 session 中找出來(lái) SecurityContext ,然后設(shè)置到 SecurityContextHolder 中去,方便后續(xù)使用,當(dāng)這個(gè)請(qǐng)求離開的時(shí)候,SecurityContextHolder 會(huì)被清空,SecurityContext 會(huì)被放回 session 中,方便下一個(gè)請(qǐng)求來(lái)的時(shí)候獲取。
登錄請(qǐng)求來(lái)的時(shí)候,還沒有登錄用戶數(shù)據(jù),但是登錄請(qǐng)求走的時(shí)候,會(huì)將用戶登錄數(shù)據(jù)存入 session 中,下個(gè)請(qǐng)求到來(lái)的時(shí)候,就可以直接取出來(lái)用了。
看了上面的分析,我們可以至少得出兩點(diǎn)結(jié)論:
如果我們暴露登錄接口的時(shí)候,使用了前面提到的第一種方式,沒有走 Spring Security,過濾器鏈,則在登錄成功后,就不會(huì)將登錄用戶信息存入 session 中,進(jìn)而導(dǎo)致后來(lái)的請(qǐng)求都無(wú)法獲取到登錄用戶信息(后來(lái)的請(qǐng)求在系統(tǒng)眼里也都是未認(rèn)證的請(qǐng)求)
如果你的登錄請(qǐng)求正常,走了 Spring Security 過濾器鏈,但是后來(lái)的 A 請(qǐng)求沒走過濾器鏈(采用前面提到的第一種方式放行),那么 A 請(qǐng)求中,也是無(wú)法通過 SecurityContextHolder 獲取到登錄用戶信息的,因?yàn)樗婚_始沒經(jīng)過 SecurityContextPersistenceFilter 過濾器鏈。
3.總結(jié)
總之,前端靜態(tài)資源放行時(shí),可以直接不走 Spring Security 過濾器鏈,像下面這樣:
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/css/**","/js/**","/index.html","/img/**","/fonts/**","/favicon.ico");
}
后端的接口要額外放行,就需要仔細(xì)考慮場(chǎng)景了,不過一般來(lái)說(shuō),不建議使用上面這種方式,建議下面這種方式,原因前面已經(jīng)說(shuō)過了:
http.authorizeRequests()
.antMatchers("/hello").permitAll()
.anyRequest().authenticated()
到此,相信大家對(duì)“Spring Security資源放行策略有哪些”有了更深的了解,不妨來(lái)實(shí)際操作一番吧!這里是創(chuàng)新互聯(lián)網(wǎng)站,更多相關(guān)內(nèi)容可以進(jìn)入相關(guān)頻道進(jìn)行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!