<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
一些許可權框架一般都包含認證器和決策器,前者處理登陸驗證,後者處理存取資源的控制
Spring Security的登陸請求處理如圖
下面來分析一下是怎麼實現認證器的
首先登陸請求會被UsernamePasswordAuthenticationFilter
攔截,這個過濾器看名字就知道是一個攔截使用者名稱密碼的攔截器
主要的驗證是在attemptAuthentication()
方法裡,他會去獲取在請求中的使用者名稱密碼,並且建立一個該使用者的上下文,然後在去執行一個驗證過程
String username = this.obtainUsername(request); String password = this.obtainPassword(request); //建立上下文 UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password); this.setDetails(request, authRequest); return this.getAuthenticationManager().authenticate(authRequest);
可以看看UsernamePasswordAuthenticationToken
這個類,他是繼承了AbstractAuthenticationToken
,然後這個父類別實現了Authentication
由這個類的方法和屬性可得知他就是儲存使用者驗證資訊的,認證器的主要功能應該就是驗證完成後填充這個類
回到UsernamePasswordAuthenticationToken
中,在上面建立的過程了可以發現
public UsernamePasswordAuthenticationToken(Object principal,Object credentials){ super(null); this.principal=principal; this.credentials=credentials; //還沒認證 setAuthenticated(false); }
還有一個super(null)
的處理,因為剛進來是還不知道有什麼許可權的,設定null是初始化一個空的許可權
//許可權利集合 private final Collection<GrantedAuthority> authorities; //空的集合 public static final List<GrantedAuthority> NO_AUTHORITIES = Collections.emptyList(); //初始化 if (authorities == null) { this.authorities = AuthorityUtils.NO_AUTHORITIES; return; }
那麼後續認證完還會把許可權設定儘量,此時可以看UsernamePasswordAuthenticationToken
的另一個過載構造器
//認證完成 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 }
在看原始碼的過程中,註釋一直在強調這些上下文的填充和設定都應該是由AuthenticationManager
或者AuthenticationProvider
的實現類去操作
接下來會把球踢給AuthenticationManager
,但他只是個介面
/** * 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; }
註釋也寫的很清楚了,認證完成後會填充Authentication
接下來會委託給ProviderManager
,因為他實現了AuthenticationManager
剛進來看authenticate()
方法會發現他先遍歷了一個List<AuthenticationProvider>
集合
/** * Indicates a class can process a specific Authentication **/ public interface AuthenticationProvider { Authentication authenticate(Authentication authentication) throws AuthenticationException; //支不支援特定型別的authentication boolean supports(Class<?> authentication); }
實現這個類就可以處理不同型別的Authentication
,比如上邊的UsernamePasswordAuthenticationToken
,對應的處理類是AbstractUserDetailsAuthenticationProvider
,為啥知道呢,因為在這個supports()
裡
public boolean supports(Class<?> authentication) { return (UsernamePasswordAuthenticationToken.class .isAssignableFrom(authentication)); }
注意到這個是抽象類,實際的處理方法是在他的子類DaoAuthenticationProvider
裡,但是最重要的authenticate()
方法子類好像沒有繼承,看看父類別是怎麼實現這個方法的
首先是繼續判斷Authentication
是不是特定的類
Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication, () -> messages.getMessage( "AbstractUserDetailsAuthenticationProvider.onlySupports", "Only UsernamePasswordAuthenticationToken is supported"));
查詢根據使用者名稱使用者,這次就是到了子類的方法了,因為這個方法是抽象的
user=retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication);
接著DaoAuthenticationProvider
會呼叫真正實現查詢使用者的類UserDetailsService
UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
UserDetailsService
這個類資訊就不陌生了,我們一般都會去實現這個類來自定義查詢使用者的方式,查詢完後會返回一個UserDetails
,當然也可以繼承這個類來擴充套件想要的欄位,主要填充的是許可權資訊和密碼
檢驗使用者,如果獲取到的UserDetails
是null,則拋異常,不為空則繼續校驗
//檢驗使用者合法性 preAuthenticationChecks.check(user); //校驗密碼 additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);
第一個教育是判斷使用者的合法性,就是判斷UserDetails
裡的幾個欄位
//賬號是否過期 boolean isAccountNonExpired(); //賬號被鎖定或解鎖狀態。 boolean isAccountNonLocked(); //密碼是否過期 boolean isCredentialsNonExpired(); //是否啟用 boolean isEnabled();
第二個則是由子類實現的,判斷從資料庫獲取的密碼和請求中的密碼是否一致,因為用的登陸方式是根據使用者名稱稱登陸,所以有檢驗密碼的步驟
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")); }
需要主要的是請求中的密碼是被加密過的,所以從資料庫獲取到的密碼也應該是被加密的
注意到當完成校驗的時候會把資訊放入快取
//當沒有從快取中獲取到值時,這個欄位會被設定成false if (!cacheWasUsed) { this.userCache.putUserInCache(user); } //下次進來的時候回去獲取 UserDetails user = this.userCache.getUserFromCache(username);
如果是從快取中獲取,也是會走檢驗邏輯的
最後完成檢驗,並填充一個完整的Authentication
return createSuccessAuthentication(principalToReturn, authentication, user);
由上述流程來看,Security的檢驗過程還是比較清晰的,通過AuthenticationManager
來委託給ProviderManager
,在通過具體的實現類來處理請求,在這個過程中,將查詢使用者的實現和驗證程式碼分離開來
整個過程看著像是策略模式,後邊將變化的部分抽離出來,實現解耦
前邊提到的認證成功會呼叫createSuccessAuthentication()
方法,裡邊的內容很簡單
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裡放了許可權集合,父類別的處理是判斷裡邊的許可權有沒有空的,沒有則轉換為唯讀集合
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方法,當我們終於從
result = provider.authenticate(authentication);
走出來時,後邊還有什麼操作
1.將返回的使用者資訊負責給當前的上下文
if (result != null) { copyDetails(authentication, result); break; }
2.刪除敏感資訊
((CredentialsContainer) result).eraseCredentials();
這個過程會將一些欄位設定為null,可以實現eraseCredentials()
方法來自定義需要刪除的資訊
最後返回到UsernamePasswordAuthenticationFilter
中通過過濾
這就是Spring Security實現認證的過程了
通過實現自己的上下文Authentication
和處理類AuthenticationProvider
以及具體的查詢使用者的方法就可以自定義自己的登陸實現
具體可以看Spring Security自定義認證器
到此這篇關於Spring Security認證器實現過程詳解的文章就介紹到這了,更多相關Spring Security認證器內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45