javascript
idea查询类_Spring Security入门(三): 基于自定义数据库查询的认证实战
0 引言
在筆者的上一篇文章中Spring Security入門(二):基于內(nèi)存的認(rèn)證一文中有提到過(guò)Spring Security實(shí)現(xiàn)自定義數(shù)據(jù)庫(kù)查詢需要你實(shí)現(xiàn)UserDetailsService接口,并實(shí)現(xiàn)loadUserByUsername(String username)抽象方法。我們可以在UserDetailsService接口的實(shí)現(xiàn)類中注入數(shù)據(jù)庫(kù)訪問(wèn)對(duì)象Dao,從而實(shí)現(xiàn)自定義數(shù)據(jù)庫(kù)查詢認(rèn)證用戶信息。下面在筆者的boot-demo實(shí)戰(zhàn)項(xiàng)目中我們結(jié)合spring data jpa作為持久層技術(shù)來(lái)一步一步實(shí)現(xiàn)自定義數(shù)據(jù)庫(kù)認(rèn)證。
1 表結(jié)構(gòu)設(shè)計(jì)與實(shí)體類
1.1 新建用戶表tbl_user與對(duì)應(yīng)實(shí)體類
筆者使用的數(shù)據(jù)庫(kù)為mysql5.6, 在IDEA中新建一個(gè)客戶端連接,并在就控制臺(tái)窗口中執(zhí)行如下新建tbl_user表的腳本:
use mysql;然后執(zhí)行插入兩條數(shù)據(jù):
#張三原始密碼為zhangsan123為了維護(hù)用戶敏感信息的安全,數(shù)據(jù)庫(kù)里用戶的登錄密碼或支付密碼等安全性要求較高的字段一律采用加密存儲(chǔ)的方式存儲(chǔ)。
添加用戶的sql腳本中用戶的加密密文均在是IDEA中的命令控制臺(tái)執(zhí)行spring-boot-cli命令spring encodepassword ${password}的方式獲得,其實(shí)質(zhì)是使用BCryptPasswordEncoder編碼原始密碼所得
tbl_user表的建表依據(jù)為:在spring security自定義用戶類必須實(shí)現(xiàn)UserDetails,UserDetails的源碼如下:
public于是我們創(chuàng)建一個(gè)實(shí)現(xiàn)UserDetails接口的實(shí)現(xiàn)類并使之與tbl_user表中的字段一一對(duì)應(yīng)
user.java
"tbl_user")1.2 新建角色表roels及其對(duì)應(yīng)的實(shí)體類
roles表的建表sql腳本如下:
use mysql;執(zhí)行難添加用戶sql腳本:
insert新建roles表對(duì)應(yīng)是實(shí)體類Role.java:
@Entity(name=1.3 新建用戶-角色關(guān)系關(guān)系表tbl_user_role及其對(duì)應(yīng)的實(shí)體類
tbl_user_role表的建表sql腳本如下:
use mysql;執(zhí)行往tbl_user_role表添加數(shù)據(jù)sql腳本:
--插入數(shù)據(jù)新建tbl_user_role表對(duì)應(yīng)的實(shí)體類UserRole.java:
@Entity(name=2 ?用于認(rèn)證用戶信息的數(shù)據(jù)庫(kù)訪問(wèn)層
2.1 新建與用戶表對(duì)應(yīng)的Repository接口
publicTblUserRepository接口繼承JpaRepository接口,自動(dòng)擁有了基本的CRUD、分頁(yè)查詢方法及根據(jù)字段和關(guān)鍵字查找表對(duì)應(yīng)實(shí)體類信息的功能。在TblUserRepository接口中我們自定義了一個(gè)根據(jù)username字段查找用戶信息的方法,繼承自JpaRepository接口的數(shù)據(jù)庫(kù)訪問(wèn)接口無(wú)需開發(fā)人員手動(dòng)實(shí)現(xiàn)其中 2.2 新建與角色表對(duì)應(yīng)的Repository接口
public在RoleRepository接口中筆者自定義了根據(jù)角色id列表查詢角色列表的抽象方法,方便給用戶查詢角色列表
2.3 新建與用戶角色關(guān)系表對(duì)應(yīng)的Repository接口
public在 UserRoleRepository接口中,筆者定義了根據(jù)角色id查詢用戶角色列表的抽象方法。
3 定義UserDetailsService接口的實(shí)現(xiàn)類
@Service以上為了從數(shù)據(jù)庫(kù)中查出登錄用戶的用戶名、加密密文及角色列表從數(shù)據(jù)庫(kù)中查了3次。
第1次通過(guò)TblUserRepository#findUserByUsername傳入username參數(shù)查出不包含角色信息的User對(duì)象,如果用戶不存在則直接返回null;
第2次通過(guò)UserRoleRepository#findByUserId 傳入用戶id查出用戶-角色關(guān)系列表
第3步通過(guò)第2部中得到了角色id列表作為入?yún)魅氲絉oleRepository#findRolesByRoleIdIn方法得到完整的角色信息列表
由于使用spring-data-jpa 實(shí)現(xiàn)關(guān)聯(lián)查詢筆者暫時(shí)還沒有掌握,因而以上認(rèn)證用戶信息訪問(wèn)了三次數(shù)據(jù)庫(kù),確實(shí)容易影響效率;在實(shí)際的商用生產(chǎn)環(huán)境可以參照spring-data-jpa的連接查詢改為連接查詢,對(duì)于用戶登錄認(rèn)證信息等熱點(diǎn)數(shù)據(jù)首次你從數(shù)據(jù)庫(kù)查詢出來(lái)后最好緩存在redis緩存中,并設(shè)置過(guò)期時(shí)間。另外如果是使用mybatis作為數(shù)據(jù)庫(kù)持久層框架,可以借助resultMap集合association屬性通過(guò)一條sql將包含角色列表的用戶信息一次性查出來(lái)
4 WebSecurityConfigurerAdapter實(shí)現(xiàn)類中配置userDetailsService
4.1 配置userDetailsService
@Configuration在上文《Spring Security入門(二) 基于內(nèi)存存儲(chǔ)的表單登錄實(shí)戰(zhàn)》的基礎(chǔ)上對(duì)所有用戶進(jìn)入登錄頁(yè)面和登錄接口放開權(quán)限,而對(duì)/index/*路徑下的接口允許訪問(wèn)角色改為數(shù)據(jù)庫(kù)中存在的Admin,SystemAdmin,Developer等角色。登錄成功處理器和失敗處理器配置用沿用上文中的邏輯。
4.2 測(cè)試用戶登錄認(rèn)證效果
在瀏覽器中輸入 http://localhost:8088/apiBoot/login 回車即可進(jìn)入登錄頁(yè)面
右鍵->檢查 ?在下方在彈出的元素審查窗口中選中Elements標(biāo)簽查看表單的html源碼,我們可以看到登錄表單中實(shí)際還包含了一個(gè)隱藏了_csrf輸入框,其值為622251f2-f7f3-4b78-88a0-52451771deaf,是一個(gè)UUID字符串,它的用處是為了保護(hù)web請(qǐng)求,防止跨站請(qǐng)求偽造(簡(jiǎn)稱CSRF)
輸入數(shù)據(jù)庫(kù)中存在的用戶賬號(hào)x_zhangsan己密碼zhangsan123,點(diǎn)擊Sign in即可登錄成功,登錄成功后瀏覽器中會(huì)返回如下信息:
"msg":登錄成功后的返回信息中包含了用戶的基本屬性和角色及權(quán)限信息。
使用postman登錄需要帶上_csrf的token值:
//localhost:8088/apiBoot/login?username=x_zhangsan&password=zhangsan123&_csrf=66dff592-bc63-488f-ab34-929258a55db6在postman 中響應(yīng)信息得到了json格式的美化,看起來(lái)非常清晰
5 ?存儲(chǔ)用戶認(rèn)證信息類的源碼解讀
5.1 認(rèn)識(shí)SecurityContextHolder和SecurityContext
用戶登錄成功后的認(rèn)證信息最終能會(huì)作為一個(gè)Authentication 實(shí)現(xiàn)類對(duì)象(表單登錄通常對(duì)應(yīng)的是一個(gè)UsernamePasswordAuthenticationToken對(duì)象)對(duì)象被認(rèn)證過(guò)濾器保存在SecurityContextHolder類的SecurityContext(安全上下文)中,之后就可以通過(guò)SecurityContextHolder這個(gè)類直接去獲取當(dāng)前登錄用戶的認(rèn)證信息了,SecurityContextHolder其實(shí)就是一個(gè)存放用戶具體認(rèn)證信息的工具類。
通過(guò)查看這兩個(gè)類的相關(guān)源碼可以對(duì)Spring Security安全框架是如何保存用戶的認(rèn)證信息的原理會(huì)有一個(gè)更全面的認(rèn)識(shí),相關(guān)源碼如下:
SecurityContextHolder.java
publicSecurityContext.java
publicSecurityContext類是一個(gè)接口,它的實(shí)現(xiàn)類是SecurityContextImpl
(1)利用SecurityContextHolder保存用戶認(rèn)證信息的示例源碼:
//第1步創(chuàng)建一個(gè)空的SecurityContext對(duì)象實(shí)例采用SecurityContextHolder.createEmptyContext()方法,而不是使用SecurityContextHolder.getContext().setAuthentication(authentication) 獲取一個(gè)SecurityContext對(duì)象實(shí)例的目的是為了避免多線程場(chǎng)景下的跨線程競(jìng)爭(zhēng)
Spring Security不關(guān)心是何種Authentication的實(shí)現(xiàn)類實(shí)例被設(shè)置到SecurityContext實(shí)例中,測(cè)試場(chǎng)景下使用TestingAuthenticationToken實(shí)現(xiàn)類是為了方便,大多數(shù)生產(chǎn)場(chǎng)景下一般選用UsernamePasswordAuthenticationToken(userDetails, password, authorities)構(gòu)造Authentication實(shí)現(xiàn)類對(duì)象實(shí)例
最后SecurityContext實(shí)例被保存到SecurityContextHolder類后,Spring Security會(huì)使用這些信息來(lái)進(jìn)行后面當(dāng)前認(rèn)證用戶在每一個(gè)限權(quán)操作的權(quán)限鑒定,簡(jiǎn)稱鑒權(quán)(authorization)
(2)利用SecurityContextHolder獲取用戶的認(rèn)證信息和權(quán)限的代碼實(shí)例如下:
SecurityContext context = SecurityContextHolder.getContext();Authentication authentication = context.getAuthentication();
String username = authentication.getName();
Object principal = authentication.getPrincipal();
Collection extends GrantedAuthority> authorities =
authentication.getAuthorities();
默認(rèn)情況下,SecurityContextHolder使用一個(gè)ThreadLocal對(duì)象用于存儲(chǔ)用戶的詳細(xì)認(rèn)證信息,這也就意味著即時(shí)當(dāng)前SecurityContext對(duì)象沒有作為一個(gè)參數(shù)傳遞到具體的方法里去,同一個(gè)線程中的任意方法都能拿到SecurityContext對(duì)象,進(jìn)而拿到用戶的認(rèn)證信息。如果在當(dāng)前主體的請(qǐng)求被處理后清除線程程,以這種方式使用ThreadLocal是非常安全的。Spring Security的 FilterChainProxy(過(guò)濾鏈代理)確保了 SecurityContext永遠(yuǎn)是干凈的。
5.2 認(rèn)識(shí)SecurityContextHolder類中的SecurityContextHolderStrategy
由于一些應(yīng)用與線程特殊的工作方式,并非所有的應(yīng)用都完全適合使用ThreadLocal對(duì)象來(lái)存儲(chǔ)安全上下文。例如對(duì)于一個(gè)Swing客戶端應(yīng)用就要求虛擬機(jī)種所有線程共享一個(gè)安全上下文對(duì)象,這種情況修啊需要選擇全局策略。
SecurityContextHolder一共由三種方式存儲(chǔ)SecurityContext對(duì)象,可以通過(guò)在應(yīng)用啟動(dòng)前調(diào)用方法SecurityContextHolder.setStrategyName(String strategyName)方法進(jìn)行設(shè)置。三種策略模式分別是
- SecurityContextHolder.MODE_GLOBAL: 全局模式,適用于單個(gè)應(yīng)用要求虛擬機(jī)中所有線程要求共享一個(gè)安全上下文的場(chǎng)景
- SecurityContextHolder.MODE_INHERITABLETHREADLOCAL:繼承本地線程模式
- SecurityContextHolder.MODE_THREADLOCAL: ?本地線程模式
SecurityContextHolder.java
public通過(guò)閱讀SecurityContextHolder類中的關(guān)鍵源碼,可以看出SecurityContextHolder類首先通過(guò)系統(tǒng)變量名spring.security.strategy從系統(tǒng)屬性中獲取strategyName,并在初始化方法中根據(jù)strategyName去實(shí)例化strategy屬性。在初始化方法中,首先判斷strategyName變量是否為空,為空的化就使用MODE_THREADLOCAL模式,然后根據(jù)strategyName的值去構(gòu)建不同的SecurityContextHolderStrategy實(shí)現(xiàn)類實(shí)例。MODE_THREADLOCAL模式對(duì)應(yīng)ThreadLocalSecurityContextHolderStrategy類實(shí)例;MODE_INHERITABLETHREADLOCAL模式對(duì)應(yīng)InheritableThreadLocalSecurityContextHolderStrategy類實(shí)例;MODE_GLOBAL對(duì)應(yīng)GlobalSecurityContextHolderStrategy類實(shí)例。而SecurityContextHolder類的三個(gè)重要的靜態(tài)方法getContext、setContext和createEmptyContext其實(shí)都是委托給strategy來(lái)操作的。
通過(guò)閱讀ThreadLocalSecurityContextHolderStrategy類的源碼,我們也可以看到SecurityContext確實(shí)是保存在了一個(gè)ThreadLocal對(duì)象中的泛型變量中。
final大多數(shù)應(yīng)用場(chǎng)景下,我們無(wú)需改變默認(rèn)的strategyName的值,默認(rèn)使用ThreadLocal存儲(chǔ)當(dāng)前登錄用戶的認(rèn)證信息即可。
本文代碼晚點(diǎn)我會(huì)提交到gitee 個(gè)人倉(cāng)庫(kù),地址:https://gitee.com/heshengfu1211/boot-demo.git
感興趣的小伙伴可以克隆下來(lái)參考完整的代碼
6 參考文章
[1]?https://docs.spring.io/spring-security/site/docs/5.4.1/reference/html5/#servlet-authentication
7 推薦閱讀
[1]?Spring Security 入門(一)Spring Security中的認(rèn)證與密碼編碼器
[2]?Spring Security入門(二) 基于內(nèi)存存儲(chǔ)的表單登錄實(shí)戰(zhàn)
[3]?SpringBoot之路(二)使用用Spring-Data-JPA訪問(wèn)數(shù)據(jù)庫(kù)進(jìn)行基本的CRUD操作
[4]?SpringBoot之路(四)Spring-Data-Jpa中的高級(jí)應(yīng)用
初次閱讀作者文章的讀者歡迎點(diǎn)擊文章標(biāo)題下方的藍(lán)色字體“碼農(nóng)的進(jìn)階之路2020”或者掃描下方二維碼加個(gè)關(guān)注,筆者會(huì)定期更新java后端與web前端的記技術(shù)文干貨文章。
讀者對(duì)本文有任何疑問(wèn)可在下面的留言板中留言,我看到后會(huì)及時(shí)回復(fù)? ? ? ? ? ? ? ? ? ? ??本文留言討論區(qū)??
? ? ? ? ? ? ? ? ? ? ? ---END---
總結(jié)
以上是生活随笔為你收集整理的idea查询类_Spring Security入门(三): 基于自定义数据库查询的认证实战的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: python中回车怎么表示_如何在pyt
- 下一篇: gradle idea java ssm