Spring Security認證系統(tǒng)淺析-龍果學(xué)院 Spring Security認證系統(tǒng)淺析
標簽:安全發(fā)布于 2017-05-26 12:42:47
Spring Security是個安全框架眾所周知菊卷,同時也提供了一整套基于Web的認證機制和安全服務(wù)速勇,當然如果你要通過其他協(xié)議的來實現(xiàn)安全服務(wù)垢油,你也可以使用SpringSecurity來幫助你其障。
今天主要是講解基于web端的安全認證機制堰乔,其他的留著以后有空再整理成博文伴找。
首先SpringSecurity Web是基于它所提供的一整套Filter鏈來實現(xiàn)的锰镀,我們來看下它所提供的有哪些Filter:
在列表中可以看到斟赚,這些Filter鏈是有序的着降,而且自身所提供的Filter是無法更改順序的,so what拗军?任洞??當然发侵,你都能任意更改順序了交掏,那我怎么能保證在不同的filter中拿到正確的數(shù)據(jù)呢,是吧刃鳄?這些filter是SpringSecurity在啟動時會默認注入到容器中的盅弛,你可以根據(jù)自身業(yè)務(wù)的需要在這些filter的before或after插入自己設(shè)計的filter。
那每一個Filter具體是做了什么工作,大家自行去查閱熊尉,這不是今天講的重點罐柳,今天著重講基于web的認證機制。
先來看看SpringSecurity中認證時的幾個關(guān)鍵類:
org.springframework.security.web.context.SecurityContextRepository
org.springframework.security.web.context.SecurityContextPersistenceFilter
org.springframework.security.core.context.SecurityContextHolder
org.springframework.security.core.context.SecurityContextHolderStrategy
org.springframework.security.core.Authentication
首先來看下作為用戶在login時的交互圖狰住,這個圖確實太大了张吉,已經(jīng)盡量縮小了,但還是很大催植。
從圖中我們可以看到肮蛹,用戶在請求服務(wù)時,首先會經(jīng)過圖中的SecurityContextPersistenceFilter(其實這個類在Filter鏈中是第二個被處理的)创南,這個類是干嘛的呢伦忠,主要就是用于設(shè)置SecurityContext到SecurityContextHolder中,具體的我們通過圖和源碼一點點分析
首先看看SecurityContextPersistenceFilter.class
publicSecurityContextPersistenceFilter()?{
this(newHttpSessionSecurityContextRepository());
}
publicSecurityContextPersistenceFilter(SecurityContextRepository?repo)?{
this.repo?=?repo;
}
我們可以看到這個類的構(gòu)造方法是需要設(shè)置SecurityContextRepository的稿辙,默認會設(shè)置為HttpSessionSecurityContextRepository.class昆码,這個東西是拿來干嘛的?是用于設(shè)置SecurityContext的存儲機制的邻储,這個類對于你每次使用SecurityContextHolder在getContext時是否能拿到正確的context起到了關(guān)鍵性的作用赋咽。可以看到默認使用HttpSession來實現(xiàn)SecutiryContext的存儲吨娜。
接下來我們看看doFilter中干了些什么事:
HttpRequestResponseHolder?holder?=newHttpRequestResponseHolder(request,response);
//?這里可以看到從repo中取得SecurityContext脓匿,默認是從HttpSession里獲取
SecurityContext?contextBeforeChainExecution?=?repo.loadContext(holder);
try{
//?同時在這里將創(chuàng)建好的SecurityContext設(shè)置到SecurityContextHolder中????????????SecurityContextHolder.setContext(contextBeforeChainExecution);
chain.doFilter(holder.getRequest(),?holder.getResponse());
}
再來看一下repo.loadCotext的實現(xiàn)。
//?先從Session里去讀取是否存在SecurityContext
SecurityContext?context?=?readSecurityContextFromSession(httpSession);
//?如果不存在將創(chuàng)建一個空的SecurityContext
if(context?==null)?{
context?=?generateNewContext();
}
returncontext;
protectedSecurityContext?generateNewContext()?{
returnSecurityContextHolder.createEmptyContext();
}
privateSecurityContext?readSecurityContextFromSession(HttpSession?httpSession)?{
Object?contextFromSession?=?httpSession.getAttribute(springSecurityContextKey);
}
在這里可以看到如果Session是一個新的話宦赠,這里的ScurityContext會從SecurityContextHolder中去創(chuàng)建陪毡,在SecurityContextHolder中可以看到默認會通過ThreadLocalSecurityContextHolderStrategy創(chuàng)建一個基于ThreadLocal存儲線程安全的SecurityContext,那當前線程中所拿到的SecurityContext均是同一個實例勾扭。
現(xiàn)在知道了SecurityContext是如何創(chuàng)建了之后毡琉,再回頭來看SecurityContextPersistenceFilter中的doFilter實現(xiàn)
try{
//?在這里將創(chuàng)建好的SecurityContext設(shè)置到SecurityContextHolder中
SecurityContextHolder.setContext(contextBeforeChainExecution);
chain.doFilter(holder.getRequest(),?holder.getResponse());
}
現(xiàn)在已經(jīng)在chain.doFilter()之前已經(jīng)完成了對SecurityContext的設(shè)置,接下來過濾器鏈繼續(xù)執(zhí)行妙色,如果你在配置SecurityConfig中使用了UsernamePasswordAuthenticationFilter.class來實現(xiàn)對用戶的認證桅滋,那我們將會進入到UsernamePasswordAuthenticationFilter的doFilter來實現(xiàn)認證
Authentication?authResult;
try{
authResult?=?attemptAuthentication(request,?response);
if(authResult?==null)?{
//?return?immediately?as?subclass?has?indicated?that?it?hasn't?completed
//?authentication
return;
}
sessionStrategy.onAuthentication(authResult,?request,?response);
}
successfulAuthentication(request,?response,?chain,?authResult);
protectedvoidsuccessfulAuthentication(HttpServletRequest?request,
HttpServletResponse?response,?FilterChain?chain,?Authentication?authResult)
throwsIOException,?ServletException?{
if(logger.isDebugEnabled())?{
logger.debug("Authentication?success.?Updating?SecurityContextHolder?to?contain:?"
+?authResult);
}
SecurityContextHolder.getContext().setAuthentication(authResult);
rememberMeServices.loginSuccess(request,?response,?authResult);
//?Fire?event
if(this.eventPublisher?!=null)?{
eventPublisher.publishEvent(newInteractiveAuthenticationSuccessEvent(
authResult,this.getClass()));
}
successHandler.onAuthenticationSuccess(request,?response,?authResult);
}
Usernamepasswordauthenticationfilter代碼
public?Authentication?attemptAuthentication(HttpServletRequest?request,
HttpServletResponse?response)?throws?AuthenticationException?{
if?(postOnly?&&?!request.getMethod().equals("POST"))?{
throw?new?AuthenticationServiceException(
"Authentication?method?not?supported:?"+?request.getMethod());
}
String?username?=?obtainUsername(request);
String?password?=?obtainPassword(request);
if?(username?==?null)?{
username?="";
}
if?(password?==?null)?{
password?="";
}
username?=?username.trim();
UsernamePasswordAuthenticationToken?authRequest?=?new?UsernamePasswordAuthenticationToken(
username,?password);
//?Allow?subclasses?to?set?the"details"property
setDetails(request,?authRequest);
return?this.getAuthenticationManager().authenticate(authRequest);
}
一旦attemptAuthentication認證通過之后,可以在successfulAuthentication方法中可以看到調(diào)用了SecurityContextHolder.getContext().setAuthentication(authResult)來完成對用戶的認證燎斩,到這里已經(jīng)完成了基于SpringSecurity所提供的一套web完整的認證過程虱歪。
在這里就不對UsernamePasswordAuthenticationFilter里的認證機制做詳細的闡述了,大家有興趣的話自己去翻源碼查看吧栅表。還是比較簡單里笋鄙,里面設(shè)計到了AuthenticationManager、AuthenticationProvider怪瓶、UserDetailsService等相關(guān)的類萧落。
如果你使用的restful接口來實現(xiàn)認證的話践美,你也可以在驗證之后構(gòu)造一個Authentication實例,再通過SecurityContextHolder.getContext().setAuthentication( authentication )來實現(xiàn)對SpringSecurity的認證找岖。
原文請參考:Spring Security認證系統(tǒng)淺析-龍果學(xué)院? http://www.roncoo.com/article/detail/128454