欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Spring?Security認(rèn)證器實(shí)現(xiàn)過(guò)程詳解

 更新時(shí)間:2022年06月24日 08:44:55   作者:阿弱  
一些權(quán)限框架一般都包含認(rèn)證器和決策器,前者處理登陸驗(yàn)證,后者處理訪問(wèn)資源的控制,這篇文章主要介紹了Spring?Security認(rèn)證器實(shí)現(xiàn)過(guò)程,需要的朋友可以參考下

一些權(quán)限框架一般都包含認(rèn)證器和決策器,前者處理登陸驗(yàn)證,后者處理訪問(wèn)資源的控制

Spring Security的登陸請(qǐng)求處理如圖

下面來(lái)分析一下是怎么實(shí)現(xiàn)認(rèn)證器的

攔截請(qǐng)求

首先登陸請(qǐng)求會(huì)被UsernamePasswordAuthenticationFilter攔截,這個(gè)過(guò)濾器看名字就知道是一個(gè)攔截用戶名密碼的攔截器

主要的驗(yàn)證是在attemptAuthentication()方法里,他會(huì)去獲取在請(qǐng)求中的用戶名密碼,并且創(chuàng)建一個(gè)該用戶的上下文,然后在去執(zhí)行一個(gè)驗(yàn)證過(guò)程

String username = this.obtainUsername(request);
String password = this.obtainPassword(request);
//創(chuàng)建上下文
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
this.setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);

可以看看UsernamePasswordAuthenticationToken這個(gè)類,他是繼承了AbstractAuthenticationToken,然后這個(gè)父類實(shí)現(xiàn)了Authentication

由這個(gè)類的方法和屬性可得知他就是存儲(chǔ)用戶驗(yàn)證信息的,認(rèn)證器的主要功能應(yīng)該就是驗(yàn)證完成后填充這個(gè)類

回到UsernamePasswordAuthenticationToken中,在上面創(chuàng)建的過(guò)程了可以發(fā)現(xiàn)

public UsernamePasswordAuthenticationToken(Object principal,Object credentials){
    super(null);
    this.principal=principal;
    this.credentials=credentials;
    //還沒(méi)認(rèn)證
    setAuthenticated(false);
}

還有一個(gè)super(null)的處理,因?yàn)閯傔M(jìn)來(lái)是還不知道有什么權(quán)限的,設(shè)置null是初始化一個(gè)空的權(quán)限

//權(quán)限利集合
private final Collection<GrantedAuthority> authorities;
//空的集合
public static final List<GrantedAuthority> NO_AUTHORITIES = Collections.emptyList();
//初始化
if (authorities == null) {
    this.authorities = AuthorityUtils.NO_AUTHORITIES;
    return;
}

那么后續(xù)認(rèn)證完還會(huì)把權(quán)限設(shè)置盡量,此時(shí)可以看UsernamePasswordAuthenticationToken的另一個(gè)重載構(gòu)造器

//認(rèn)證完成
public UsernamePasswordAuthenticationToken(Object principal, Object credentials,
    Collection<? extends GrantedAuthority> authorities) {
    super(authorities);
    this.principal = principal;
    this.credentials = credentials;
    super.setAuthenticated(true); // must use super, as we override
}

在看源碼的過(guò)程中,注釋一直在強(qiáng)調(diào)這些上下文的填充和設(shè)置都應(yīng)該是由AuthenticationManager或者AuthenticationProvider的實(shí)現(xiàn)類去操作

驗(yàn)證過(guò)程

接下來(lái)會(huì)把球踢給AuthenticationManager,但他只是個(gè)接口

/**
 * Attempts to authenticate the passed {@link Authentication} object, returning a
 * fully populated <code>Authentication</code> object (including granted authorities)
 * if successful.
 **/
public interface AuthenticationManager {
    Authentication authenticate(Authentication authentication)
            throws AuthenticationException;
}

注釋也寫(xiě)的很清楚了,認(rèn)證完成后會(huì)填充Authentication

接下來(lái)會(huì)委托給ProviderManager,因?yàn)樗麑?shí)現(xiàn)了AuthenticationManager

剛進(jìn)來(lái)看authenticate()方法會(huì)發(fā)現(xiàn)他先遍歷了一個(gè)List<AuthenticationProvider>集合

/**
 * Indicates a class can process a specific Authentication 
 **/
public interface AuthenticationProvider {
    Authentication authenticate(Authentication authentication)
            throws AuthenticationException;
    //支不支持特定類型的authentication
    boolean supports(Class<?> authentication);
}

實(shí)現(xiàn)這個(gè)類就可以處理不同類型的Authentication,比如上邊的UsernamePasswordAuthenticationToken,對(duì)應(yīng)的處理類是AbstractUserDetailsAuthenticationProvider,為啥知道呢,因?yàn)樵谶@個(gè)supports()

public boolean supports(Class<?> authentication) {
		return (UsernamePasswordAuthenticationToken.class
				.isAssignableFrom(authentication));
}

注意到這個(gè)是抽象類,實(shí)際的處理方法是在他的子類DaoAuthenticationProvider里,但是最重要的authenticate()方法子類好像沒(méi)有繼承,看看父類是怎么實(shí)現(xiàn)這個(gè)方法的

首先是繼續(xù)判斷Authentication是不是特定的類

 Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
     () -> messages.getMessage(
     "AbstractUserDetailsAuthenticationProvider.onlySupports",
     "Only UsernamePasswordAuthenticationToken is supported"));

查詢根據(jù)用戶名用戶,這次就是到了子類的方法了,因?yàn)檫@個(gè)方法是抽象的

 user=retrieveUser(username,
     (UsernamePasswordAuthenticationToken)authentication);

接著DaoAuthenticationProvider會(huì)調(diào)用真正實(shí)現(xiàn)查詢用戶的類UserDetailsService

UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);

UserDetailsService這個(gè)類信息就不陌生了,我們一般都會(huì)去實(shí)現(xiàn)這個(gè)類來(lái)自定義查詢用戶的方式,查詢完后會(huì)返回一個(gè)UserDetails,當(dāng)然也可以繼承這個(gè)類來(lái)擴(kuò)展想要的字段,主要填充的是權(quán)限信息和密碼

檢驗(yàn)用戶,如果獲取到的UserDetails是null,則拋異常,不為空則繼續(xù)校驗(yàn)

//檢驗(yàn)用戶合法性
preAuthenticationChecks.check(user);
//校驗(yàn)密碼
additionalAuthenticationChecks(user,
(UsernamePasswordAuthenticationToken) authentication);

第一個(gè)教育是判斷用戶的合法性,就是判斷UserDetails里的幾個(gè)字段

//賬號(hào)是否過(guò)期
boolean isAccountNonExpired();
//賬號(hào)被鎖定或解鎖狀態(tài)。
boolean isAccountNonLocked();
//密碼是否過(guò)期
boolean isCredentialsNonExpired();
//是否啟用
boolean isEnabled();

第二個(gè)則是由子類實(shí)現(xiàn)的,判斷從數(shù)據(jù)庫(kù)獲取的密碼和請(qǐng)求中的密碼是否一致,因?yàn)橛玫牡顷懛绞绞歉鶕?jù)用戶名稱登陸,所以有檢驗(yàn)密碼的步驟

 String presentedPassword = authentication.getCredentials().toString();
 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"));
 }

需要主要的是請(qǐng)求中的密碼是被加密過(guò)的,所以從數(shù)據(jù)庫(kù)獲取到的密碼也應(yīng)該是被加密的

注意到當(dāng)完成校驗(yàn)的時(shí)候會(huì)把信息放入緩存

//當(dāng)沒(méi)有從緩存中獲取到值時(shí),這個(gè)字段會(huì)被設(shè)置成false
if (!cacheWasUsed) {
			this.userCache.putUserInCache(user);
 }
 //下次進(jìn)來(lái)的時(shí)候回去獲取
 UserDetails user = this.userCache.getUserFromCache(username);

如果是從緩存中獲取,也是會(huì)走檢驗(yàn)邏輯的

最后完成檢驗(yàn),并填充一個(gè)完整的Authentication

return createSuccessAuthentication(principalToReturn, authentication, user);

由上述流程來(lái)看,Security的檢驗(yàn)過(guò)程還是比較清晰的,通過(guò)AuthenticationManager來(lái)委托給ProviderManager,在通過(guò)具體的實(shí)現(xiàn)類來(lái)處理請(qǐng)求,在這個(gè)過(guò)程中,將查詢用戶的實(shí)現(xiàn)和驗(yàn)證代碼分離開(kāi)來(lái)

整個(gè)過(guò)程看著像是策略模式,后邊將變化的部分抽離出來(lái),實(shí)現(xiàn)解耦

返回完整的Authentication

前邊提到的認(rèn)證成功會(huì)調(diào)用createSuccessAuthentication()方法,里邊的內(nèi)容很簡(jiǎn)單

UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(
     principal, authentication.getCredentials(),
     authoritiesMapper.mapAuthorities(user.getAuthorities()));
     result.setDetails(authentication.getDetails());
public UsernamePasswordAuthenticationToken(Object principal, Object credentials,
        Collection<? extends GrantedAuthority> authorities) {
        super(authorities);
        this.principal = principal;
        this.credentials = credentials;
        super.setAuthenticated(true); // must use super, as we override
        }

這次往supe里放了權(quán)限集合,父類的處理是判斷里邊的權(quán)限有沒(méi)有空的,沒(méi)有則轉(zhuǎn)換為只讀集合

for (GrantedAuthority a : authorities) {
    if (a == null) {
        throw new IllegalArgumentException(
        "Authorities collection cannot contain any null elements");
    }
}
ArrayList<GrantedAuthority> temp = new ArrayList<>(
authorities.size());
temp.addAll(authorities);
this.authorities = Collections.unmodifiableList(temp);

收尾工作

回到ProviderManager里的authenticate方法,當(dāng)我們終于從

result = provider.authenticate(authentication);

走出來(lái)時(shí),后邊還有什么操作

1.將返回的用戶信息負(fù)責(zé)給當(dāng)前的上下文

  if (result != null) {
   	copyDetails(authentication, result);
   	break;
   }

2.刪除敏感信息

((CredentialsContainer) result).eraseCredentials();

這個(gè)過(guò)程會(huì)將一些字段設(shè)置為null,可以實(shí)現(xiàn)eraseCredentials()方法來(lái)自定義需要?jiǎng)h除的信息

最后返回到UsernamePasswordAuthenticationFilter中通過(guò)過(guò)濾

結(jié)論

這就是Spring Security實(shí)現(xiàn)認(rèn)證的過(guò)程了

通過(guò)實(shí)現(xiàn)自己的上下文Authentication和處理類AuthenticationProvider以及具體的查詢用戶的方法就可以自定義自己的登陸實(shí)現(xiàn)
具體可以看Spring Security自定義認(rèn)證器

    到此這篇關(guān)于Spring Security認(rèn)證器實(shí)現(xiàn)過(guò)程詳解的文章就介紹到這了,更多相關(guān)Spring Security認(rèn)證器內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

    相關(guān)文章

    最新評(píng)論