SpringSecurity過濾器原理
點(diǎn)擊上方藍(lán)色字體,選擇“標(biāo)星公眾號”
優(yōu)質(zhì)文章,第一時(shí)間送達(dá)

-? ? ?主要過濾器鏈? ? -
SpringSecurity的功能主要是由一系列的過濾器鏈相互配合完成的。驗(yàn)證一個(gè)過濾器之后放行到下一個(gè)過濾器鏈,然后到最后。


-? ? ?認(rèn)證流程? ? -


-? ? ?過濾器作用? ? -
SecurityContextPersistenceFilter:會在每次請求處理之前從配置好的SecurityContextRepository中獲取SecurityContext安全上下文信息,然后加載到SecurityContextHolder中,然后在該次請求處理完成之后,將SecurityContextHolder中關(guān)于這次請求的信息存儲到一個(gè)“倉庫”中,然后將SecurityContextHolder中的信息清除,例如在Session中維護(hù)一個(gè)用戶的安全信息就是這個(gè)過濾器處理的。
DefaultLoginPageGeneratingFilter:如果沒有配置自定義登錄頁面,那系統(tǒng)初始化時(shí)就會配置這個(gè)過濾器,并且用于在需要進(jìn)行登錄時(shí)生成一個(gè)登錄表單頁面。
BasicAuthenticationFilter:檢測和處理http basic認(rèn)證。
UsernamePasswordAuthenticationFilter:用于處理基于表單的登錄請求,從表單中獲取用戶名和密碼。默認(rèn)情況下處理來自/login的表單action。從表單中獲取用戶名和密碼時(shí),默認(rèn)使用的表單name屬性值為username和password,這倆個(gè)值也可以通過usernameParameter和passwordParameter在配置中自定義。
這個(gè)過濾器在表單提交登錄請求之時(shí)會起作用。那么假設(shè)現(xiàn)在采用SpringSecurity整合Jwt,那么我需要配置一個(gè)Jwt登錄認(rèn)證類(繼承BasicAuthenticationFilter或者繼承OncePerRequestFilter都可以,因?yàn)锽asicAuthenticationFilter繼承了OncePerRequestFilter),重寫過濾器方法。Jwt的token認(rèn)證登錄是需要在在采用用戶名密碼登錄認(rèn)證之前,所以在配置Jwt登錄認(rèn)證類的時(shí)候需要在UsernamePasswordAuthenticationFilter之前添加過濾器。
//配置自定義過濾器?添加jwt登錄授權(quán)過濾器
//在過濾器UsernamePasswordAuthenticationFilter之前
http.addFilterBefore(jwtAuthenticationFilter,UsernamePasswordAuthenticationFilter.class);RequestCacheAwareFilter:用來處理請求的緩存。
SecurityContextHolderAwareRequestFilter:主要是包裝請求對象request。
AnonymousAuthenticationFilter:檢測SecurityContextHolder中是否存在Authentication對象,如果不存在則為其提供一個(gè)匿名Authentication。
SessionManagementFilter:管理Session的過濾器
ExceptionTranslationFilter:捕獲來自過濾器鏈的所有異常,并進(jìn)行處理。但是只處理兩類異常:AccessDeniedException和AuthenticationException 異常,其他的異常會繼續(xù)拋出。
如果捕獲到的AuthenticationException,那么將會使用其對應(yīng)的AuthenticationEntryPoint的commence()方法處理。在處理之前,ExceptionTranslationFilter先使用RequestCache將當(dāng)前的HTTPServletRequest的信息保存起來,方便用戶登錄成功后可以跳轉(zhuǎn)到之前的頁面。
可以自定義AuthenticationException的處理方法。需要實(shí)現(xiàn)AuthenticationEntryPoint接口,然后重寫commence()方法。
/**
?*?當(dāng)未登錄或者token失效時(shí)訪問接口自定義的返回結(jié)果
?*/
@Component
public?class?RestfulAuthorizationEntryPoint?implements?AuthenticationEntryPoint?{
????@Override
????public?void?commence(HttpServletRequest?request,?HttpServletResponse?response,?AuthenticationException?e)?throws?IOException,?ServletException?{
????????response.setCharacterEncoding("utf-8");
????????response.setContentType("application/json");
????????PrintWriter?writer?=?response.getWriter();
????????RespBean?bean?=?RespBean.error("請先登錄!");
????????bean.setCode(401);
????????writer.write(new?ObjectMapper().writeValueAsString(bean));
????????writer.flush();
????????writer.close();
????}
}如果捕獲的AuthenticationDeniedException,那么將會根據(jù)當(dāng)前訪問的用戶是否已經(jīng)登錄認(rèn)證做不同的處理,如果未登錄,則會使用關(guān)聯(lián)的AuthenticationEntryPoint的commence()方法進(jìn)行處理,否則將使用關(guān)聯(lián)的AccessDeniedHandler的handle()方法進(jìn)行處理。
可以進(jìn)行自定義AuthenticationDeniedException的處理方法。需要實(shí)現(xiàn)AccessDeniedHandler接口,然后重寫handle()方法。
@Component
public?class?RestfulAccessDeniedHandler?implements?AccessDeniedHandler?{
????@Override
????public?void?handle(HttpServletRequest?request,?HttpServletResponse?response,?AccessDeniedException?e)?throws?IOException,?ServletException?{
????????response.setCharacterEncoding("utf-8");
????????response.setContentType("application/json");
????????PrintWriter?writer?=?response.getWriter();
????????RespBean?error?=?RespBean.error("權(quán)限不足,聯(lián)系管理員!");
????????writer.write(new?ObjectMapper().writeValueAsString(error));
????????error.setCode(403);
????????writer.flush();
????????writer.close();
????}
}FilterSecurityInterceptor:可以看做過濾器鏈的出口
RememberMeAuthenticationFilter:當(dāng)用戶沒有登錄而直接訪問資源時(shí), 從 cookie 里找出用戶的信息, 如果 Spring Security 能夠識別出用戶提供的remember me cookie, 用戶將不必填寫用戶名和密碼, 而是直接登錄進(jìn)入系統(tǒng),該過濾器默認(rèn)不開啟。

-? ? ?SecurityContextHolder? ? -
SecurityContext對象是安全上下文信息,包括當(dāng)前使用系統(tǒng)的用戶的信息。每個(gè)用戶都會有它的安全上下文對象,所以把每一個(gè)用戶的SecurityContext保存到SecurityContextHolder中。
SecurityContextHolder存儲SecurityContext的方式根據(jù)應(yīng)用場景不同也有區(qū)別:
(1)單機(jī)系統(tǒng),即應(yīng)用從開啟到關(guān)閉的整個(gè)生命周期只有一個(gè)用戶在使用。由于整個(gè)應(yīng)用只需要保存一個(gè)SecurityContext(安全上下文即可)
(2)多用戶系統(tǒng),比如典型的Web系統(tǒng),整個(gè)生命周期可能同時(shí)有多個(gè)用戶在使用。這時(shí)候應(yīng)用需要保存多個(gè)SecurityContext(安全上下文),需要利用ThreadLocal進(jìn)行保存,每個(gè)線程都可以利用ThreadLocal獲取其自己的SecurityContext,及安全上下文。ThreadLocal內(nèi)部會用數(shù)組來存儲多個(gè)對象的。原理是,ThreadLocal會為每個(gè)線程開辟一個(gè)存儲區(qū)域,來存儲相應(yīng)的對象。
Authentication:用戶信息的表示
在SecurityContextHolder中存儲了當(dāng)前與系統(tǒng)交互的用戶的信息。Spring Security使用一個(gè)Authentication 對象來表示這些信息。
Authentication 主要包含了:
用戶權(quán)限集合
用戶證書(密碼)
細(xì)節(jié)(Details)
Principal(就是這個(gè)用戶的賬戶信息)
在自定義登錄認(rèn)證過濾器的時(shí)候,記得需要把用戶的信息(Authentication )保存到SecurityContextHolder中,以便后續(xù)用戶的正常使用。比如我在做和Jwt認(rèn)證的整合的時(shí)候,繼承OncePerRequestFilter,重寫doFilterInternal方法,認(rèn)證完token之后,就需要把用戶的信息存入安全上下文Holder中。
UsernamePasswordAuthenticationToken?authenticationToken
????=new?UsernamePasswordAuthenticationToken(user,null,?null);
authenticationToken.setDetails(new?WebAuthenticationDetailsSource()
???????????????????????????????.buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
? 作者?|??ins1mnia
來源 |??cnblogs.com/ins1mnia/p/15635130.html
加鋒哥微信:?java1239?? 圍觀鋒哥朋友圈,每天推送Java干貨!

