偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

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

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

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

這是小伙伴們?cè)谖⑿湃豪锏囊粋€(gè)提問(wèn),我覺(jué)得很有意思:

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

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

1. 問(wèn)題再現(xiàn)

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

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

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

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

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

2. 源碼分析

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

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

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

@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ù)用戶(hù)登錄輸入的用戶(hù)名去查找用戶(hù),如果沒(méi)找到,就會(huì)拋出一個(gè) UsernameNotFoundException,這個(gè)異常被 catch 之后,會(huì)首先判斷是否要隱藏這個(gè)異常,如果不隱藏,則原異常原封不動(dòng)拋出來(lái),如果需要隱藏,則拋出一個(gè)新的 BadCredentialsException 異常,BadCredentialsException 異常從字面理解就是密碼輸入錯(cuò)誤的異常。

所以問(wèn)題的核心就變成了 hideUserNotFoundExceptions 變量了。

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

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

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

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

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

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

3. 登錄流程

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

首先大家知道,Spring Security 的認(rèn)證工作主要是由 AuthenticationManager 來(lái)完成的,而 AuthenticationManager 則是一個(gè)接口,它的實(shí)現(xiàn)類(lèi)是 ProviderManager。簡(jiǎn)而言之,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 出場(chǎng)進(jìn)行用戶(hù)名密碼的校驗(yàn)工作,如果校驗(yàn)成功,那么用戶(hù)就登錄成功了,如果校驗(yàn)失敗,則會(huì)調(diào)用局部 ProviderManager 的 parent,也就是全局 ProviderManager 去完成校驗(yàn)工作,如果全局 ProviderManager 校驗(yàn)成功,就表示用戶(hù)登錄成功,如果全局 ProviderManager 校驗(yàn)失敗,就表示用戶(hù)登錄失敗,如下圖:

OK,有了上面的知識(shí)儲(chǔ)備,我們?cè)賮?lái)分析一下我們想要拋出 UsernameNotFoundException 該怎么做。

4. 思路分析

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

松哥的建議是沒(méi)必要!

為什么?

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

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

弄明白了原理,代碼就簡(jiǎn)單了。

5. 具體實(shí)踐

全局 ProviderManager 的修改在 WebSecurityConfigurerAdapter#configure(AuthenticationManagerBuilder) 類(lèi)中,這里配置的 AuthenticationManagerBuilder 最終用來(lái)生成全局的 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();

}

}

這里的代碼就簡(jiǎn)單了:

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

行啦。

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

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

6. 小結(jié)

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

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

2024-08-19 09:07:09

TSvoid類(lèi)型

2023-12-12 08:41:01

2015-10-23 09:34:16

2025-02-14 10:13:55

2019-12-12 09:23:29

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

2022-03-10 08:25:27

JavaScrip變量作用域

2021-09-13 19:28:42

JavaNetty開(kāi)發(fā)

2021-11-02 22:50:10

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

2023-12-20 08:23:53

NIO組件非阻塞

2023-04-26 10:21:04

2024-04-30 09:02:48

2024-04-15 00:04:00

APP開(kāi)發(fā)

2021-10-14 06:52:47

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

2022-09-29 15:32:58

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

2024-09-18 07:00:00

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

2024-01-18 07:46:53

HookReact回調(diào)函數(shù)

2021-11-17 11:03:14

Python代碼語(yǔ)法

2024-08-01 17:34:56

Promiseaxios請(qǐng)求

2019-01-07 13:01:08

Linux驚嘆用法命令

2023-02-28 07:39:18

點(diǎn)贊
收藏

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