最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會員登陸 & 注冊

學(xué)習(xí)記錄之Spring Security框架(續(xù))

2022-07-19 23:10 作者:星月襲空  | 我要投稿

解析JWT時,可能會出現(xiàn)一些異常,例如:

- 當(dāng)JWT數(shù)據(jù)過期時:

當(dāng)生成和解析使用的密鑰不一致時,或JWT數(shù)據(jù)的最后一部分被惡意篡改時:

?

當(dāng)JWT數(shù)據(jù)的第1部分被惡意篡改時:

?在項目中使用JWT

在項目中使用JWT時,通常需要關(guān)注的問題:

- 什么時候生成JWT:通常是登錄成功之后,將生成JWT,且會將JWT響應(yīng)到客戶端

- 客戶端什么時候攜帶JWT來訪問服務(wù)器端:服務(wù)器端不關(guān)心

- 什么時候檢查JWT:

向客戶端響應(yīng)JWT

當(dāng)需要向客戶端響應(yīng)JWT時,需要:

- 在`AdminServiceImpl`的`login()`中,獲取`authenticate()`返回的結(jié)果,將此結(jié)果轉(zhuǎn)換成`User`類型,即可從此`User`類型中獲取當(dāng)初在`UserDetailsService`中存入的數(shù)據(jù),然后,將必要的部分取出(暫時為`username`),將其生成為JWT數(shù)據(jù)(參考測試類,暫不考慮封裝工具類)

- 在`IAdminService` 接口中將`login()`的返回值改為`String`

- 在`AdminServiceImpl`類中也將`login()`的返回值改為`String`,并返回JWT數(shù)據(jù)

- 在`AdminController`處理登錄的方法中,調(diào)用Service組件的方法時獲取返回值,并將此返回值封裝到響應(yīng)結(jié)果中

關(guān)于`AdminServiceImpl`中的實現(xiàn)代碼:

?關(guān)于客戶端攜帶JWT數(shù)據(jù)

當(dāng)客戶端嘗試訪問需要認(rèn)證才能請求的資源時,客戶端應(yīng)該攜帶JWT數(shù)據(jù),而服務(wù)器端應(yīng)該對JWT數(shù)據(jù)進(jìn)行獲取、檢查、解析等處理。

當(dāng)客戶端攜帶JWT時,通常會將JWT數(shù)據(jù)放在請求頭(Request Header)中的`Authorization`屬性中,并且,通常,服務(wù)器端的程序都會設(shè)計為從請求頭中的`Authorization`屬性中獲取JWT數(shù)據(jù)。

?服務(wù)器端檢查JWT

由于許多不同的請求都需要檢查JWT,所以,不會在控制器中處理JWT!

通常,應(yīng)該在過濾器組件中檢查JWT!

- 過濾器是Java服務(wù)器端程序(無論你使用什么框架)中最早接收到客戶端請求的組件,且所有請求都會經(jīng)過過濾器才會執(zhí)行到控制器

需自定義過濾器類:

并在配置類中添加配置:

完整編碼流程:基于Security+JWT的管理員登錄

- 相關(guān)依賴:`spring-boot-starter-security`、`jjwt`

- 創(chuàng)建管理員登錄的VO類,例如`AdminLoginVO`

- 在`AdminMapper`接口和`AdminMapper.xml`文件中實現(xiàn):根據(jù)用戶名查詢管理員信息,應(yīng)該至少包括:用戶名、密碼、權(quán)限

- 自定義類,實現(xiàn)`UserDetailsService`接口,重寫`loadUserByUsername()`方法,在此類中通過`AdminMapper`的查詢找到對應(yīng)的管理員信息,并封裝到`UserDetails`類型的對象中返回

- 創(chuàng)建Security配置類,繼承自`WebSecurityConfigurerAdapter`類,在此類中使用`@Bean`方法得到`AuthenticationManager`對象,使用`@Bean`方法得到`BCryptPasswordEncoder`對象

- 在`IAdminService`接口中添加登錄的抽象方法,并在`AdminServiceImpl`中重寫此方法,在方法體中,調(diào)用`AuthenticationManager`的`authenticate()`執(zhí)行認(rèn)證,如果認(rèn)證通過,應(yīng)該生成JWT數(shù)據(jù)并返回,此JWT數(shù)據(jù)中應(yīng)該包含用戶名及必要信息

- 在`AdminController`中處理登錄請求,并通過調(diào)用`IAdminService`類型的組件來實現(xiàn),將調(diào)用得到的JWT響應(yīng)到客戶端去

?完整編碼流程:登錄后的訪問

- 在Security的配置類中,指定一些白名單,這些是不需要登錄就可以直接訪問的,其它請求路徑都必須登錄后才可以訪問,需要注意:登錄、注冊等請求路徑必須在白名單,否則不合理

- 創(chuàng)建JWT過濾器,在此過濾器中:

- 清除Security的上下文

- 從請求頭中獲取JWT

- 對JWT數(shù)據(jù)進(jìn)行基本判斷(是否有值),如果沒有有效值,直接放行

- 如果獲取到有效的JWT,則解析,得到用戶信息,將用戶信息存入到上下文中

- 在Security的配置類中,添加以上過濾器,將其添加在`UsernamePasswordAuthenticationFilter`之前

?關(guān)于CORS

CORS:跨域的異步訪問,默認(rèn)情況下,是不允許的。

在使用Spring MVC框架時,需要允許跨域訪問時,可以自定義配置類,實現(xiàn)`WebMvcConfigure`接口,重寫其中的`addCorsMappings()`方法:

當(dāng)項目中進(jìn)一步使用了Spring Security框架后,當(dāng)客戶端提交復(fù)雜請求(自定義了請求頭中非常規(guī)屬性,例如添加了`Authorization`屬性)時,還需要在Spring Security的配置類允許復(fù)雜請求的跨域訪問,解決方案可以是:

或者:

之所以需要進(jìn)行這樣的處理,是因為復(fù)雜請求本身有預(yù)檢(PreFlight)機制,在提交請求時,客戶端會自動先提交`OPTIONS`類型的請求,此時服務(wù)器端可能是不通過的,則會出現(xiàn)`403`錯誤,并且,實質(zhì)嘗試提交的請求(例如`GET`、`POST`)中復(fù)雜請求頭部信息不會被提交。在瀏覽器端,一旦成功的提交了復(fù)雜請求,則后續(xù)不會自動提交`OPTIONS`請求執(zhí)行預(yù)檢。

實現(xiàn)授權(quán)訪問

實現(xiàn)授權(quán)訪問的步驟:

- 當(dāng)用戶嘗試登錄時,應(yīng)該根據(jù)用戶名從數(shù)據(jù)庫中查詢出此管理員的權(quán)限信息

- 在`UserDetailsServiceImpl`中,(當(dāng)?shù)卿浾J(rèn)證時,Spring Security框架會自動調(diào)用此類中的`loadUserByUesrname()`方法),根據(jù)用戶名查詢到有效管理員信息后,向`UserDetails`中存入權(quán)限信息

? - 將`List<String>`格式的權(quán)限集合轉(zhuǎn)換成`String...`格式即可,例如:

- 在`AdminServiceImpl`的`login()`中,認(rèn)證成功后,從返回的`Authentication`中取出權(quán)限信息,并其生成到JWT中

? - 為保證后續(xù)能從JWT中取出權(quán)限且還原成正常的格式,應(yīng)該將權(quán)限列表(`Collection<? extend GrandtedAuthority>`)轉(zhuǎn)換成JSON格式的字符串再寫入

- 在`JwtAuthorizationFilter`中,從JWT中解析出權(quán)限,并存入到Security的上下文中

? - 從JWT中解析出的權(quán)限是JSON格式的字符串,需還原成`Collection<? extend GrandtedAuthority>`類型才可以存入到Security的上下文中,可以還原成`List<SimpleGrantedAuthority>`

- 在Security的配置類`SecurityConfiguration`上添加注解`@EnableGlobalMethodSecurity(prePostEnabled = true)`以開啟全局的授權(quán)訪問檢查

? - 此配置是一次性的配置

- 在控制器中,在處理請求的方法上,使用`@PreAuthorize`注解,配置其中的`hasAuthority`屬性,即可要求此請求必須具有某種權(quán)限

? - 例如:`@PreAuthorize("hasAuthority('/ams/admin/read')")`

根據(jù)用戶名查詢管理員的權(quán)限

首先,在`AdminLoginVO`中添加必要的屬性:

然后,在`AdminMapper.xml`中配置查詢:

?關(guān)于JWT過濾器的處理細(xì)節(jié)

解析JWT是可能失敗的,例如JWT數(shù)據(jù)過期、簽名錯誤、數(shù)據(jù)非法等,這些錯誤都應(yīng)該被處理,否則,就會存在異常未處理的情況,最終將導(dǎo)致500錯誤!

關(guān)于以上可能的錯誤,應(yīng)該大致分為3類,一類是JWT數(shù)據(jù)過期,一類是JWT數(shù)據(jù)被惡意篡改,再另外還有可能是其它的錯誤。

首先,先在`ServiceCode`中添加新的業(yè)務(wù)狀態(tài)碼,對應(yīng)一些錯誤:

然后,需要在JWT過濾器中,自行使用`try...catch`來捕獲并處理異常!

在登錄的用戶身份標(biāo)識中添加自定義信息

Spring Security框架中并沒有使用、封裝用戶的ID等相關(guān)信息,如果使用過程中,需要自行封裝更多的信息,并添加到用戶身份標(biāo)識中,則需要:

- 自定義類實現(xiàn)`UserDetails`接口

- 或,自定義類繼承`User`類

并且,在自定義類中添加所需的屬性,例如ID,然后,在`UserDetailsService`的實現(xiàn)類中,在`loadUserByUsername()`方法返回自定義類的對象。

所以,創(chuàng)建`AdminDetails`類:

在`UserDetailsServiceImpl`中,需要返回時:

接下來,在`AdminServiceImpl`的`login()`方法中,通過`AuthenticationManager`的`authenticate()`執(zhí)行認(rèn)證且通過認(rèn)證的返回結(jié)果就是以上`AdminDetails`對象,所以,可以從中獲取管理員的id,并用于生成JWT數(shù)據(jù),則用戶登錄成功后得到的JWT數(shù)據(jù)中將包含Id信息。

后續(xù),客戶端提交請求時,攜帶的JWT也是包含Id信息的,可以在`JwtAuthenticationFilter`中解析得到此Id,最終,此Id值應(yīng)該封裝到Security的上下文中,則可以利用`UsernamePasswordAuthenticationToken`類的`principal`屬性(`Object`類型),所以,自定義類,用于封裝后續(xù)可能需要使用到的管理員信息:

然后,在過濾器,將其存入:

至此,當(dāng)客戶端攜帶JWT訪問服務(wù)器端時,服務(wù)器端的Security的上下文中就包含了管理員的id、用戶名、權(quán)限,其中,權(quán)限不需要自行使用,都是Security框架自動判斷(你只需要在控制器處理請求的方法上配置`@PreAuthorize`注解即可),當(dāng)需要獲取管理員的id、用戶名時,可以在控制器處理請求的方法的參數(shù)列表中添加`Authentication`即可,此參數(shù)就是Security上下文中的認(rèn)證信息(過濾器中存入的對象),例如:

從`Authentication`中獲取`LoginPrincipal`比較麻煩,還需要自行獲取、轉(zhuǎn)換類型,可以改為聲明`LoginPricipal`參數(shù)(在過濾器中封裝到`UsernamePasswordAuthenticationToken`的`pricipal`屬性中的對象),然后,在此參數(shù)前添加`@AuthenticationPrincipal`注解,即可直接使用:

Spring Security框架的相關(guān)概念

**Authorization**

認(rèn)證,在項目中,它主要表現(xiàn)為攜帶JWT的請求頭的屬性名,是建議使用的屬性名。

**Authority**

權(quán)限,通常表現(xiàn)為一些字符串,這些字符串應(yīng)該具有唯一、易于閱讀的特性,框架會根據(jù)登錄后的用戶信息和控制器中配置的權(quán)限進(jìn)行檢查,以判斷某用戶是否具有執(zhí)行此操作的權(quán)力。

**Principal**

當(dāng)事人,是`Authentication`中的部分屬性,以`UsernamePasswordAuthenticationToken`為例,它當(dāng)中就包括了Principal、Credentials、Authorities這3大部分,在項目中,如果`Authentication`是用于執(zhí)行認(rèn)證,則此Principal就是用戶名,如果`Authentication`是用戶認(rèn)證后的信息,則可以包含其它意義,例如ID、用戶名等。

**Token**

票據(jù)、令牌,指的是攜帶了一部分有意義的數(shù)據(jù)的信息。

**UserDetails**

用戶詳情,是用于執(zhí)行認(rèn)證過程中,封裝用戶的信息,例如,在`UserDetailsService`接口的實現(xiàn)類中,在`loadUserByUsername()`方法中就應(yīng)該返回此類型的對象,則Spring Security會自動調(diào)用此方法來獲取`UserDetails`類型的結(jié)果,此結(jié)果中應(yīng)該包含密碼,且Spring Security會自動調(diào)用`PasswordEncoder`來驗證用戶請求登錄時輸入的密碼,并且,此類型也會是認(rèn)證成功后`Authentication`中的Principal。

使用Spring Security框架時涉及的文件

**pom.xml**

需要添加相關(guān)依賴,當(dāng)需要使用Spring Security時,添加`spring-boot-starter-security`,當(dāng)需要使用JWT時,添加`jjwt`(生成和解析JWT數(shù)據(jù)的工具包)和`fastjson`(實現(xiàn)對象與JSON字符串互相轉(zhuǎn)換的工具包)。

**UserDetailsServiceImpl**

是`UserDetailsService`接口的實現(xiàn)類,需要重寫其中的`UserDetails loadUserByUsername(String s)`方法,Spring Security在執(zhí)行認(rèn)證時會自動調(diào)用此方法,此方法的返回結(jié)果必須至少包括:密碼、權(quán)限和其它必要的信息(根據(jù)API決定)。

關(guān)于返回的`UserDetails`,通??赡苁褂胉User`類型,但是,此類型并不包含`id`等屬性,所以,也可能自定義類實現(xiàn)`UserDetails`接口,或自定義類繼承自`User`,然后作為返回的`UserDetails`對象。

**SecurityConfiguration**

是Spring Security框架的配置類,需要繼承自`WebSecurityConfigurerAdapter`。此類可以添加`@EnableGlabalMethodSecurity(prePostEnabled = true)`注解,用于開啟全局的方法上的授權(quán)檢查(允許在處理請求的方法使用`@PreAuthorize`檢查權(quán)限)。通常,在此類中會配置`PasswordEncoder`對應(yīng)的`@Bean`方法(此方法也可以在其它配置類中),在執(zhí)行認(rèn)證時,Spring Security會自動使用此`PasswordEncoder`對象的`matches()`方法來驗證密碼。

- 如果密文是BCrypt算法生成的,則應(yīng)該在`@Bean`方法中返回`BCryptPasswordEncoder`,如果沒有密文(密碼并未加密),則此方法中應(yīng)該返回`NoOpPasswordEncoder`,以此類推

在此類中,還可能配置`AuthenticationManager`對應(yīng)的`@Bean`,此方法一般是重寫的方法,用于返回`AuthenticationManager`對象,用于在其它組件中執(zhí)行認(rèn)證,例如在Service中自動裝配此類型的屬性,并調(diào)用`authenticate()`方法來執(zhí)行認(rèn)證。

在此類中,比較重要的是重寫`void configure(HttpSecurity http)`方法,在此方法內(nèi)部對如何處理請求進(jìn)行配置,通常,需要配置的有:

- `http.csrf().disable()`:禁用防止跨域偽造的攻擊,是固定的配置

- `http.cors()`:在Spring Security的過濾器鏈中添加`CorsFilter`,以實現(xiàn)放行復(fù)雜的異步請求的預(yù)檢。另外,還應(yīng)該調(diào)用`http`參數(shù)對象及對應(yīng)的鏈?zhǔn)椒椒ㄟM(jìn)行一些配置:

- `authroizeRequests()`:對請求進(jìn)行認(rèn)證

- `antMatchers()`:匹配某些路徑,此方法并不決定這些路徑應(yīng)該如何被處理

- `permitAll()`:允許此前的`antMatchers()`配置的路徑的所有方法直接訪問

- `anyRequest()`:匹配其它的任何請求(請求路徑),即在此前調(diào)用的所有`antMatchers()`以外的請求,此方法也不決定這些請求應(yīng)該如何被處理

- `authenticated()`:已經(jīng)認(rèn)證的

**AdminDetails**

是`UserDetails`接口的實現(xiàn)類,或`User`的子類,這個類的主要作用是對`User`類進(jìn)行擴(kuò)展,因為在開發(fā)實踐中,需要的認(rèn)證信息中通常還包括用戶的id等信息,而Spring Security的`User`中并沒有定義這些屬性,所以,不滿足開發(fā)需求,則其進(jìn)行擴(kuò)展。

當(dāng)編寫`UserDetailsServiceImpl`的`loadUserByUsername()`時,此方法應(yīng)該返回`AdminDetails`類型的對象。

當(dāng)調(diào)用`AuthenticationManager`的`authenticate()`方法時,返回結(jié)果中的Principal就是`AdminDetails`對象。

**JwtUtils**

主要定義生成JWT和解析JWT的方法,便于在其它組件中直接調(diào)用,而不必關(guān)心生成JWT和解析JWT的細(xì)節(jié)。

**JwtAuthorizationFilter**

這是處理JWT的過濾器,其主要作用是對客戶端的請求頭中的有效JWT進(jìn)行解析,并將解析得到的結(jié)果封裝到認(rèn)證信息中,然后將認(rèn)證信息到Spring Security的上下文中,以至于:

- Spring Security會自動從上下文中取出認(rèn)證信息中的權(quán)限部分,用于自動判斷權(quán)限,所以,在控制器中處理請求的方法上,只需要使用`@PreAuthroize`注解即可實現(xiàn)授權(quán)訪問的檢查

- 在控制器中處理請求的方法的參數(shù)列表中,可以添加`Authentication`參數(shù),則在控制器中就可以獲取認(rèn)證信息,甚至,不使用`Authentication`參數(shù),而是使用自定義的當(dāng)事人類型,添加`@AuthenticationPrincipal`注解,就可以直接得到自定義的當(dāng)事人信息

在此過濾器的實現(xiàn)過程中,需要注意:

- 對于明顯無效的JWT(為`null`、是空字符串等)應(yīng)該直接放行,因為有些請求本不應(yīng)該攜帶JWT數(shù)據(jù),例如登錄、注冊……

- 解析JWT是可能失敗的,特別是JWT可能過期,則應(yīng)該直接對相關(guān)的異常進(jìn)行處理,當(dāng)前組件是過濾器,是執(zhí)行在所有其它組件之前的,所以,不能拋出異常使用Spring MVC統(tǒng)一處理異常的機制

- 從JWT中解析出相關(guān)數(shù)據(jù)后,應(yīng)該封裝到`UsernamePasswordAuthenticationToken`中,其中,權(quán)限信息應(yīng)該封裝到此類型的`authorities`屬性中,用戶的登錄信息(當(dāng)事人信息)應(yīng)該封裝到此類型的`principal`屬性中,另外,如果某個其它的系統(tǒng)(其它項目)并沒有權(quán)限相關(guān)的概念,此處的`authorities`也不能為空,否則,會被Spring Security視為“沒有有效的認(rèn)證信息”

- 一定要將認(rèn)證信息存入到Spring Security的上下文中

- 為了避免后續(xù)使用中可能出現(xiàn)的某些問題(例如第1次訪問攜帶JWT最終向上下文中存入信息,后續(xù)不再攜帶JWT也會視為已登錄),應(yīng)該在過濾器剛剛執(zhí)行時,清除Spring Security的上下文中的信息

**LoginPrincipal**

主要用于封裝當(dāng)事人的多個屬性,例如同時將id、 用戶名存入到`UsernamePasswordAuthenticationToken`中去,后續(xù),在控制器中處理請求的方法的參數(shù)列表中,就可以使用`@AuthenticationPrincipal LoginPrincipal loginPrincipal`參數(shù)來得到當(dāng)前登錄的當(dāng)事人信息。

**其它相關(guān)類或?qū)崿F(xiàn):AdminMapper及相關(guān)**

必須實現(xiàn)“根據(jù)用戶名查詢管理員信息”的功能,且返回的結(jié)果中必須包含此管理員的權(quán)限列表。

**其它相關(guān)類或?qū)崿F(xiàn):AdminServiceImpl**

在處理登錄的過程中,應(yīng)該調(diào)用`AuthenticationManager`的`authenticate()`執(zhí)行認(rèn)證,并獲取返回結(jié)果,然后,將返回結(jié)果中的必要數(shù)據(jù)用于生成JWT,作為業(yè)務(wù)方法的返回值。

**其它相關(guān)類或?qū)崿F(xiàn):AdminController**

在處理登錄時,必須響應(yīng)調(diào)用Service組件時返回的JWT數(shù)據(jù)。

在其它需要獲取認(rèn)證信息的方法中,在參數(shù)列表中添加`@AuthenticationPrincipal LoginPrincipal loginPrincipal`來獲取當(dāng)前登錄的當(dāng)事人信息。

當(dāng)某個請求必須擁有某種權(quán)限才可以訪問時,在方法上添加`@PreAuthorize`注解配置權(quán)限。

學(xué)習(xí)記錄之Spring Security框架(續(xù))的評論 (共 條)

分享到微博請遵守國家法律
德清县| 马关县| 阳新县| 安多县| 龙门县| 柞水县| 浦县| 平顶山市| 曲松县| 中方县| 安龙县| 镇江市| 竹北市| 饶阳县| 鲁甸县| 台州市| 东明县| 江西省| 古浪县| 德兴市| 乌兰县| 郯城县| 金坛市| 四川省| 深泽县| 瑞昌市| 昭觉县| 汾阳市| 商水县| 平阴县| 眉山市| 环江| 望奎县| 邵阳县| 七台河市| 阿拉善右旗| 阿拉善左旗| 闽侯县| 信宜市| 安国市| 辽中县|