認(rèn)證流程原理
認(rèn)證流程
SpringSecurity是基于Filter實(shí)現(xiàn)認(rèn)證和授權(quán),底層通過FilterChainProxy代理去調(diào)用各種Filter(Filter鏈),F(xiàn)ilter通過調(diào)用AuthenticationManager完成認(rèn)證 鞋既,通過調(diào)用AccessDecisionManager完成授權(quán)凉翻,SpringSecurity中核心的過濾器鏈詳細(xì)如下:
SecurityContextPersistenceFilter
Filter的入口和出口,它是用來將SecurityContext(認(rèn)證的上下文橡伞,里面有登錄成功后的認(rèn)證授權(quán)信息)對(duì)象持久到Session的Filter捶惜,同時(shí)會(huì)把SecurityContext設(shè)置給SecurityContextHolder方便我們獲取用戶認(rèn)證授權(quán)信息
UsernamePasswordAuthenticationFilter
默認(rèn)攔截“/login”登錄請(qǐng)求田藐,處理表單提交的登錄認(rèn)證,將請(qǐng)求中的認(rèn)證信息包括
username,password等封裝成UsernamePasswordAuthenticationToken售躁,然后調(diào)用
AuthenticationManager的認(rèn)證方法進(jìn)行認(rèn)證
BasicAuthenticationFilter
基本認(rèn)證坞淮,支持httpBasic認(rèn)證方式的Filter
RememberAuthenticationFilter
記住我功能實(shí)現(xiàn)的Filter
AnonymousAuthenticationFilter
匿名Filter茴晋,用來處理匿名訪問的資源陪捷,如果用戶未登錄,SecurityContext中沒有Authentication诺擅,
就會(huì)創(chuàng)建匿名的Token(AnonymousAuthenticationToken),然后通過
SecurityContextHodler設(shè)置到SecurityContext中市袖。
ExceptionTranslationFilter
用來捕獲FilterChain所有的異常,進(jìn)行處理烁涌,但是只會(huì)處理 AuthenticationException和AccessDeniedException苍碟,異常,其他的異常 會(huì)繼續(xù)拋出撮执。
FilterSecurityInterceptor
用來做授權(quán)的Filter,通過父類(AbstractSecurityInterceptor.beforeInvocation)調(diào)用
AccessDecisionManager.decide方法對(duì)用戶進(jìn)行授權(quán)微峰。
Security相關(guān)概念
Authentication
認(rèn)證對(duì)象,用來封裝用戶的認(rèn)證信息(賬戶狀態(tài)抒钱,用戶名蜓肆,密碼颜凯,權(quán)限等)所有提交給AuthenticationManager的認(rèn)證請(qǐng)求都會(huì)被封裝成一個(gè)Token的實(shí)現(xiàn),比如 最容易理解的UsernamePasswordAuthenticationToken仗扬,其中包含了用戶名和密碼
Authentication常用的實(shí)現(xiàn)類:
- UsernamePasswordAuthenticationToken:用戶名密碼登錄的Token
- AnonymousAuthenticationToken:針對(duì)匿名用戶的Token
- RememberMeAuthenticationToken:記住我功能的的Token
AuthenticationManager
用戶認(rèn)證的管理類症概,所有的認(rèn)證請(qǐng)求(比如login)都會(huì)通過提交一個(gè)封裝了到了登錄信息的Token對(duì)象給 AuthenticationManager的authenticate()方法來實(shí)現(xiàn)認(rèn)證。AuthenticationManager會(huì) 調(diào)用AuthenticationProvider.authenticate進(jìn)行認(rèn)證早芭。認(rèn)證成功后彼城,返回一個(gè)包含了認(rèn) 證信息的Authentication對(duì)象。
AuthenticationProvider
認(rèn)證的具體實(shí)現(xiàn)類退个,一個(gè)provider是一種認(rèn)證方式的實(shí)現(xiàn)募壕,比如提交的用戶名密碼是通過和DB中查出的user記錄做比對(duì)實(shí)現(xiàn)的,那就有一個(gè)DaoProvider帜乞;如果是通過CAS請(qǐng)求單點(diǎn)登錄系統(tǒng)實(shí)現(xiàn)司抱,那就有一個(gè)CASProvider。按照Spring一貫的作風(fēng)黎烈, 主流的認(rèn)證方式它都已經(jīng)提供了默認(rèn)實(shí)現(xiàn)习柠,比如DAO、LDAP照棋、CAS资溃、OAuth2等。 前 面講了AuthenticationManager只是一個(gè)代理接口烈炭,真正的認(rèn)證就是由 AuthenticationProvider來做的溶锭。一個(gè)AuthenticationManager可以包含多個(gè)Provider, 每個(gè)provider通過實(shí)現(xiàn)一個(gè)support方法來表示自己支持那種Token的認(rèn)證符隙。 AuthenticationManager默認(rèn)的實(shí)現(xiàn)類是ProviderManager趴捅。
UserDetailService
用戶的認(rèn)證通過Provider來完成,而Provider會(huì)通過UserDetailService拿到數(shù)據(jù)庫(或 內(nèi)存)中的認(rèn)證信息然后和客戶端提交的認(rèn)證信息做校驗(yàn)霹疫。雖然叫Service,但是我更愿 意把它認(rèn)為是我們系統(tǒng)里經(jīng)常有的UserDao拱绑。
SecurityContext
當(dāng)用戶通過認(rèn)證之后,就會(huì)為這個(gè)用戶生成一個(gè)唯一的SecurityContext丽蝎,里面包含用 戶的認(rèn)證信息Authentication猎拨。通過SecurityContext我們可以獲取到用戶的標(biāo)識(shí) Principle和授權(quán)信息GrantedAuthrity。在系統(tǒng)的任何地方只要通過 SecurityHolder.getSecruityContext()就可以獲取到SecurityContext屠阻。在Shiro中通過 SecurityUtils.getSubject()到達(dá)同樣的目的
SpringSecurity認(rèn)證流程原理
請(qǐng)求過來會(huì)被過濾器鏈中的UsernamePasswordAuthenticationFilter攔截到红省,請(qǐng)求中的用戶名和密碼被封裝成UsernamePasswordAuthenticationToken(Authentication的實(shí)現(xiàn)類)
過濾器將UsernamePasswordAuthenticationToken提交給認(rèn)證管理器(AuthenticationManager)進(jìn)行認(rèn)證.
AuthenticationManager委托AuthenticationProvider(DaoAuthenticationProvider)進(jìn)行認(rèn)證,AuthenticationProvider通過調(diào)用UserDetailsService獲取到數(shù)據(jù)庫中存儲(chǔ)的用戶信息(UserDetails)国觉,然后調(diào)用passwordEncoder密碼編碼器對(duì)UsernamePasswordAuthenticationToken中的密碼和UserDetails中的密碼進(jìn)行比較
AuthenticationProvider認(rèn)證成功后封裝Authentication并設(shè)置好用戶的信息(用戶名吧恃,密碼,權(quán)限等)返回
Authentication被返回到UsernamePasswordAuthenticationFilter,通過調(diào)用SecurityContextHolder工具把Authentication封裝成SecurityContext中存儲(chǔ)起來麻诀。然后UsernamePasswordAuthenticationFilter調(diào)用AuthenticationSuccessHandler.onAuthenticationSuccess做認(rèn)證成功后續(xù)處理操作
最后SecurityContextPersistenceFilter通過SecurityContextHolder.getContext()獲取到SecurityContext對(duì)象然后調(diào)用SecurityContextRepository將SecurityContext存儲(chǔ)起來痕寓,然后調(diào)用SecurityContextHolder.clearContext方法清理SecurityContext缸逃。
- 注意:SecurityContext是一個(gè)和當(dāng)前線程綁定的工具,在代碼的任何地方都可以通過SecurityContextHolder.getContext()獲取到登陸信息厂抽。
認(rèn)證流程源碼跟蹤
SecurityContextPersistenceFilter
這個(gè)filter是整個(gè)filter鏈的入口和出口需频,請(qǐng)求開始會(huì)從SecurityContextRepository中 獲取SecurityContext對(duì)象并設(shè)置給SecurityContextHolder。在請(qǐng)求完成后將SecurityContextHolder持有的SecurityContext再保存到配置好的DecurityContextRepository中筷凤,同時(shí)清除SecurityContextHolder中的SecurityContext
- 總結(jié)一下:
SecurityContextPersistenceFilter
作用就是請(qǐng)求來的時(shí)候?qū)苏J(rèn)證授權(quán)信息的SecurityContext對(duì)象從SecurityContextRepository中取出交給SecurityContextHolder工具類昭殉,方便我們通過SecurityContextHolder獲取SecurityContext從而獲取到認(rèn)證授權(quán)信息,請(qǐng)求走的時(shí)候又把SecurityContextHolder清空藐守,源碼如下:
public class SecurityContextPersistenceFilter extends GenericFilterBean {
...省略...
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
...省略部分代碼...
HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request,
response);
//從SecurityContextRepository獲取到SecurityContext
SecurityContext contextBeforeChainExecution = repo.loadContext(holder);
try {
//把 securityContext設(shè)置到SecurityContextHolder挪丢,如果沒認(rèn)證通過,這個(gè)SecurtyContext就是空的
SecurityContextHolder.setContext(contextBeforeChainExecution);
//調(diào)用后面的filter卢厂,比如掉用usernamepasswordAuthenticationFilter實(shí)現(xiàn)認(rèn)證
chain.doFilter(holder.getRequest(), holder.getResponse());
}
finally {
//如果認(rèn)證通過了乾蓬,這里可以從SecurityContextHolder.getContext();中獲取到SecurityContext
SecurityContext contextAfterChainExecution = SecurityContextHolder
.getContext();
// Crucial removal of SecurityContextHolder contents - do this before anything
// else.
//刪除SecurityContextHolder中的SecurityContext
SecurityContextHolder.clearContext();
//把SecurityContext 存儲(chǔ)到SecurityContextRepository
repo.saveContext(contextAfterChainExecution, holder.getRequest(),
holder.getResponse());
request.removeAttribute(FILTER_APPLIED);
if (debug) {
logger.debug("SecurityContextHolder now cleared, as request processing completed");
}
}
...省略...
UsernamePasswordAuthenticationFilter
它的作用是,攔截“/login”登錄請(qǐng)求慎恒,處理表單提交的登錄認(rèn)證任内,將請(qǐng)求中的認(rèn)證信息包括username,password等封裝成UsernamePasswordAuthenticationToken,然后調(diào)用
AuthenticationManager的認(rèn)證方法進(jìn)行認(rèn)證融柬。
public class UsernamePasswordAuthenticationFilter extends
AbstractAuthenticationProcessingFilter {
// ~ Static fields/initializers
// =====================================================================================
//從登錄請(qǐng)求中獲取參數(shù):username,password的名字
public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";
public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";
private String usernameParameter = SPRING_SECURITY_FORM_USERNAME_KEY;
private String passwordParameter = SPRING_SECURITY_FORM_PASSWORD_KEY;
//默認(rèn)支持POST登錄
private boolean postOnly = true;
//默認(rèn)攔截/login請(qǐng)求死嗦,Post方式
public UsernamePasswordAuthenticationFilter() {
super(new AntPathRequestMatcher("/login", "POST"));
}
// ~ Methods
// ========================================================================================================
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException {
//判斷請(qǐng)求是否是POST
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();
//用戶名和密碼封裝Token
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
username, password);
//設(shè)置details屬性
// Allow subclasses to set the "details" property
setDetails(request, authRequest);
//調(diào)用AuthenticationManager().authenticate進(jìn)行認(rèn)證,參數(shù)就是Token對(duì)象
return this.getAuthenticationManager().authenticate(authRequest);
}
AuthenticationManager
請(qǐng)求通過UsernamePasswordAuthenticationFilter調(diào)用AuthenticationManager粒氧,默認(rèn)走的實(shí)現(xiàn)類是ProviderManager越除,它會(huì)找到能支持當(dāng)前認(rèn)證的AuthenticationProvider實(shí)現(xiàn)類調(diào)用器authenticate方法執(zhí)行認(rèn)證,認(rèn)證成功后會(huì)清除密碼外盯,然后拋出AuthenticationSuccessEvent事件
public class ProviderManager implements AuthenticationManager, MessageSourceAware,
InitializingBean {
...省略...
//這里authentication 是封裝了登錄請(qǐng)求的認(rèn)證參數(shù)摘盆,
//即:UsernamePasswordAuthenticationFilter傳入的Token對(duì)象
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
Class<? extends Authentication> toTest = authentication.getClass();
AuthenticationException lastException = null;
AuthenticationException parentException = null;
Authentication result = null;
Authentication parentResult = null;
boolean debug = logger.isDebugEnabled();
//找到所有的AuthenticationProvider ,選擇合適的進(jìn)行認(rèn)證
for (AuthenticationProvider provider : getProviders()) {
//是否支持當(dāng)前認(rèn)證
if (!provider.supports(toTest)) {
continue;
}
if (debug) {
logger.debug("Authentication attempt using "
+ provider.getClass().getName());
}
try {
//調(diào)用provider執(zhí)行認(rèn)證
result = provider.authenticate(authentication);
if (result != null) {
copyDetails(authentication, result);
break;
}
}
...省略...
}
...省略...
//result就是Authentication 饱苟,使用的實(shí)現(xiàn)類依然是UsernamepasswordAuthenticationToken孩擂,
//封裝了認(rèn)證成功后的用戶的認(rèn)證信息和授權(quán)信息
if (result != null) {
if (eraseCredentialsAfterAuthentication
&& (result instanceof CredentialsContainer)) {
// Authentication is complete. Remove credentials and other secret data
// from authentication
//這里在擦除登錄密碼
((CredentialsContainer) result).eraseCredentials();
}
// If the parent AuthenticationManager was attempted and successful than it will publish an AuthenticationSuccessEvent
// This check prevents a duplicate AuthenticationSuccessEvent if the parent AuthenticationManager already published it
if (parentResult == null) {
//發(fā)布事件
eventPublisher.publishAuthenticationSuccess(result);
}
return result;
}
DaoAuthenticationProvider
請(qǐng)求到達(dá)AuthenticationProvider,默認(rèn)實(shí)現(xiàn)是DaoAuthenticationProvider掷空,它的作用是根據(jù)傳入的Token中的username調(diào)用UserDetailService加載數(shù)據(jù)庫中的認(rèn)證授權(quán)信息(UserDetails)肋殴,然后使用PasswordEncoder對(duì)比用戶登錄密碼是否正確
public class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
//密碼編碼器
private PasswordEncoder passwordEncoder;
//UserDetailsService 囤锉,根據(jù)用戶名加載UserDetails對(duì)象坦弟,從數(shù)據(jù)庫加載的認(rèn)證授權(quán)信息
private UserDetailsService userDetailsService;
//認(rèn)證檢查方法
protected void additionalAuthenticationChecks(UserDetails userDetails,
UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
if (authentication.getCredentials() == null) {
logger.debug("Authentication failed: no credentials provided");
throw new BadCredentialsException(messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.badCredentials",
"Bad credentials"));
}
//獲取密碼
String presentedPassword = authentication.getCredentials().toString();
//通過passwordEncoder比較密碼,presentedPassword是用戶傳入的密碼官地,userDetails.getPassword()是從數(shù)據(jù)庫加載到的密碼
//passwordEncoder編碼器不一樣比較密碼的方式也不一樣
if (!passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
logger.debug("Authentication failed: password does not match stored value");
throw new BadCredentialsException(messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.badCredentials",
"Bad credentials"));
}
}
//檢索用戶酿傍,參數(shù)為用戶名和Token對(duì)象
protected final UserDetails retrieveUser(String username,
UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
prepareTimingAttackProtection();
try {
//調(diào)用UserDetailsService的loadUserByUsername方法,
//根據(jù)用戶名檢索數(shù)據(jù)庫中的用戶驱入,封裝成UserDetails
UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
if (loadedUser == null) {
throw new InternalAuthenticationServiceException(
"UserDetailsService returned null, which is an interface contract violation");
}
return loadedUser;
}
catch (UsernameNotFoundException ex) {
mitigateAgainstTimingAttack(authentication);
throw ex;
}
catch (InternalAuthenticationServiceException ex) {
throw ex;
}
catch (Exception ex) {
throw new InternalAuthenticationServiceException(ex.getMessage(), ex);
}
}
//創(chuàng)建認(rèn)證成功的認(rèn)證對(duì)象Authentication赤炒,使用的實(shí)現(xiàn)是UsernamepasswordAuthenticationToken,
//封裝了認(rèn)證成功后的認(rèn)證信息和授權(quán)信息氯析,以及賬戶的狀態(tài)等
@Override
protected Authentication createSuccessAuthentication(Object principal,
Authentication authentication, UserDetails user) {
boolean upgradeEncoding = this.userDetailsPasswordService != null
&& this.passwordEncoder.upgradeEncoding(user.getPassword());
if (upgradeEncoding) {
String presentedPassword = authentication.getCredentials().toString();
String newPassword = this.passwordEncoder.encode(presentedPassword);
user = this.userDetailsPasswordService.updatePassword(user, newPassword);
}
return super.createSuccessAuthentication(principal, authentication, user);
}
...省略...
這里提供了三個(gè)方法
- additionalAuthenticationChecks:通過passwordEncoder比對(duì)密碼
- retrieveUser:根據(jù)用戶名調(diào)用UserDetailsService加載用戶認(rèn)證授權(quán)信息
- createSuccessAuthentication:登錄成功,創(chuàng)建認(rèn)證對(duì)象Authentication
然而你發(fā)現(xiàn) DaoAuthenticationProvider 中并沒有authenticate認(rèn)證方法莺褒,真正的認(rèn)證邏輯是通過父類AbstractUserDetailsAuthenticationProvider.authenticate方法完成的
AbstractUserDetailsAuthenticationProvider
public abstract class AbstractUserDetailsAuthenticationProvider implements
AuthenticationProvider, InitializingBean, MessageSourceAware {
//認(rèn)證邏輯
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
//得到傳入的用戶名
String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED"
: authentication.getName();
//從緩存中得到UserDetails
boolean cacheWasUsed = true;
UserDetails user = this.userCache.getUserFromCache(username);
if (user == null) {
cacheWasUsed = false;
try {
//檢索用戶掩缓,底層會(huì)調(diào)用UserDetailsService加載數(shù)據(jù)庫中的UserDetails對(duì)象,保護(hù)認(rèn)證信息和授權(quán)信息
user = retrieveUser(username,
(UsernamePasswordAuthenticationToken) authentication);
}
catch (UsernameNotFoundException notFound) {
...省略...
}
try {
//前置檢查遵岩,主要檢查賬戶是否鎖定你辣,賬戶是否過期等
preAuthenticationChecks.check(user);
//比對(duì)密碼在這個(gè)方法里面比對(duì)的
additionalAuthenticationChecks(user,
(UsernamePasswordAuthenticationToken) authentication);
}
catch (AuthenticationException exception) {
...省略...
}
//后置檢查
postAuthenticationChecks.check(user);
if (!cacheWasUsed) {
//設(shè)置UserDetails緩存
this.userCache.putUserInCache(user);
}
Object principalToReturn = user;
if (forcePrincipalAsString) {
principalToReturn = user.getUsername();
}
//認(rèn)證成功,創(chuàng)建Auhentication認(rèn)證對(duì)象
return createSuccessAuthentication(principalToReturn, authentication, user);
}
UsernamePasswordAuthenticationFilter
認(rèn)證成功尘执,請(qǐng)求會(huì)重新回到UsernamePasswordAuthenticationFilter舍哄,然后會(huì)通過其父類AbstractAuthenticationProcessingFilter.successfulAuthentication方法將認(rèn)證對(duì)象封裝成SecurityContext設(shè)置到SecurityContextHolder中
protected void successfulAuthentication(HttpServletRequest request,
HttpServletResponse response, FilterChain chain, Authentication authResult)
throws IOException, ServletException {
if (logger.isDebugEnabled()) {
logger.debug("Authentication success. Updating SecurityContextHolder to contain: "
+ authResult);
}
//認(rèn)證成功,吧Authentication 設(shè)置到SecurityContextHolder
SecurityContextHolder.getContext().setAuthentication(authResult);
//處理記住我業(yè)務(wù)邏輯
rememberMeServices.loginSuccess(request, response, authResult);
// Fire event
if (this.eventPublisher != null) {
eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(
authResult, this.getClass()));
}
//重定向登錄成功地址
successHandler.onAuthenticationSuccess(request, response, authResult);
}
然后后續(xù)請(qǐng)求又會(huì)回到SecurityContextPersistenceFilter誊锭,它就可以從SecurityContextHolder獲取到SecurityContext持久到SecurityContextRepository(默認(rèn)實(shí)現(xiàn)是HttpSessionSecurityContextRepository基于Session存儲(chǔ))
參考:https://blog.csdn.net/u014494148/article/details/108261616###