自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

一個(gè)奇怪的登錄需求,你知道嗎?

開發(fā) 前端
今天就和小伙伴們分享了一下在 Spring Security 中如何拋出 UsernameNotFoundException 異常,雖然這只是一個(gè)小眾需求,但是可以加深大家對 Spring Security 的理解。

一個(gè)奇怪的登錄需求。

這是小伙伴們在微信群里的一個(gè)提問,我覺得很有意思:

雖然這并非一個(gè)典型需求,但是把這個(gè)問題解決了,有助于加深大家對于 Spring Security 的理解。

因此,松哥打算擼一篇文章和大家稍微聊聊這個(gè)話題。

1. 問題再現(xiàn)

可能有小伙伴還不明白這個(gè)問題,因此我先稍微解釋一下。

當(dāng)我們登錄失敗的時(shí)候,可能用戶名寫錯(cuò),也可能密碼寫錯(cuò),但是出于安全考慮,服務(wù)端一般不會(huì)明確提示是用戶名寫錯(cuò)了還是密碼寫錯(cuò)了,而只會(huì)給出一個(gè)模糊的用戶名或者密碼寫錯(cuò)了。

然而對于很多新手程序員而言,可能并不了解這樣一些“潛規(guī)則”,可能會(huì)給用戶一個(gè)明確的提示,明確提示是用戶名寫錯(cuò)了還是密碼寫錯(cuò)了。

為了避免這一情況,Spring Security 通過封裝,隱藏了用戶名不存在的異常,導(dǎo)致開發(fā)者在開發(fā)的時(shí)候,只能獲取到 BadCredentialsException,這個(gè)異常既表示用戶名不存在,也表示用戶密碼輸入錯(cuò)誤。Spring Security 這樣做是為了確保我們的系統(tǒng)足夠安全。

然而由于種種原因,有時(shí)候我們又希望能夠分別獲取到用戶不存在的異常和密碼輸入錯(cuò)誤的異常,這個(gè)時(shí)候就需要我們對 Spring Security 進(jìn)行一些簡單的定制了。

2. 源碼分析

首先我們要先找到問題發(fā)生的原因,發(fā)生的地方。

在 Spring Security 中,負(fù)責(zé)用戶校驗(yàn)的工作的類有很多,我這里就不一一列舉了(感興趣的小伙伴可以查看《深入淺出Spring Security》一書),我這里直接說我們涉及到的關(guān)鍵類 AbstractUserDetailsAuthenticationProvider。

這個(gè)類將負(fù)責(zé)用戶名密碼的校驗(yàn)工作,具體在 authenticate 方法里邊,這個(gè)方法本來特別長,我這里只把和本文相關(guān)的代碼列出來:

@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
try {
user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
}
catch (UsernameNotFoundException ex) {
if (!this.hideUserNotFoundExceptions) {
throw ex;
}
throw new BadCredentialsException(this.messages
.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
}
}
}

retrieveUser 方法就是根據(jù)用戶登錄輸入的用戶名去查找用戶,如果沒找到,就會(huì)拋出一個(gè) UsernameNotFoundException,這個(gè)異常被 catch 之后,會(huì)首先判斷是否要隱藏這個(gè)異常,如果不隱藏,則原異常原封不動(dòng)拋出來,如果需要隱藏,則拋出一個(gè)新的 BadCredentialsException 異常,BadCredentialsException 異常從字面理解就是密碼輸入錯(cuò)誤的異常。

所以問題的核心就變成了 hideUserNotFoundExceptions 變量了。

這是一個(gè) Boolean 類型的屬性,默認(rèn)是 true,AbstractUserDetailsAuthenticationProvider 也為該屬性提供了 set 方法:

public void setHideUserNotFoundExceptions(boolean hideUserNotFoundExceptions) {
this.hideUserNotFoundExceptions = hideUserNotFoundExceptions;
}

看起來修改 hideUserNotFoundExceptions 屬性并不難!只要找到 AbstractUserDetailsAuthenticationProvider 的實(shí)例,然后調(diào)用相應(yīng)的 set 方法就能修改了。

現(xiàn)在問題的核心變成了從哪里獲取 AbstractUserDetailsAuthenticationProvider 的實(shí)例?

看名字就知道,AbstractUserDetailsAuthenticationProvider 是一個(gè)抽象類,所以它的實(shí)例其實(shí)就是它子類的實(shí)例,子類是誰?當(dāng)然是負(fù)責(zé)用戶密碼校驗(yàn)工作的 DaoAuthenticationProvider。

這個(gè)知識點(diǎn)先記住,我們一會(huì)會(huì)用到。

3. 登錄流程

為了弄明白這個(gè)問題,我們還需要搞懂 Spring Security 一個(gè)大致的認(rèn)證流程,這個(gè)也非常重要。

首先大家知道,Spring Security 的認(rèn)證工作主要是由 AuthenticationManager 來完成的,而 AuthenticationManager 則是一個(gè)接口,它的實(shí)現(xiàn)類是 ProviderManager。簡而言之,Spring Security 中具體負(fù)責(zé)校驗(yàn)工作的是 ProviderManager#authenticate 方法。

但是校驗(yàn)工作并不是由 ProviderManager 直接完成的,ProviderManager 中管理了若干個(gè) AuthenticationProvider,ProviderManager 會(huì)調(diào)用它所管理的 AuthenticationProvider 去完成校驗(yàn)工作,如下圖:

另一方面,ProviderManager 又分為全局的和局部的。

當(dāng)我們登錄的時(shí)候,首先由局部的 ProviderManager 出場進(jìn)行用戶名密碼的校驗(yàn)工作,如果校驗(yàn)成功,那么用戶就登錄成功了,如果校驗(yàn)失敗,則會(huì)調(diào)用局部 ProviderManager 的 parent,也就是全局 ProviderManager 去完成校驗(yàn)工作,如果全局 ProviderManager 校驗(yàn)成功,就表示用戶登錄成功,如果全局 ProviderManager 校驗(yàn)失敗,就表示用戶登錄失敗,如下圖:

OK,有了上面的知識儲備,我們再來分析一下我們想要拋出 UsernameNotFoundException 該怎么做。

4. 思路分析

首先我們的用戶校驗(yàn)工作在局部的 ProviderManager 中進(jìn)行,局部的 ProviderManager 中管理了若干個(gè) AuthenticationProvider,這若干個(gè) AuthenticationProvider 中就有可能包含了我們所需要的 DaoAuthenticationProvider。那我們是否需要在這里調(diào)用 DaoAuthenticationProvider 的 setHideUserNotFoundExceptions 方法完成屬性的修改呢?

松哥的建議是沒必要!

為什么?

因?yàn)楫?dāng)用戶登錄的時(shí)候,首先去局部的 ProviderManager 中去校驗(yàn),如果校驗(yàn)成功當(dāng)然最好;如果校驗(yàn)失敗,并不會(huì)立馬拋出異常,而是去全局的 ProviderManager 中繼續(xù)校驗(yàn),這樣即使我們在局部 ProviderManager 中拋出了 UsernameNotFoundException 也沒用,因?yàn)樽罱K這個(gè)異常能不能拋出來決定權(quán)在全局 ProviderManager 中(如果全局的 ProviderManager 所管理的 DaoAuthenticationProvider 沒做任何特殊處理,那么局部 ProviderManager 中拋出來的 UsernameNotFoundException 異常最終還是會(huì)被隱藏)。

所以,我們要做的就是獲取全局的 ProviderManager,進(jìn)而獲取到全局 ProviderManager 所管理的 DaoAuthenticationProvider,然后調(diào)用其 setHideUserNotFoundExceptions 方法修改相應(yīng)屬性值即可。

弄明白了原理,代碼就簡單了。

5. 具體實(shí)踐

全局 ProviderManager 的修改在 WebSecurityConfigurerAdapter#configure(AuthenticationManagerBuilder) 類中,這里配置的 AuthenticationManagerBuilder 最終用來生成全局的 ProviderManager,所以我們的配置如下:

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
daoAuthenticationProvider.setHideUserNotFoundExceptions(false);
InMemoryUserDetailsManager userDetailsService = new InMemoryUserDetailsManager();
userDetailsService.createUser(User.withUsername("javaboy").password("{noop}123").roles("admin").build());
daoAuthenticationProvider.setUserDetailsService(userDetailsService);
auth.authenticationProvider(daoAuthenticationProvider);
}

@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.failureHandler((request, response, exception) -> System.out.println(exception))
.permitAll();

}

}

這里的代碼就簡單了:

  • 創(chuàng)建一個(gè) DaoAuthenticationProvider 對象。
  • 調(diào)用 DaoAuthenticationProvider 對象的 setHideUserNotFoundExceptions 方法,修改相應(yīng)的屬性值。
  • 為 DaoAuthenticationProvider 配置用戶數(shù)據(jù)源。
  • 將 DaoAuthenticationProvider 設(shè)置給 auth 對象,auth 將用來生成全局的 ProviderManager。
  • 在另一個(gè) configure 方法中,我們就配置一下登錄回調(diào)即可,登錄失敗的時(shí)候,打印異常信息看看。

行啦。

接下來啟動(dòng)項(xiàng)目進(jìn)行測試。輸入一個(gè)錯(cuò)誤的用戶名,可以看到 IDEA 控制臺會(huì)打印出如下信息:

可以看到,UsernameNotFoundException 異常已經(jīng)拋出來了。

6. 小結(jié)

好啦,今天就和小伙伴們分享了一下在 Spring Security 中如何拋出 UsernameNotFoundException 異常,雖然這只是一個(gè)小眾需求,但是可以加深大家對 Spring Security 的理解,感興趣的小伙伴可以仔細(xì)琢磨下。

責(zé)任編輯:武曉燕 來源: 江南一點(diǎn)雨
相關(guān)推薦

2024-08-19 09:07:09

TSvoid類型

2021-11-02 22:50:10

鼠標(biāo)計(jì)算機(jī)傳感器

2023-12-12 08:41:01

2025-02-14 10:13:55

2015-10-23 09:34:16

2024-04-15 00:04:00

APP開發(fā)

2021-10-14 06:52:47

算法校驗(yàn)碼結(jié)構(gòu)

2024-09-18 07:00:00

消息隊(duì)列中間件消息隊(duì)列

2022-09-29 15:32:58

云計(jì)算計(jì)算模式

2024-08-01 17:34:56

Promiseaxios請求

2021-11-17 11:03:14

Python代碼語法

2024-04-07 00:00:00

ESlint命令變量

2024-05-28 09:12:10

2021-09-13 19:28:42

JavaNetty開發(fā)

2022-03-10 08:25:27

JavaScrip變量作用域

2019-12-12 09:23:29

Hello World操作系統(tǒng)函數(shù)庫

2020-11-10 10:26:16

串口打印工具

2021-05-31 10:22:09

Go語言代碼

2022-01-05 11:40:36

Go特性語言

2023-12-20 08:23:53

NIO組件非阻塞
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號