App下載

穿過(guò)Spring Security魔幻山谷了解獲取認(rèn)證機(jī)制的核心原理

潮起潮落 2021-08-16 12:09:00 瀏覽數(shù) (1860)
反饋

本文基于Springboot+Vue+Spring Security框架而寫的原創(chuàng)學(xué)習(xí)筆記,demo代碼參考《Spring Boot+Spring Cloud+Vue+Element項(xiàng)目實(shí)戰(zhàn):手把手教你開發(fā)權(quán)限管理系統(tǒng)》一書。

這是一個(gè)古老的傳說(shuō)。

在神秘的Web系統(tǒng)世界里,有一座名為SpringSecurity的山谷,它高聳入云,蔓延千里,鳥飛不過(guò),獸攀不了。這座山谷只有一條逼仄的道路可通。然而,若要通過(guò)這條道路前往另一頭的世界,就必須先拿到一塊名為token的令牌,只有這樣,道路上戍守關(guān)口的士兵才會(huì)放行。

想要獲得這塊token令牌,必須帶著一把有用的userName鑰匙和password密碼,進(jìn)入到山谷深處,找到藏匿寶箱的山洞(數(shù)據(jù)庫(kù)),若能用鑰匙打開其中一個(gè)寶箱,就證明這把userName鑰匙是有用的。正常情況下,寶箱里會(huì)有一塊記錄各種信息的木牌,包含著鑰匙名和密碼,其密碼只有與你所攜帶的密碼檢驗(yàn)一致時(shí),才能繼續(xù)往前走,得到的通行信息將會(huì)在下一個(gè)關(guān)口處做認(rèn)證,進(jìn)而在道路盡頭處的JWT魔法屋里獲得加密的token令牌。

慢著,既然山谷關(guān)口處有士兵戍守,令牌又在山谷當(dāng)中,在還沒(méi)有獲得令牌的情況下,又怎么能進(jìn)入呢?

設(shè)置關(guān)口的軍官早已想到這種情況,因此,他特意設(shè)置了一條自行命名為“l(fā)ogin”的道路,沒(méi)有令牌的外來(lái)人員可從這條道路進(jìn)入山谷,去尋找傳說(shuō)中的token令牌。這條道路僅僅只能進(jìn)入到山谷,卻無(wú)法通過(guò)山谷到達(dá)另一頭的世界,因此,它更像是一條專門為了給外來(lái)人員獲取token令牌而開辟出來(lái)的道路。

這一路上會(huì)有各種關(guān)口被士兵把守檢查,只有都一一通過(guò)了,才能繼續(xù)往前走,路上會(huì)遇到一位名為ProviderManager的管理員,他管理著所有信息提供者Provider......需找到一位可正確帶路的信息提供者Provider,在他的引導(dǎo)下,前往山洞(數(shù)據(jù)庫(kù)),成功獲取到寶箱,拿到里面記錄信息的木牌,這樣方能驗(yàn)證所攜帶的username和password是否正確。若都正確,那么接下來(lái)就可將信息進(jìn)行認(rèn)證,并前往JWT魔法屋獲取token令牌。最后攜帶著token返回到家鄉(xiāng),讓族人都可穿過(guò)山谷而進(jìn)入到web系統(tǒng),去獲取更多珍貴的資源。

這就是整個(gè)security的游戲規(guī)則原理。

那么,在游戲開始之前,我們先了解下當(dāng)年戍守山谷的軍官是如何設(shè)置這道權(quán)限關(guān)口的......

關(guān)口的自定義設(shè)置主要有三部分:通過(guò)鑰匙u(yù)sername獲取到寶箱;寶箱里的UserDetails通行信息設(shè)置;關(guān)口通行過(guò)往檢查SecurityConfig設(shè)置。

一.寶箱里的通行信息:

/**
* 安全用戶模型
*
* @author zhujiqian
* @date 2020/7/ 15:
*/
public class JwtUserDetails implements UserDetails {
private static final long serialVersionUID = 1L;
private String username;
private String password;
private String salt;
private Collection<? extends GrantedAuthority> authorities;
JwtUserDetails(String username, String password, String salt, Collection<? extends GrantedAuthority> authorities) {
    this.username = username;
  this.password = password;
   this.salt = salt;
   this.authorities = authorities;
}
@Override
public String getUsername() {
   return username;
}
@JsonIgnore
@Override
public String getPassword() {
    return password;
}
public String getSalt() {
    return salt;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
   return authorities;
}
@JsonIgnore
@Override
public boolean isAccountNonExpired() {
    return true;
}
@JsonIgnore
@Override
public boolean isAccountNonLocked() {
   return true;
}
@JsonIgnore
@Override
public boolean isCredentialsNonExpired() {
    return true;
}
@JsonIgnore
@Override
public boolean isEnabled() {
    return true;
}
  }

這里JwtUserDetails實(shí)現(xiàn)Spring Security 里的UserDetails類,這個(gè)類是長(zhǎng)這樣的,下面對(duì)各個(gè)字段做了注釋:

 public interface UserDetails extends Serializable {
/**
*用戶權(quán)限集,默認(rèn)需要添加ROLE_前綴
*/
Collection<? extends GrantedAuthority> getAuthorities();
/**
*用戶的加密密碼,不加密會(huì)使用{noop}前綴
*/
String getPassword();
/**
*獲取應(yīng)用里唯一用戶名
*/
String getUsername();
/**
*檢查賬戶是否過(guò)期
*/
boolean isAccountNonExpired();
/**
*檢查賬戶是否鎖定
*/
boolean isAccountNonLocked();
/**
*檢查憑證是否過(guò)期
*/
boolean isCredentialsNonExpired();
/**
*檢查賬戶是否可用
*/
boolean isEnabled();
  }

說(shuō)明:JwtUserDetails自定義實(shí)現(xiàn)了UserDetails類,增加username和password字段,除此之外,還可以擴(kuò)展存儲(chǔ)更多用戶信息,例如,身份證,手機(jī)號(hào),郵箱等等。其作用在于可構(gòu)建成一個(gè)用戶安全模型,用于裝載從數(shù)據(jù)庫(kù)查詢出來(lái)的用戶及權(quán)限信息。

二.通過(guò)鑰匙u(yù)sername獲取到寶箱方法:

/**
 * 用戶登錄認(rèn)證信息查詢
 *
 * @author zhujiqian
 * @date 2020/7/30 15:30
 */
 @Service
 public class UserDetailsServiceImpl implements UserDetailsService {
 @Resource
 private SysUserService sysUserService;
 @Override
 public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
 SysUser user = sysUserService.findByName(username);
 if (user == null) {
  throw new UsernameNotFoundException("該用戶不存在");
 }
 Set<String> permissions = sysUserService.findPermissions(user.getName());
 List<GrantedAuthority> grantedAuthorities = permissions.stream().map(AuthorityImpl::new).collect(Collectors.toList());
 return new JwtUserDetails(user.getName(), user.getPassword(), user.getSalt(), grantedAuthorities); 
	  }
  }

這個(gè)自定義的UserDetailsServiceImpl類實(shí)現(xiàn)了Spring Security框架自帶的UserDetailsService接口,這個(gè)接口只定義一個(gè)簡(jiǎn)單的loadUserByUsername方法:

public interface UserDetailsService {
   UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
 }

根據(jù)loadUserByUsername方法名便能看出,這是一個(gè)可根據(jù)username用戶名獲取到User對(duì)象信息的方法,并返回一個(gè)UserDetails對(duì)象,即前頭的“寶箱里的通行信息”,換言之,通過(guò)重寫這個(gè)方法,我們能在該方法里實(shí)現(xiàn)用戶登錄認(rèn)證信息的查詢,并返回對(duì)應(yīng)查詢信息。

綜合以上代碼,先用開頭提到的寶箱意象做一個(gè)總結(jié),即拿著userName這把鑰匙,通過(guò)loadUserByUsername這個(gè)方法指引,可進(jìn)入到山洞(數(shù)據(jù)庫(kù)),去尋找能打開的寶箱(在數(shù)據(jù)庫(kù)里select查詢userName對(duì)應(yīng)數(shù)據(jù)),若能打開其中一個(gè)寶箱(即數(shù)據(jù)庫(kù)里存在userName對(duì)應(yīng)的數(shù)據(jù)),則獲取寶箱里的通行信息(實(shí)現(xiàn)UserDetails的JwtUserDetails對(duì)象信息)。

三.關(guān)口通行過(guò)往檢查設(shè)置

自定義的SecurityConfig配置類是SpringBoot整合Spring Security的關(guān)鍵靈魂所在。該配置信息會(huì)在springboot啟動(dòng)時(shí)進(jìn)行加載。其中,authenticationManager()會(huì)創(chuàng)建一個(gè)可用于傳token做認(rèn)證的AuthenticationManager對(duì)象,而AuthenticationManagerBuilder中的auth.authenticationProvider()則會(huì)創(chuàng)建一個(gè)provider提供者,并將userDetailsService注入進(jìn)去,該userDetailsService的子類被自定義的UserDetailsServiceImpl類繼承,并重寫loadUserByUsername()方法,因此,當(dāng)源碼里執(zhí)行userDetailsService的loadUserByUsername()方法時(shí),即會(huì)執(zhí)行被重寫的子類loadUserByUsername()方法。

由此可見,在做認(rèn)證的過(guò)程中,只需找到注入userDetailsService的provider對(duì)象,即可執(zhí)行l(wèi)oadUserByUsername去根據(jù)username獲取數(shù)據(jù)庫(kù)里信息。

那具體是在哪個(gè)provider對(duì)象?請(qǐng)看下面詳細(xì)解析。

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
 @Resource
 private UserDetailsService userDetailsService;
 @Override
public void configure(AuthenticationManagerBuilder auth) {
 auth.authenticationProvider(new JwtAuthenticationProvider(userDetailsService));
}
@Bean
@Override
public AuthenticationManager authenticationManager() throws Exception {
 return super.authenticationManager();
}
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
 //使用的是JWT,禁用csrf
 httpSecurity.cors().and().csrf().disable()
//設(shè)置請(qǐng)求必須進(jìn)行權(quán)限認(rèn)證
.authorizeRequests()
//跨域預(yù)檢請(qǐng)求
.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
//permitAll()表示所有用戶可認(rèn)證
.antMatchers( "/webjars/**").permitAll()
//首頁(yè)和登錄頁(yè)面
.antMatchers("/").permitAll()
.antMatchers("/login").permitAll()
// 驗(yàn)證碼
.antMatchers("/captcha.jpg**").permitAll()
// 其他所有請(qǐng)求需要身份認(rèn)證
.anyRequest().authenticated();
 //退出登錄處理
 httpSecurity.logout().logoutSuccessHandler(new HttpStatusReturningLogoutSuccessHandler());
 //token驗(yàn)證過(guò)濾器
 httpSecurity.addFilterBefore(new JwtAuthenticationFilter(authenticationManager()), UsernamePasswordAuthenticationFilter.class);
}
  }

首先,雙擊SecurityConfig 類里的JwtAuthenticationProvider——

進(jìn)入到JWTAuthenticationProvider類內(nèi)部,發(fā)現(xiàn)原來(lái)該類是繼承了DaoAuthenticationProvider。

請(qǐng)注意這段話,很關(guān)鍵:

點(diǎn)擊setUserDetailsService(userDetailsService)。進(jìn)入到方法里面后,發(fā)現(xiàn)這里其實(shí)是把UserDetailsService通過(guò)set方式依賴注入到DaoAuthenticationProvider類中,換言之,我們接下來(lái)在加載完成的框架里只需通過(guò)DaoAuthenticationProvider的getUserDetailsService()方法,便可獲取前面注入的userDetailsService,進(jìn)而調(diào)用其子類實(shí)現(xiàn)的loadUserByUsername()方法。

看到這里,您須重點(diǎn)關(guān)注一下DaoAuthenticationProvider這個(gè)類,它將會(huì)在后面再次與我們碰面,而它是一個(gè)AuthenticationProvider。

若您還不是很明白AuthenticationProvider究竟是什么,那就暫且統(tǒng)一把它當(dāng)做信息提供者吧,而它是ProviderManager管理員底下其中一個(gè)信息提供者Provider。

寫到這里,還有一個(gè)疑問(wèn),即security框架是如何將信息提供者Provider歸納到ProviderManager管理員手下的呢?

解答這個(gè)問(wèn)題,需回到SecurityConfig配置文件里,點(diǎn)擊authenticationProvider進(jìn)入到底層方法當(dāng)中。

進(jìn)入后,里面是具體的方法實(shí)現(xiàn),大概功能就是把注入了userDetailsService的信息提供者DaoAuthenticationProvider添加到一個(gè)List集合里,然后再將集合里的所有提供者,通過(guò)構(gòu)造器傳入ProviderManager,命名生成一個(gè)新的提供者管理員providerManager。這里面還涵蓋不少細(xì)節(jié),感興趣的讀者可自行再擴(kuò)展深入研究。

以上,就初步設(shè)置好了游戲規(guī)則。

接下來(lái),就是主角上場(chǎng)了。

在所有的游戲里,都會(huì)有一個(gè)主角,而我們這個(gè)故事,自然也不例外。

此時(shí),在一扇刻著“登錄”二字的大門前,有一個(gè)小兵正在收拾他的包袱,準(zhǔn)備跨過(guò)大門,踏上通往SpringSecurity山谷的道路。他背負(fù)著整個(gè)家族賦予的任務(wù),需前往Security山谷,拿到token令牌,只有把它成功帶回來(lái),家族里的其他成員,才能有機(jī)會(huì)穿過(guò)這座山谷,前往另一頭的神秘世界,獲取到珍貴的資源。

這個(gè)小兵,便是我們這故事里的主角,我把他叫做線程,他將帶著整個(gè)線程家族的希望,尋找可通往神秘系統(tǒng)世界的令牌。

線程把族長(zhǎng)給予的鑰匙和密碼放進(jìn)包袱,他回頭看了一眼自己的家鄉(xiāng),然后揮了揮手,跨過(guò)“登錄”這扇大門,勇敢地上路了。

線程來(lái)到戒備森嚴(yán)的security關(guān)口前,四周望了一眼,忽然發(fā)現(xiàn)關(guān)口旁立著一塊顯眼的石碑,上面刻著一些符號(hào)。他走上前一看,發(fā)現(xiàn)原來(lái)是當(dāng)年軍官設(shè)置的指令與對(duì)應(yīng)的說(shuō)明:

  @Override
 protected void configure(HttpSecurity httpSecurity) throws Exception {
  //使用的是JWT,禁用csrf
  httpSecurity.cors().and().csrf().disable()
 //設(shè)置請(qǐng)求必須進(jìn)行權(quán)限認(rèn)證
 .authorizeRequests()
 //跨域預(yù)檢請(qǐng)求
 .antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
 //首頁(yè)和登錄頁(yè)面
.antMatchers("/login").permitAll()
// 其他所有請(qǐng)求需要身份認(rèn)證
.anyRequest().authenticated();
 //退出登錄處理
 httpSecurity.logout().logoutSuccessHandler(new HttpStatusReturningLogoutSuccessHandler());
 //token驗(yàn)證過(guò)濾器
 httpSecurity.addFilterBefore(new JwtAuthenticationFilter(authenticationManager()), UsernamePasswordAuthenticationFilter.class);
}

其中,permitAll()代表所有請(qǐng)求都可訪問(wèn),當(dāng)它設(shè)置成類似“.antMatchers("/login").permitAll()”的形式時(shí),則代表該/login路徑請(qǐng)求無(wú)需認(rèn)證便可通過(guò),相反,代碼anyRequest().authenticated()則意味著其他的所有請(qǐng)求都必須進(jìn)行身份驗(yàn)證方能通過(guò),否則,會(huì)被拒絕訪問(wèn)。

下面,將通過(guò)debug一步一步揭示,線程是如何闖關(guān)升級(jí)的,最后成功獲取到傳說(shuō)中的token令牌。

線程來(lái)到關(guān)口處,不久,在戍守士兵的指引下,開始往login道路走去,前面迎接他,將是一系列的關(guān)口檢查。

1.傳入userName,password屬性,封裝成一個(gè)token對(duì)象。

進(jìn)入到該對(duì)象里,可看到用戶名賦值給this.principal,密碼賦值給this.credentials,其中setAuthenticated(false)意味著尚未進(jìn)行認(rèn)證。

注意一點(diǎn)是,UsernamePasswordAuthenticationToken繼承了AbstractAuthenticationToken,而AbstractAuthenticationToken實(shí)現(xiàn)Authentication,由傳遞關(guān)系可知,Authentication是UsernamePasswordAuthenticationToken的基類,故而UsernamePasswordAuthenticationToken是可以向上轉(zhuǎn)換為Authentication,理解這一點(diǎn),就能明白,為何接下來(lái)authenticationManager.authenticate(token)方法傳進(jìn)去的是UsernamePasswordAuthenticationToken,但在源碼里,方法參數(shù)則為Authentication。

2.將username,password封裝成token對(duì)象。

通過(guò)Authenticationauthentication=authenticationManager.authenticate(token)方法進(jìn)行認(rèn)證,里面會(huì)執(zhí)行一系列認(rèn)證操作,需要看懂源碼,才能知道這行代碼背后藏著的水月洞天,然,有一點(diǎn)是可以從表面上看懂的,即若成功認(rèn)證通過(guò),將會(huì)返回一個(gè)認(rèn)證成功的Authentication對(duì)象,至于對(duì)象里是什么信息,請(qǐng)繼續(xù) 往下看。

3.點(diǎn)擊進(jìn)入到AuthenticationManager里,發(fā)現(xiàn)該接口里只有一個(gè)方法:

Authentication authenticate(Authentication authentication)
     throws AuthenticationException;

由此可知,它的具體實(shí)現(xiàn),是通過(guò)實(shí)現(xiàn)類來(lái)操作的,它的主要實(shí)現(xiàn)類有N多個(gè),其中,在認(rèn)證過(guò)程中,我們需關(guān)注的是ProviderManager這個(gè)類。

這個(gè)ProviderManager,即前面提到的Provider管理員,他管理著一堆信息提供者provider。線程此行的目的,就是先找到這個(gè)Provider管理員,再去管理員手中尋找能夠匹配到的提供者provider,只有通過(guò)匹配到的提供者,才能找到獲取數(shù)據(jù)庫(kù)的方法loadUserByUsername。

4.ProviderManager類實(shí)際上是實(shí)現(xiàn)AuthenticationManager接口。

重寫authenticate方法。因此,當(dāng)前面代碼執(zhí)行authenticationManager.authenticate(token)方法時(shí),具體實(shí)現(xiàn)將由其子類重寫的方法操作,子類即ProviderManager。

debug進(jìn)去后——

繼續(xù)往下執(zhí)行,通過(guò)getProviders() 可獲取到內(nèi)部維護(hù)在List中的AuthenticationProvider遍歷進(jìn)行驗(yàn)證,若該提供者能支持傳入的token進(jìn)行驗(yàn)證,則繼續(xù)往下執(zhí)行。

其中,JwtAuthAuthenticationProvider可執(zhí)行本次驗(yàn)證,而JwtAuthAuthenticationProvider是繼承DaoAuthenticationProvider后自定義的類,可以理解成,進(jìn)行認(rèn)證驗(yàn)證的Provider是前面重點(diǎn)提到的DaoAuthenticationProvider。

DaoAuthenticationProvider是一個(gè)具體實(shí)現(xiàn)類,它繼承AbstractUserDetailsAuthenticationProvider抽象類。

而AbstractUserDetailsAuthenticationProvider實(shí)現(xiàn)了AuthenticationProvider接口。

5.在ProviderManager中,執(zhí)行到result =provider.authenticate(authentication)。

其中provider是由AuthenticationProvider定義的,但AuthenticationProvider是一個(gè)接口,需由其子類具體實(shí)現(xiàn)。根據(jù)上面分析,可知,AbstractUserDetailsAuthenticationProvider會(huì)具體實(shí)現(xiàn)provider.authenticate(authentication)方法。debug進(jìn)入到其authenticate方法當(dāng)中,會(huì)跳轉(zhuǎn)到AbstractUserDetailsAuthenticationProvider重寫的authenticate()方法當(dāng)中,接下來(lái)會(huì)詳細(xì)介紹該authenticate()執(zhí)行的代碼模塊:

5.1.首先,第一步,會(huì)執(zhí)行this.userCache.getUserFromCache(username)獲取緩存里的信息。

5.2若緩存里沒(méi)有UserDetails信息,將會(huì)繼續(xù)往下執(zhí)行。

執(zhí)行到retrieveUser方法,該方法的總體作用是:通過(guò)登錄時(shí)傳入的userName去數(shù)據(jù)庫(kù)里做查詢,若查詢成功,便將數(shù)據(jù)庫(kù)的User信息包裝成UserDetails對(duì)象返回,當(dāng)然,具體如何從數(shù)據(jù)庫(kù)里獲取到信息,則需要重寫一個(gè)方法,即前面提到的loadUserByUsername()方法。

值得注意一點(diǎn)是,一般新手接觸到security框架,都會(huì)有一個(gè)疑問(wèn),即我登錄時(shí)傳入了username,是如何獲取到數(shù)據(jù)庫(kù)里的用戶信息?

其實(shí),這個(gè)疑問(wèn)的關(guān)鍵答案,就藏在這個(gè)retrieveUser()方法里。該方法名的英文解析是:“(訓(xùn)練成能尋回獵物的)獵犬”。我覺(jué)得這個(gè)翻譯在這里很有意思,暫且可以把它當(dāng)成信息提供者Provider馴養(yǎng)的一頭獵犬,它可以幫我們的游戲主角線程在茫茫的森林里,尋找到藏匿寶箱的山洞(數(shù)據(jù)庫(kù))。

5.3 ,接下來(lái),就讓這頭獵犬給我們帶路吧——點(diǎn)擊retrieveUser()。

進(jìn)入到方法當(dāng)中,發(fā)現(xiàn),這其實(shí)是一個(gè)抽象方法,故而其具體實(shí)現(xiàn)將在子類中進(jìn)行。

5.4進(jìn)入到其子類實(shí)現(xiàn)的方法當(dāng)中。

發(fā)現(xiàn)會(huì)進(jìn)入前面提到AbstractUserDetailsAuthenticationProvider的子類DaoAuthenticationProvider它也是一個(gè)AuthenticationProvider,即所謂的信息提供者之一。在DaoAuthenticationProvider類里,實(shí)現(xiàn)了父類的retrieveUser方法。

在獵犬的(retrieveUser)的帶路下,我們最后看到 了熟悉的老朋友,關(guān)鍵方法loadUserByUserName()。

點(diǎn)進(jìn)loadUserByUsername()方法里,會(huì)進(jìn)入到UserDetailsService接口里,該接口只有l(wèi)oadUserByUsername一個(gè)方法,該方法具體在子類里實(shí)現(xiàn)。

這個(gè)接口被我們自定義重寫了,即前面露過(guò)面的:

在DaoAuthenticationProvider類中,調(diào)用loadUserByUserName()方法時(shí),最終會(huì)執(zhí)行我們重寫的loadUserByUsername()方法,該方法將會(huì)去數(shù)據(jù)庫(kù)里查詢username的信息,并返回一個(gè)User對(duì)象,最后SysUser對(duì)象轉(zhuǎn)換成UserDetails,返回給DaoAuthenticationProvider對(duì)象里的UserDetails,跳轉(zhuǎn)如下圖:

5.5DaoAuthenticationProvider的retirieveUser執(zhí)行完后。

會(huì)將數(shù)據(jù)庫(kù)查詢到的UserDetails返回給上一層,即AbstractUserDetailsAuthenticationProvider執(zhí)行的retrieveUser()方法,得到的UserDetails賦值給user。

6.接下來(lái)就是各種檢查。

其中,有一個(gè)檢查方法需要特別關(guān)注

注:additionalAuthenticationChecks()方法的作用是檢查密碼是否一致的,前面已根據(jù)username去數(shù)據(jù)庫(kù)里查詢出user數(shù)據(jù),接下來(lái),就需要在該方法里,檢查數(shù)據(jù)庫(kù)里user的密碼與登錄時(shí)傳入的密碼是否一致了。

6.1點(diǎn)擊additionalAuthenticationChecks()進(jìn)入到方法里。

發(fā)現(xiàn)AbstractUserDetailsAuthenticationProvider當(dāng)中的additionalAuthenticationChecks同樣是一個(gè)抽象方法,沒(méi)有具體實(shí)現(xiàn),它與前面的retrieveUser()方法一樣,具體實(shí)現(xiàn)都在AbstractUserDetailsAuthenticationProvider的子類DaoAuthenticationProvider中重寫了。

6.2.跳轉(zhuǎn)進(jìn)入子類重寫的additionalAuthenticationChecks()當(dāng)中。

先通過(guò)authentication.getCredentials().toString()從token對(duì)象中獲取登錄時(shí)輸入的密碼,再通過(guò)passwordEncoder.matches(presentedPassword,userDetails.getPassword())進(jìn)行比較,即拿登錄的密碼與數(shù)據(jù)庫(kù)里取出的密碼做對(duì)比,執(zhí)行到這一步,若兩個(gè)密碼一致時(shí),即登錄的username和password能與數(shù)據(jù)庫(kù)里某個(gè)username和密碼匹配,則可登錄成功。

7.用戶名與密碼都驗(yàn)證通過(guò)后,可繼續(xù)執(zhí)行下一步操作。

中間還有幾個(gè)檢查方法,讀者若感興趣,可自行研究。最后會(huì)把user賦值給一個(gè)principalToReturn對(duì)象,然后連同authentication還有user,一塊傳入到createSuccessAuthentication方法當(dāng)中。

8.在createSuccessAuthentication方法里,會(huì)創(chuàng)建一個(gè)已經(jīng)認(rèn)證通過(guò)的token。

點(diǎn)進(jìn)該token對(duì)象當(dāng)中,可以看到,這次的setAuthenticated設(shè)置成了true,即意味著已經(jīng)認(rèn)證通過(guò)。

最后,將生成一個(gè)新的token,并以Authentication對(duì)象形式返回到最開始的地方。

執(zhí)行到這一步,就可以把認(rèn)證通過(guò)的信息進(jìn)行存儲(chǔ),到這里,就完成了核心的認(rèn)證部分。

接下來(lái),我們的主角線程就可以前往JWT魔法屋獲取加密的token令牌,然后攜帶令牌返回故土,屆時(shí),其線程家族里的其他成員,都可穿過(guò)這座Spring Security山谷,前往山谷另一邊的web系統(tǒng)世界了。

那是另外一個(gè)世界的故事,我們將在以后漫長(zhǎng)的歲月當(dāng)中,緩緩道來(lái).....

而這個(gè)關(guān)于Spring Security山谷的故事,就暫且記到這里,若當(dāng)中有不當(dāng)之處,還需各位大佬指出而加以改進(jìn)。

以上就是關(guān)于Spring Security中獲取認(rèn)證機(jī)制核心原理的全部?jī)?nèi)容,如果您想要了解更多關(guān)于Spring Security的內(nèi)容,可以多多關(guān)注W3Cschool相關(guān)內(nèi)容的文章。如果您覺(jué)得本篇文章還不錯(cuò),還希望大家能夠多多支持!


0 人點(diǎn)贊