米拓建設(shè)網(wǎng)站合肥做網(wǎng)絡(luò)推廣的公司
文章目錄
- 一、Shiro概述
- 1、Shiro簡(jiǎn)介
- 1.1 介紹
- 1.2 Shiro特點(diǎn)
- 2、Shiro與SpringSecurity的對(duì)比
- 3、Shiro基本功能
- 4、Shiro原理
- 4.1 Shiro 架構(gòu)(外部)
- 4.2 shiro架構(gòu)(內(nèi)部)
- 二、Shiro基本使用
- 1、環(huán)境準(zhǔn)備
- 2、登錄認(rèn)證
- 2.1 登錄認(rèn)證概念
- 2.2 登錄認(rèn)證基本流程
- 2.3 登錄認(rèn)證實(shí)例
- 2.4 身份認(rèn)證源碼流程
- 3、角色與授權(quán)
- 3.1 授權(quán)概念
- 3.2 授權(quán)方式
- 3.3 授權(quán)流程
- 3.4 代碼實(shí)例
- 4、Shiro加密
- 5、自定義登陸認(rèn)證
- 三、Shiro整合Springboot
- 1、登錄認(rèn)證準(zhǔn)備
- 1.1 環(huán)境準(zhǔn)備
- 1.2 后端整合
- 1.3 前端thymeleaf整合
- 2、多 realm 認(rèn)證策略
- 2.1 實(shí)現(xiàn)原理
- 2.2 配置修改
- 3、remember me
- 3.1 實(shí)現(xiàn)原理
- 3.2 代碼實(shí)現(xiàn)
- 4、用戶注銷
- 5、授權(quán)、角色認(rèn)證
- 5.1 后端接口服務(wù)注解
- 5.2 授權(quán)驗(yàn)證-獲取角色驗(yàn)證
- 5.3 授權(quán)驗(yàn)證-獲取權(quán)限驗(yàn)證
- 5.4 權(quán)限驗(yàn)證異常處理類
- 5.5 前端頁(yè)面授權(quán)驗(yàn)證
- 6、緩存管理
- 6.1 緩存工具EhCache
- 6.2 Ehcache簡(jiǎn)單搭建
- 6.3 Shiro整合EhCache
- 7、會(huì)話管理
- 7.1 SessionManager
- 7.2 會(huì)話管理實(shí)現(xiàn)
- 7.3 獲得session方式
一、Shiro概述
1、Shiro簡(jiǎn)介
1.1 介紹
官網(wǎng):https://shiro.apache.org/
Apache Shiro 是一個(gè)功能強(qiáng)大且易于使用的 Java 安全(權(quán)限)框架。Shiro 可以完成:認(rèn)證、授權(quán)、加密、會(huì)話管理、與 Web 集成、緩存 等。借助 Shiro 您可以快速輕松地保護(hù)任何應(yīng)用程序——從最小的移動(dòng)應(yīng)用程序到最大的 Web 和企業(yè)應(yīng)用程序。
1.2 Shiro特點(diǎn)
- 易于使用:使用 Shiro 構(gòu)建系統(tǒng)安全框架非常簡(jiǎn)單。就算第一次接觸也可以快速掌握
- 全面:Shiro 包含系統(tǒng)安全框架需要的功能,滿足安全需求的“一站式服務(wù)”
- 靈活:Shiro 可以在任何應(yīng)用程序環(huán)境中工作。雖然它可以在 Web、EJB 和 IoC 環(huán)境中工作,但不需要依賴它們。Shiro 也沒有強(qiáng)制要求任何規(guī)范,甚至沒有很多依賴項(xiàng)
- 強(qiáng)力支持 Web:Shiro 具有出色的 Web 應(yīng)用程序支持,可以基于應(yīng)用程序 URL 和 Web 協(xié)議(例如 REST)創(chuàng)建靈活的安全策略,同時(shí)還提供一組 JSP 庫(kù)來(lái)控制頁(yè)面輸出
- 兼容性強(qiáng):Shiro 的設(shè)計(jì)模式使其易于與其他框架和應(yīng)用程序集成。Shiro 與 Spring、Grails、Wicket、Tapestry、Mule、Apache Camel、Vaadin 等框架無(wú)縫集成
- 社區(qū)支持:Shiro 是 Apache 軟件基金會(huì)的一個(gè)開源項(xiàng)目,有完備的社區(qū)支持,文檔支持。如果需要,像 Katasoft 這樣的商業(yè)公司也會(huì)提供專業(yè)的支持和服務(wù)
2、Shiro與SpringSecurity的對(duì)比
SpringSecurity參考:Spring Security學(xué)習(xí)筆記
- Spring Security 基于 Spring 開發(fā),項(xiàng)目若使用 Spring 作為基礎(chǔ),配合 Spring Security 做權(quán)限更加方便,而 Shiro 需要和 Spring 進(jìn)行整合開發(fā);
- Spring Security 功能比 Shiro 更加豐富些,例如安全維護(hù)方面;
- Spring Security 社區(qū)資源相對(duì)比 Shiro 更加豐富;
- Shiro 的配置和使用比較簡(jiǎn)單,Spring Security 上手復(fù)雜些;
- Shiro 依賴性低,不需要任何框架和容器,可以獨(dú)立運(yùn)行。Spring Security 依賴 Spring 容器;
- shiro 不僅僅可以使用在 web 中,它可以工作在任何應(yīng)用環(huán)境中。在集群會(huì)話時(shí) Shiro 最重要的一個(gè)好處或許就是它的會(huì)話是獨(dú)立于容器的
3、Shiro基本功能
- Authentication:身份認(rèn)證/登錄,驗(yàn)證用戶是不是擁有相應(yīng)的身份;
- Authorization:授權(quán),即權(quán)限驗(yàn)證,驗(yàn)證某個(gè)已認(rèn)證的用戶是否擁有某個(gè)權(quán)限;即判斷用 戶是否能進(jìn)行什么操作,如:驗(yàn)證某個(gè)用戶是否擁有某個(gè)角色?;蛘呒?xì)粒度的驗(yàn)證某個(gè)用戶 對(duì)某個(gè)資源是否具有某個(gè)權(quán)限;
- Session Manager:會(huì)話管理,即用戶登錄后就是一次會(huì)話,在沒有退出之前,它的所有信息都在會(huì)話中;會(huì)話可以是普通 JavaSE 環(huán)境,也可以是 Web 環(huán)境的;
- Cryptography:加密,保護(hù)數(shù)據(jù)的安全性,如密碼加密存儲(chǔ)到數(shù)據(jù)庫(kù),而不是明文存儲(chǔ);
- Web Support:Web 支持,可以非常容易的集成到 Web 環(huán)境;
- Caching:緩存,比如用戶登錄后,其用戶信息、擁有的角色/權(quán)限不必每次去查,這樣可以提高效率;
- Concurrency:Shiro 支持多線程應(yīng)用的并發(fā)驗(yàn)證,即如在一個(gè)線程中開啟另一個(gè)線程,能把權(quán)限自動(dòng)傳播過(guò)去;
- Testing:提供測(cè)試支持;
- Run As:允許一個(gè)用戶假裝為另一個(gè)用戶(如果他們?cè)试S)的身份進(jìn)行訪問(wèn);
- Remember Me:記住我,這個(gè)是非常常見的功能,即一次登錄后,下次再來(lái)的話不用登錄了
4、Shiro原理
4.1 Shiro 架構(gòu)(外部)
從外部來(lái)看 Shiro ,即從應(yīng)用程序角度的來(lái)觀察如何使用Shiro 完成工作
Subject
:應(yīng)用代碼直接交互的對(duì)象是 Subject,也就是說(shuō) Shiro 的對(duì)外 API 核心 就是 Subject。Subject 代表了當(dāng)前“用戶”, 這個(gè)用戶不一定 是一個(gè)具體的人,與當(dāng)前應(yīng)用交互的任何東西都是 Subject,如網(wǎng)絡(luò)爬蟲, 機(jī)器人等;與 Subject 的所有交互都會(huì)委托給 SecurityManager; Subject 其實(shí)是一個(gè)門面,SecurityManager 才是實(shí)際的執(zhí)行者;SecurityManager
:安全管理器;即所有與安全有關(guān)的操作都會(huì)與 SecurityManager交互;且其管理著所有 Subject;可以看出它是** Shiro 的核心**,它負(fù)責(zé)與 Shiro 的其他組件進(jìn)行交互,它相當(dāng)于 SpringMVC 中 DispatcherServlet 的角色Realm
:Shiro 從 Realm 獲取安全數(shù)據(jù)(如用戶、角色、權(quán)限),就是說(shuō)SecurityManager 要驗(yàn)證用戶身份,那么它需要從 Realm 獲取相應(yīng)的用戶 進(jìn)行比較以確定用戶身份是否合法;也需要從 Realm 得到用戶相應(yīng)的角色/ 權(quán)限進(jìn)行驗(yàn)證用戶是否能進(jìn)行操作;可以把 Realm 看成 DataSource
4.2 shiro架構(gòu)(內(nèi)部)
Subject
:任何可以與應(yīng)用交互的“用戶”;SecurityManager
:相當(dāng)于 SpringMVC 中的 DispatcherServlet;是 Shiro 的心臟; 所有具體的交互都通過(guò) SecurityManager 進(jìn)行控制;它管理著所有 Subject、且負(fù)責(zé)進(jìn) 行認(rèn)證、授權(quán)、會(huì)話及緩存的管理。Authenticator
:負(fù)責(zé) Subject 認(rèn)證,是一個(gè)擴(kuò)展點(diǎn),可以自定義實(shí)現(xiàn);可以使用認(rèn)證策略(Authentication Strategy),即什么情況下算用戶認(rèn)證通過(guò)了;Authorizer
:授權(quán)器、即訪問(wèn)控制器,用來(lái)決定主體是否有權(quán)限進(jìn)行相應(yīng)的操作;即控制著用戶能訪問(wèn)應(yīng)用中的哪些功能;Realm
:可以有 1 個(gè)或多個(gè) Realm,可以認(rèn)為是安全實(shí)體數(shù)據(jù)源,即用于獲取安全實(shí)體的;可以是 JDBC 實(shí)現(xiàn),也可以是內(nèi)存實(shí)現(xiàn)等等;由用戶提供;所以一般在應(yīng)用中都需要實(shí)現(xiàn)自己的 Realm;SessionManager
:管理 Session 生命周期的組件;而 Shiro 并不僅僅可以用在 Web環(huán)境,也可以用在如普通的 JavaSE 環(huán)境CacheManager
:緩存控制器,來(lái)管理如用戶、角色、權(quán)限等的緩存的;因?yàn)檫@些數(shù)據(jù) 基本上很少改變,放到緩存中后可以提高訪問(wèn)的性能Cryptography
:密碼模塊,Shiro 提高了一些常見的加密組件用于如密碼加密/解密。
二、Shiro基本使用
1、環(huán)境準(zhǔn)備
引入shiro依賴
<dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-core</artifactId><version>1.9.0</version>
</dependency><dependency><groupId>commons-logging</groupId><artifactId>commons-logging</artifactId><version>1.2</version>
</dependency>
創(chuàng)建ini文件,因?yàn)镾hiro獲取權(quán)限相關(guān)信息可以通過(guò)數(shù)據(jù)庫(kù)獲取,也可以通過(guò)ini配置文件獲取,后期放數(shù)據(jù)庫(kù)
[users]
shawn=123456
leo=123456
2、登錄認(rèn)證
2.1 登錄認(rèn)證概念
- 身份驗(yàn)證:一般需要提供如身份ID等一些標(biāo)識(shí)信息來(lái)表明登錄者的身份,如提供email,用戶名/密碼來(lái)證明
- 在shiro中,用戶需要提供**principals(身份)**和credentials(證明)給shiro,從而應(yīng)用能驗(yàn)證用戶身份
- principals:身份,即主體的標(biāo)識(shí)屬性,可以是任何屬性,如用戶名、郵箱等,唯一即可。一個(gè)主體可以有多個(gè)principals,但只有一個(gè)Primary principals,一般是用戶名/郵箱/手機(jī)號(hào)
- credentials:證明/憑證,即只有主體知道的安全值,如密碼/數(shù)字證書等。
- 最常見的principals和credentials組合就是用戶名/密碼
2.2 登錄認(rèn)證基本流程
- 收集用戶身份/憑證,即如用戶名/密碼
- 調(diào)用
Subject.login
進(jìn)行登錄,如果失敗將得到相應(yīng) 的AuthenticationException
異常,根據(jù)異常提示用戶錯(cuò)誤信息;否則登錄成功 - 創(chuàng)建自定義的
Realm
類,繼承org.apache.shiro.realm.AuthenticatingRealm
類,實(shí)現(xiàn)doGetAuthenticationInfo()
方法
2.3 登錄認(rèn)證實(shí)例
- 初始化獲取SecurityManager
- 獲取subject對(duì)象
- 創(chuàng)建token對(duì)象,web應(yīng)用用戶名密碼從頁(yè)面?zhèn)鬟f
- 完成登錄
public static void main(String[] args) {//1初始化獲取SecurityManagerFactory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");SecurityManager securityManager = factory.getInstance();SecurityUtils.setSecurityManager(securityManager);//2獲取Subject對(duì)象Subject subject = SecurityUtils.getSubject();//3創(chuàng)建token對(duì)象,web應(yīng)用用戶名密碼從頁(yè)面?zhèn)鬟fAuthenticationToken token = new UsernamePasswordToken("shawn","123456");//4完成登錄try {subject.login(token);System.out.println("登錄成功");}catch (UnknownAccountException e) {e.printStackTrace();System.out.println("用戶不存在");}catch (IncorrectCredentialsException e) {e.printStackTrace();System.out.println("密碼錯(cuò)誤");}catch (AuthenticationException e) {e.printStackTrace();}}
2.4 身份認(rèn)證源碼流程
- 首先調(diào)用
Subject.login(token)
進(jìn)行登錄,其會(huì)自動(dòng)委托給SecurityManager
SecurityManager
負(fù)責(zé)真正的身份驗(yàn)證邏輯;它會(huì)委托給Authenticator
進(jìn)行身份驗(yàn)證- Authenticator 才是真正的身份驗(yàn)證者,Shiro API 中核心的身份 認(rèn)證入口點(diǎn),此處可以自定義插入自己的實(shí)現(xiàn);
- Authenticator 可能會(huì)委托給相應(yīng)的
AuthenticationStrategy
進(jìn) 行多 Realm 身份驗(yàn)證,默認(rèn)ModularRealmAuthenticator
會(huì)調(diào)用AuthenticationStrategy
進(jìn)行多 Realm 身份驗(yàn)證; - Authenticator 會(huì)把相應(yīng)的 token 傳入 Realm,從 Realm 獲取 身份驗(yàn)證信息,如果沒有返回/拋出異常表示身份驗(yàn)證失敗了。此處可以配置多個(gè)Realm,將按照相應(yīng)的順序及策略進(jìn)行訪問(wèn)。
3、角色與授權(quán)
3.1 授權(quán)概念
- 授權(quán),也叫訪問(wèn)控制,即在應(yīng)用中控制誰(shuí)訪問(wèn)哪些資源(如訪問(wèn)頁(yè)面/編輯數(shù)據(jù)/頁(yè)面 操作等)。在授權(quán)中需了解的幾個(gè)關(guān)鍵對(duì)象:
主體(Subject)
、資源(Resource)
、權(quán)限 (Permission)
、角色(Role)
- 主體(Subject):訪問(wèn)應(yīng)用的用戶,在 Shiro 中使用 Subject 代表該用戶。用戶只有授權(quán) 后才允許訪問(wèn)相應(yīng)的資源
- 資源(Resource):在應(yīng)用中用戶可以訪問(wèn)的 URL,比如訪問(wèn) JSP 頁(yè)面、查看/編輯 某些 數(shù)據(jù)、訪問(wèn)某個(gè)業(yè)務(wù)方法、打印文本等等都是資源。用戶只要授權(quán)后才能訪問(wèn)
- 權(quán)限(Permission):安全策略中的原子授權(quán)單位,通過(guò)權(quán)限我們可以表示在應(yīng)用中用戶 有沒有操作某個(gè)資源的權(quán)力。即權(quán)限表示在應(yīng)用中用戶能不能訪問(wèn)某個(gè)資源,如:訪問(wèn)用 戶列表頁(yè)面查看/新增/修改/刪除用戶數(shù)據(jù)(即很多時(shí)候都是CRUD(增查改刪)式權(quán)限控 制)等。權(quán)限代表了用戶有沒有操作某個(gè)資源的權(quán)利,即反映在某個(gè)資源上的操作允不允 許。
- Shiro 支持粗粒度權(quán)限(如用戶模塊的所有權(quán)限)和細(xì)粒度權(quán)限(操作某個(gè)用戶的權(quán)限, 即實(shí)例級(jí)別的)
- 角色(Role):
權(quán)限的集合
,一般情況下會(huì)賦予用戶角色而不是權(quán)限,即這樣用戶可以擁有 一組權(quán)限,賦予權(quán)限時(shí)比較方便。典型的如:項(xiàng)目經(jīng)理、技術(shù)總監(jiān)、CTO、開發(fā)工程師等 都是角色,不同的角色擁有一組不同的權(quán)限
3.2 授權(quán)方式
//編程式
subject.hasRole("admin")
//注解式
@RequiresRoles("admin")
// JSP/GSP 標(biāo)簽
<shiro:hasRole name="admin">
</shiro:hasRole>
3.3 授權(quán)流程
- 首先調(diào)用Subject.isPermitted的
/hasRole
接口,其會(huì)委托給SecurityManager
,而SecurityManager接著會(huì)委托給 Authorizer; - Authorizer是真正的授權(quán)者,如果調(diào)用如isPermitted(“user:view”),其首先會(huì)通過(guò)PermissionResolver把字符串轉(zhuǎn)換成相應(yīng)的Permission實(shí)例;
- 在進(jìn)行授權(quán)之前,其會(huì)調(diào)用相應(yīng)的Realm獲取Subject相應(yīng)的角色/權(quán)限用于匹配傳入的角色/權(quán)限;
- Authorizer會(huì)判斷Realm的角色/權(quán)限是否和傳入的匹配,如果有多個(gè)Realm,會(huì)委托給ModularRealmAuthorizer進(jìn)行循環(huán)判斷,如果匹配如isPermitted
/hasRole
會(huì)返回 true,否則返回false表示授權(quán)失敗
3.4 代碼實(shí)例
首先修改resource下的shiro.ini
文件
[users]
zhangsan=7174f64b13022acd3c56e2781e098a5f
shawn=123456,role1,role2[roles]
role1=user:insert,user:select
修改主函數(shù)進(jìn)行測(cè)試
public static void main(String[] args) {//1初始化獲取SecurityManagerDefaultSecurityManager securityManager=new DefaultSecurityManager();IniRealm iniRealm=new IniRealm("classpath:shiro.ini");securityManager.setRealm(iniRealm);// 其中 shiro.ini 在 resources 的根目錄下,此方法已經(jīng)過(guò)期// Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");// SecurityManager securityManager = factory.getInstance();SecurityUtils.setSecurityManager(securityManager);//2獲取Subject對(duì)象Subject subject = SecurityUtils.getSubject();//3創(chuàng)建token對(duì)象,web應(yīng)用用戶名密碼從頁(yè)面?zhèn)鬟fAuthenticationToken token = new UsernamePasswordToken("shawn","123456");//4完成登錄try {subject.login(token);System.out.println("登錄成功");//5判斷角色boolean hasRole = subject.hasRole("role1");System.out.println("是否擁有此角色 = " + hasRole);//6判斷權(quán)限boolean permitted = subject.isPermitted("user:insert1111");System.out.println("是否擁有此權(quán)限 = " + permitted);//也可以用checkPermission方法,但沒有返回值,沒權(quán)限拋AuthenticationExceptionsubject.checkPermission("user:select1111");}catch (UnknownAccountException e) {e.printStackTrace();System.out.println("用戶不存在");}catch (IncorrectCredentialsException e) {e.printStackTrace();System.out.println("密碼錯(cuò)誤");}catch (AuthenticationException e) {e.printStackTrace();}
}
4、Shiro加密
實(shí)際系統(tǒng)開發(fā)中,一些敏感信息需要進(jìn)行加密,比如說(shuō)用戶的密碼。Shiro 內(nèi)嵌很多 常用的加密算法,比如 MD5 加密。Shiro 可以很簡(jiǎn)單的使用信息加密。
public static void main(String[] args) {//密碼明文String password = "shawn";//使用md5加密Md5Hash md5Hash = new Md5Hash(password);System.out.println("md5加密 = " + md5Hash.toHex());//帶鹽的md5加密,鹽就是在密碼明文后拼接新字符串,然后再進(jìn)行加密Md5Hash md5Hash2 = new Md5Hash(password,"salt");System.out.println("帶鹽的md5加密 = " + md5Hash2.toHex());//為了保證安全,避免被破解還可以多次迭代加密,保證數(shù)據(jù)安全Md5Hash md5Hash3 = new Md5Hash(password,"salt",3);System.out.println("md5帶鹽的3次加密 = " + md5Hash3.toHex());//使用父類進(jìn)行加密SimpleHash simpleHash = new SimpleHash("MD5",password,"salt",3);System.out.println("父類帶鹽的3次加密 = " + simpleHash.toHex());}
5、自定義登陸認(rèn)證
Shiro 默認(rèn)的登錄認(rèn)證是不帶加密的,如果想要實(shí)現(xiàn)加密認(rèn)證需要自定義登錄認(rèn)證, 自定義 Realm;首先是創(chuàng)建自定義Realm
public class MyRealm extends AuthenticatingRealm {//自定義登錄認(rèn)證方法,shiro的login方法底層會(huì)調(diào)用該類的認(rèn)證方法進(jìn)行認(rèn)證//需要配置自定義的realm生效,在ini文件中配置,在Springboot中配置//該方法只是獲取進(jìn)行對(duì)比的信息,認(rèn)證邏輯還是按照shiro底層認(rèn)證邏輯完成@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {//1獲取身份信息String principal = authenticationToken.getPrincipal().toString();//2獲取憑證信息String password = new String((char[])authenticationToken.getCredentials());System.out.println("認(rèn)證用戶信息:"+principal+"---"+password);//3獲取數(shù)據(jù)庫(kù)中存儲(chǔ)的用戶信息if(principal.equals("shawn")){//3.1數(shù)據(jù)庫(kù)中存儲(chǔ)的加鹽3次迭代的密碼// 第二種方法密碼,需要自己進(jìn)行加密
// String pwdInfo = "123456 ";// 第一種方法String pwdInfo = "d1b129656359e35e95ebd56a63d7b9e0";//4創(chuàng)建封裝校驗(yàn)邏輯對(duì)象,封裝數(shù)據(jù)返回AuthenticationInfo info = new SimpleAuthenticationInfo(authenticationToken.getPrincipal(),pwdInfo,ByteSource.Util.bytes("salt"),authenticationToken.getPrincipal().toString());return info;}return null;}
}
第一種方法
public static void main(String[] args) {//1初始化獲取SecurityManagerFactory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");SecurityManager securityManager = factory.getInstance();SecurityUtils.setSecurityManager(securityManager);//2獲取Subject對(duì)象Subject subject = SecurityUtils.getSubject();//3創(chuàng)建token對(duì)象,web應(yīng)用用戶名密碼從頁(yè)面?zhèn)鬟fAuthenticationToken token = new UsernamePasswordToken("shawn","123456");//4完成登錄try {subject.login(token);System.out.println("登錄成功");//5判斷角色boolean hasRole = subject.hasRole("role1");System.out.println("是否擁有此角色 = " + hasRole);//6判斷權(quán)限boolean permitted = subject.isPermitted("user:insert1111");System.out.println("是否擁有此權(quán)限 = " + permitted);//也可以用checkPermission方法,但沒有返回值,沒權(quán)限拋AuthenticationExceptionsubject.checkPermission("user:select1111");}catch (UnknownAccountException e) {e.printStackTrace();System.out.println("用戶不存在");}catch (IncorrectCredentialsException e) {e.printStackTrace();System.out.println("密碼錯(cuò)誤");}catch (AuthenticationException e) {e.printStackTrace();}
}
同時(shí)在Ini文件添加
[main]
md5CredentialsMatcher=org.apache.shiro.authc.credential.Md5CredentialsMatcher
md5CredentialsMatcher.hashIterations=3myrealm=com.atguigu.shirotest.MyRealm
myrealm.credentialsMatcher=$md5CredentialsMatcher
securityManager.realms=$myrealm
第二種方法,新的方法
public static void main(String[] args) {//1初始化獲取SecurityManagerDefaultSecurityManager securityManager=new DefaultSecurityManager(new MyRealm());SecurityUtils.setSecurityManager(securityManager);//2獲取Subject對(duì)象Subject subject = SecurityUtils.getSubject();//3創(chuàng)建token對(duì)象,web應(yīng)用用戶名密碼從頁(yè)面?zhèn)鬟fAuthenticationToken token = new UsernamePasswordToken("shawn","123456");//4完成登錄try {subject.login(token);System.out.println("登錄成功");//5判斷角色boolean hasRole = subject.hasRole("role1");System.out.println("是否擁有此角色 = " + hasRole);//6判斷權(quán)限boolean permitted = subject.isPermitted("user:insert1111");System.out.println("是否擁有此權(quán)限 = " + permitted);//也可以用checkPermission方法,但沒有返回值,沒權(quán)限拋AuthenticationExceptionsubject.checkPermission("user:select1111");}catch (UnknownAccountException e) {e.printStackTrace();System.out.println("用戶不存在");}catch (IncorrectCredentialsException e) {e.printStackTrace();System.out.println("密碼錯(cuò)誤");}catch (AuthenticationException e) {e.printStackTrace();}
}
三、Shiro整合Springboot
1、登錄認(rèn)證準(zhǔn)備
1.1 環(huán)境準(zhǔn)備
首先創(chuàng)建SpringBoot腳手架項(xiàng)目,導(dǎo)入依賴
<dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-spring-boot-web-starter</artifactId><version>1.9.0</version>
</dependency><!--mybatis-plus-->
<dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.2</version>
</dependency><!--mysql-->
<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.30</version>
</dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId>
</dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!--配置Thymeleaf與Shrio的整合依賴-->
<dependency><groupId>com.github.theborakompanioni</groupId><artifactId>thymeleaf-extras-shiro</artifactId><version>2.0.0</version>
</dependency><!--Shiro整合EhCache-->
<dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-ehcache</artifactId><version>1.10.0</version>
</dependency>
<dependency><groupId>commons-io</groupId><artifactId>commons-io</artifactId><version>2.11.0</version>
</dependency>
Mysql腳本,配置好mysql
CREATE DATABASE IF NOT EXISTS `shirodb` CHARACTER SET utf8mb4;
USE `shirodb`;
CREATE TABLE `user` (`id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '編號(hào)',`name` VARCHAR(30) DEFAULT NULL COMMENT '用戶名',`pwd` VARCHAR(50) DEFAULT NULL COMMENT '密碼',`rid` BIGINT(20) DEFAULT NULL COMMENT '角色編號(hào)',
PRIMARY KEY(`id`)
) ENGINE = INNODB AUTO_INCREMENT = 2 DEFAULT CHARSET = utf8 COMMENT = '用戶表';
配置application.yaml文件
mybatis-plus:configuration:log-impl: org.apache.ibatis.logging.stdout.StdOutImplmapper-locations: classpath:mapper/*.xml
spring:datasource:type: com.zaxxer.hikari.HikariDataSourcedriver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/shirodb?characterEncoding=utf-8&useSSL=falseusername: rootpassword: rootjackson:date-format: yyyy-MM-dd HH:mm:sstime-zone: GMT+8
# 未認(rèn)證的請(qǐng)求重定向地址
shiro:loginUrl: /myController/login
1.2 后端整合
創(chuàng)建各個(gè)模塊和類
// 實(shí)體類
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {private Integer id;private String name;private String pwd;private Integer rid;
}// Mapper層
@Mapper
public interface UserMapper extends BaseMapper<User> {}// Service層,接口類自己定義(方法抽取一下即可)
@Service
public class UserServiceImpl implements UserService {@Autowiredprivate UserMapper userMapper;@Overridepublic User getUserInfoByName(String name) {QueryWrapper<User> wrapper = new QueryWrapper<>();wrapper.eq("name",name);User user = userMapper.selectOne(wrapper);return user;}
}// controller層
@Controller
@RequestMapping("myController")
public class MyController {/*** 登錄* @param username 用戶名* @param password 密碼* @return {@link String}*/@GetMapping("userLogin")@ResponseBodypublic String login(@RequestParam("username") String username, @RequestParam("password") String password){// 默認(rèn)30分鐘過(guò)期//永不過(guò)期,在登陸最開始加上//SecurityUtils.getSubject().getSession().setTimeout(-1000L);//其他時(shí)間 單位毫秒//SecurityUtils.getSubject().getSession().setTimeout(1800000);Subject subject = SecurityUtils.getSubject();UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(username, password);try{subject.login(usernamePasswordToken);return "success";}catch (Exception e) {e.printStackTrace();return "error";}}
}
創(chuàng)建自定義realm
@Component
public class MyRealm extends AuthorizingRealm {@Autowiredprivate UserService userService;/*** 自定義授權(quán)** @param principals 權(quán)限* @return {@link AuthorizationInfo}*/@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {return null;}/*** 自定義身份驗(yàn)證** @param token 令牌* @return {@link AuthenticationInfo}* @throws AuthenticationException 身份驗(yàn)證異常*/@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {//1. 獲取用戶身份信息String name = token.getPrincipal().toString();//2. 調(diào)用業(yè)務(wù)層獲取用戶信息User user = userService.getUserInfoByName(name);//3. 非空判斷,將數(shù)據(jù)封裝返回if (user != null) {return new SimpleAuthenticationInfo(token.getPrincipal(),user.getPwd(),ByteSource.Util.bytes("salt"),name);}return null;}
}
創(chuàng)建配置類
@Configuration
public class ShiroConfig {@Autowiredprivate MyRealm myRealm;//配置SecurityManager@Beanpublic DefaultWebSecurityManager defaultWebSecurityManager(){//1創(chuàng)建defaultWebSecurityManager 對(duì)象DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();//2創(chuàng)建加密對(duì)象,設(shè)置相關(guān)屬性HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();//2.1采用md5加密matcher.setHashAlgorithmName("md5");//2.2迭代加密次數(shù)matcher.setHashIterations(3);//3將加密對(duì)象存儲(chǔ)到myRealm中myRealm.setCredentialsMatcher(matcher);//4將myRealm存入defaultWebSecurityManager 對(duì)象defaultWebSecurityManager.setRealm(myRealm);//5返回return defaultWebSecurityManager;}//配置Shiro內(nèi)置過(guò)濾器攔截范圍@Beanpublic DefaultShiroFilterChainDefinition shiroFilterChainDefinition(){DefaultShiroFilterChainDefinition definition = new DefaultShiroFilterChainDefinition();//設(shè)置不認(rèn)證可以訪問(wèn)的資源definition.addPathDefinition("/myController/userLogin","anon");definition.addPathDefinition("/myController/login","anon");//設(shè)置需要進(jìn)行登錄認(rèn)證的攔截范圍definition.addPathDefinition("/**","authc");return definition;}
}
1.3 前端thymeleaf整合
引入thymeleaf依賴后,在resource/templares
創(chuàng)建login.html
和main.html
<!--login.html-->
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<h1>Shiro 登錄認(rèn)證</h1>
<br>
<form action="/myController/userLogin"><div>用戶名:<input type="text" name="username" value=""></div><div>密碼:<input type="password" name="password" value=""></div><div><input type="submit" value="登錄"></div>
</form>
</body>
</html><!--main.html-->
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<h1><br>Shiro 登錄認(rèn)證后主頁(yè)面</h1>登錄用戶為: <span th:text="${session.user}"></span>
</body>
改造controller,讓其返回走視圖處理器,去除@ResponseBody
@Controller
@RequestMapping("myController")
public class MyController {//跳轉(zhuǎn)登錄頁(yè)面@GetMapping("login")public String login(){return "login";}@GetMapping("userLogin")public String login(@RequestParam("username") String username, @RequestParam("password") String password,HttpSession session){//1 獲取 Subject 對(duì)象Subject subject = SecurityUtils.getSubject();//2 封裝請(qǐng)求數(shù)據(jù)到 token 對(duì)象中AuthenticationToken token = new UsernamePasswordToken(username,password);//3 調(diào)用 login 方法進(jìn)行登錄認(rèn)證try {subject.login(token);session.setAttribute("user",token.getPrincipal().toString());return "main";} catch (AuthenticationException e) {e.printStackTrace();System.out.println("登錄失敗");return "登錄失敗";}}}
2、多 realm 認(rèn)證策略
2.1 實(shí)現(xiàn)原理
當(dāng)應(yīng)用程序配置多個(gè) Realm 時(shí),例如:用戶名密碼校驗(yàn)、手機(jī)號(hào)驗(yàn)證碼校驗(yàn)等等。 Shiro 的 ModularRealmAuthenticator
會(huì)使用內(nèi)部的AuthenticationStrategy
組件判斷認(rèn)證是成功還是失敗。
AuthenticationStrategy
是一個(gè)無(wú)狀態(tài)的組件,它在身份驗(yàn)證嘗試中被詢問(wèn) 4 次(這 4 次交互所需的任何必要的狀態(tài)將被作為方法參數(shù)):
- 在所有 Realm 被調(diào)用之前
- 在調(diào)用 Realm 的 getAuthenticationInfo 方法之前
- 在調(diào)用 Realm 的 getAuthenticationInfo 方法之后
- 在所有 Realm 被調(diào)用之后
認(rèn)證策略的另外一項(xiàng)工作就是聚合所有 Realm 的結(jié)果信息封裝至一個(gè)AuthenticationInfo 實(shí)例中,并將此信息返回,以此作為 Subject 的身份信息。Shiro 中定義了 3 種認(rèn)證策略的實(shí)現(xiàn):
認(rèn)證策略類 | 描述 |
---|---|
AtLeastOneSuccessfulStrategy | 只要有一個(gè)(或更多)的 Realm 驗(yàn)證成功,那么認(rèn)證將視為成功 |
FirstSuccessfulStrategy | 第一個(gè) Realm 驗(yàn)證成功,整體認(rèn)證將視為成功,且后續(xù) Realm 將被忽略 |
AllSuccessfulStrategy | 所有 Realm 成功,認(rèn)證才視為成功 |
ModularRealmAuthenticator
內(nèi)置的認(rèn)證策略默認(rèn)實(shí)現(xiàn)是 AtLeastOneSuccessfulStrategy
方式??梢酝ㄟ^(guò)配置修改策略
2.2 配置修改
@Bean
public DefaultWebSecurityManager defaultWebSecurityManager(){//1 創(chuàng)建 defaultWebSecurityManager 對(duì)象DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();//2 創(chuàng)建認(rèn)證對(duì)象,并設(shè)置認(rèn)證策略ModularRealmAuthenticator modularRealmAuthenticator = new ModularRealmAuthenticator();modularRealmAuthenticator.setAuthenticationStrategy(new AllSuccessfulStrategy());defaultWebSecurityManager.setAuthenticator(modularRealmAuthenticator);//3 封裝 myRealm 集合List<Realm> list = new ArrayList<>();list.add(myRealm);list.add(myRealm2);//4 將 myRealm 存入 defaultWebSecurityManager 對(duì)象defaultWebSecurityManager.setRealms(list);//5 返回return defaultWebSecurityManager;
}
3、remember me
3.1 實(shí)現(xiàn)原理
Shiro 提供了記住我(RememberMe)的功能,比如訪問(wèn)一些網(wǎng)站時(shí),關(guān)閉了瀏覽器, 下次再打開時(shí)還是能記住你是誰(shuí), 下次訪問(wèn)時(shí)無(wú)需再登錄即可訪問(wèn)。
基本流程
- 首先在登錄頁(yè)面選中 RememberMe 然后登錄成功;如果是瀏覽器登錄,一般會(huì) 把 RememberMe 的 Cookie 寫到客戶端并保存下來(lái);
- 關(guān)閉瀏覽器再重新打開;會(huì)發(fā)現(xiàn)瀏覽器還是記住你的;
- 訪問(wèn)一般的網(wǎng)頁(yè)服務(wù)器端,仍然知道你是誰(shuí),且能正常訪問(wèn);
- 但是,如果我們?cè)L問(wèn)電商平臺(tái)時(shí),如果要查看我的訂單或進(jìn)行支付時(shí),此時(shí)還是需要再進(jìn)行身份認(rèn)證的,以確保當(dāng)前用戶還是你。
3.2 代碼實(shí)現(xiàn)
過(guò)濾器可以參考:Shiro學(xué)習(xí)之過(guò)濾器詳解
修改配置類,注意未認(rèn)證的重定向在yml中進(jìn)行配置
@Configuration
public class ShiroConfig {@Autowiredprivate MyRealm myRealm;//配置 SecurityManager@Beanpublic DefaultWebSecurityManager defaultWebSecurityManager() {//1 創(chuàng)建 defaultWebSecurityManager 對(duì)象DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();//2 創(chuàng)建加密對(duì)象,并設(shè)置相關(guān)屬性HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();//2.1 采用 md5 加密matcher.setHashAlgorithmName("md5");//2.2 迭代加密次數(shù)matcher.setHashIterations(3);//3 將加密對(duì)象存儲(chǔ)到 myRealm 中myRealm.setCredentialsMatcher(matcher);//4 將 myRealm 存入 defaultWebSecurityManager 對(duì)象defaultWebSecurityManager.setRealm(myRealm);//4.5 設(shè)置 rememberMedefaultWebSecurityManager.setRememberMeManager(rememberMeManager());//5 返回return defaultWebSecurityManager;}//cookie 屬性設(shè)置public SimpleCookie rememberMeCookie() {SimpleCookie cookie = new SimpleCookie("rememberMe");//設(shè)置跨域//cookie.setDomain(domain);cookie.setPath("/");cookie.setHttpOnly(true);cookie.setMaxAge(30 * 24 * 60 * 60);return cookie;}//創(chuàng)建 Shiro 的 cookie 管理對(duì)象public CookieRememberMeManager rememberMeManager() {CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();cookieRememberMeManager.setCookie(rememberMeCookie());cookieRememberMeManager.setCipherKey("1234567890987654".getBytes());return cookieRememberMeManager;}//配置 Shiro 內(nèi)置過(guò)濾器攔截范圍@Beanpublic DefaultShiroFilterChainDefinition shiroFilterChainDefinition() {DefaultShiroFilterChainDefinition definition = new DefaultShiroFilterChainDefinition();//設(shè)置不認(rèn)證可以訪問(wèn)的資源definition.addPathDefinition("/myController/userLogin", "anon");definition.addPathDefinition("/myController/login", "anon");//設(shè)置需要進(jìn)行登錄認(rèn)證的攔截范圍definition.addPathDefinition("/**", "authc");//添加存在用戶的過(guò)濾器(rememberMe)definition.addPathDefinition("/**", "user");return definition;}
}
修改controller
@Controller
@RequestMapping("myController")
public class MyController {//跳轉(zhuǎn)登錄頁(yè)面@GetMapping("login")public String login(){return "login";}@GetMapping("userLogin")public String userLogin(String name, String pwd,@RequestParam(defaultValue = "false")boolean rememberMe,HttpSession session){//1獲取subject對(duì)象Subject subject = SecurityUtils.getSubject();//2封裝請(qǐng)求數(shù)據(jù)到tokenAuthenticationToken token = new UsernamePasswordToken(name,pwd,rememberMe);//3調(diào)用login方法進(jìn)行登錄認(rèn)證try {subject.login(token);//return "登錄成功";session.setAttribute("user",token.getPrincipal().toString());return "main";} catch (AuthenticationException e) {e.printStackTrace();System.out.println("登錄失敗");return "登錄失敗";}}//登錄認(rèn)證驗(yàn)證rememberMe@GetMapping("userLoginRm")public String userLogin(HttpSession session) {session.setAttribute("user","rememberMe");return "main";}
}
改造login頁(yè)面
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<h1>Shiro 登錄認(rèn)證</h1>
<br>
<form action="/myController/userLogin"><div>用戶名:<input type="text" name="name" value=""></div><div>密碼:<input type="password" name="pwd" value=""></div><div>記住用戶:<input type="checkbox" name="rememberMe" value="true"></div><div><input type="submit" value="登錄"></div>
</form>
</body>
</html>
4、用戶注銷
用戶登錄后,配套的有登出操作。直接通過(guò)Shiro過(guò)濾器即可實(shí)現(xiàn)登出,首先修改main.html
<body>
<h1>Shiro 登錄認(rèn)證后主頁(yè)面</h1>
<br>
登錄用戶為:<span th:text="${session.user}"></span>
<br>
<a href="/logout">登出</a>
</body>
配置類中添加logout過(guò)濾器
@Bean
public DefaultShiroFilterChainDefinition shiroFilterChainDefinition(){ DefaultShiroFilterChainDefinition definition = new DefaultShiroFilterChainDefinition();//設(shè)置不認(rèn)證可以訪問(wèn)的資源definition.addPathDefinition("/myController/userLogin","anon"); definition.addPathDefinition("/myController/login","anon"); //配置登出過(guò)濾器definition.addPathDefinition("/logout","logout"); //設(shè)置需要進(jìn)行登錄認(rèn)證的攔截范圍definition.addPathDefinition("/**","authc"); //添加存在用戶的過(guò)濾器(rememberMe) definition.addPathDefinition("/**","user"); return definition;
}
5、授權(quán)、角色認(rèn)證
用戶登錄后,需要驗(yàn)證是否具有指定角色指定權(quán)限。Shiro也提供了方便的工具進(jìn)行判斷。這個(gè)工具就是Realm的doGetAuthorizationInfo方法進(jìn)行判斷,觸發(fā)權(quán)限判斷的有兩種方式
- 在頁(yè)面中通過(guò)shiro:屬性判斷
- 在接口服務(wù)中通過(guò)注解@Requires進(jìn)行判斷
5.1 后端接口服務(wù)注解
通過(guò)給接口服務(wù)方法添加注解可以實(shí)現(xiàn)權(quán)限校驗(yàn),可以加在控制器方法上,也可以加
在業(yè)務(wù)方法上,一般加在控制器方法上。常用注解如下:
-
@RequiresAuthentication
驗(yàn)證用戶是否登錄,等同于方法subject.isAuthenticated()
@RequiresUser
驗(yàn)證用戶是否被記憶: 登錄認(rèn)證成功subject.isAuthenticated()為true ;登錄后被記憶subject.isRemembered()為true
-
@RequiresGuest
驗(yàn)證是否是一個(gè)guest的請(qǐng)求,是否是游客的請(qǐng)求 ,此時(shí)subject.getPrincipal()為null
-
@RequiresRoles
驗(yàn)證subject是否有相應(yīng)角色,有角色訪問(wèn)方法,沒有則會(huì)拋出異常
AuthorizationException
。例如:@RequiresRoles("aRoleName") void someMethod();
只有subject有aRoleName角色才能訪問(wèn)方法someMethod() -
@RequiresPermissions
驗(yàn)證subject是否有相應(yīng)權(quán)限,有權(quán)限訪問(wèn)方法,沒有則會(huì)拋出異常
AuthorizationException
。例如:
@RequiresPermissions ("file:read","wite:aFile.txt") void someMethod();
subject必須同時(shí)含有file:read和wite:aFile.txt權(quán)限才能訪問(wèn)方someMethod()
5.2 授權(quán)驗(yàn)證-獲取角色驗(yàn)證
首先添加數(shù)據(jù)庫(kù)
CREATE TABLE `role` (`id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '編號(hào)', `name` VARCHAR(30) DEFAULT NULL COMMENT '角色名',`desc` VARCHAR(50) DEFAULT NULL COMMENT '描述',`realname` VARCHAR(20) DEFAULT NULL COMMENT '角色顯示名', PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COMMENT='角色表';CREATE TABLE `role_user` (`id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '編號(hào)', `uid` BIGINT(20) DEFAULT NULL COMMENT '用戶 id',`rid` BIGINT(20) DEFAULT NULL COMMENT '角色 id', PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COMMENT='角色用戶映射表';
mapper
@Repository
public interface UserMapper extends BaseMapper<User> {@Select("SELECT NAME FROM role WHERE id IN (SELECT rid FROM role_user WHERE uid=(SELECT id FROM USER WHERE NAME=#{principal}))")List<String> getUserRoleInfoMapper(@Param("principal") String principal);
}
service服務(wù)實(shí)現(xiàn)類
@Override
public List<String> getUserRoleInfo(String principal) { return userMapper.getUserRoleInfoMapper(principal);
}
MyRealm 授權(quán)認(rèn)證方法改造
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {System.out.println("進(jìn)入自定義授權(quán)方法"); //獲取當(dāng)前用戶身份信息String principal = principalCollection.getPrimaryPrincipal().toString();//調(diào)用接口方法獲取用戶的角色信息List<String> roles = userService.getUserRoleInfo(principal); System.out.println("當(dāng)前用戶角色信息:"+roles);//創(chuàng)建對(duì)象,存儲(chǔ)當(dāng)前登錄的用戶的權(quán)限和角色SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); //存儲(chǔ)角色,測(cè)試的話可以直接塞String的角色info.addRoles(roles);//返回 return info;
}
最后controller添加授權(quán)方法,在mian.html添加<a href="/myController/userLoginRoles">測(cè)試授權(quán)-角色驗(yàn)證</a>
測(cè)試鏈接即可進(jìn)行測(cè)試(數(shù)據(jù)庫(kù)數(shù)據(jù)自行添加)
//登錄認(rèn)證驗(yàn)證角色
@RequiresRoles("admin")
@GetMapping("userLoginRoles")
@ResponseBody
public String userLoginRoles(){System.out.println("登錄認(rèn)證驗(yàn)證角色");return "驗(yàn)證角色成功";
}
5.3 授權(quán)驗(yàn)證-獲取權(quán)限驗(yàn)證
創(chuàng)建權(quán)限數(shù)據(jù)表
CREATE TABLE `permissions` (`id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '編號(hào)', `name` VARCHAR(30) DEFAULT NULL COMMENT '權(quán)限名',`info` VARCHAR(30) DEFAULT NULL COMMENT '權(quán)限信息', `desc` VARCHAR(50) DEFAULT NULL COMMENT '描述', PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COMMENT='權(quán)限表';CREATE TABLE `role_ps` (`id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '編號(hào)', `rid` BIGINT(20) DEFAULT NULL COMMENT '角色 id',`pid` BIGINT(20) DEFAULT NULL COMMENT '權(quán)限 id', PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COMMENT='角色權(quán)限映射表';
創(chuàng)建方法
//Mapper類
@Select({"<script>","select info FROM permissions WHERE id IN ","(SELECT pid FROM role_ps WHERE rid IN (","SELECT id FROM role WHERE NAME IN ","<foreach collection='roles' item='name' open='(' separator=',' close=')'>","#{name}","</foreach>","))","</script>"
})
List<String> getUserPermissionInfoMapper(@Param("roles")List<String> roles);// Service層
@Override
public List<String> getUserPermissionInfo(List<String> roles) { return userMapper.getUserPermissionInfoMapper(roles);
}
修改MyRealm配置類
//自定義授權(quán)方法:獲取當(dāng)前登錄用戶權(quán)限信息,返回給 Shiro 用來(lái)進(jìn)行授權(quán)對(duì)比
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {System.out.println("進(jìn)入自定義授權(quán)方法");//獲取當(dāng)前用戶身份信息String principal = principalCollection.getPrimaryPrincipal().toString();//調(diào)用接口方法獲取用戶的角色信息List<String> roles = userService.getUserRoleInfo(principal);System.out.println("當(dāng)前用戶角色信息:"+roles);//調(diào)用接口方法獲取用戶角色的權(quán)限信息List<String> permissions = userService.getUserPermissionInfo(roles);System.out.println("當(dāng)前用戶權(quán)限信息:"+permissions);//創(chuàng)建對(duì)象,存儲(chǔ)當(dāng)前登錄的用戶的權(quán)限和角色SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();//存儲(chǔ)角色info.addRoles(roles);//存儲(chǔ)權(quán)限信息info.addStringPermissions(permissions);//返回 return info;
}
controller層
//登錄認(rèn)證驗(yàn)證權(quán)限
@RequiresPermissions("user:delete")
@GetMapping("userPermissions")
@ResponseBody
public String userLoginPermissions(){System.out.println("登錄認(rèn)證驗(yàn)證權(quán)限");return "驗(yàn)證權(quán)限成功";
}
修改main.html文件
<body>
<h1>Shiro 登錄認(rèn)證后主頁(yè)面</h1>
<br>
登錄用戶為:<span th:text="${session.user}"></span>
<br>
<a href="/logout">登出</a>
<br>
<a href="/myController/userLoginRoles">測(cè)試授權(quán)-角色驗(yàn)證</a>
<br>
<a href="/myController/userPermissions">測(cè)試授權(quán)-權(quán)限驗(yàn)證</a>
</body>
5.4 權(quán)限驗(yàn)證異常處理類
@ControllerAdvice
public class PermissionsException {@ResponseBody@ExceptionHandler(UnauthorizedException.class)public String unauthorizedException(Exception e){return "無(wú)權(quán)限";}@ResponseBody@ExceptionHandler(AuthorizationException.class)public String authorizationException(Exception e){return "權(quán)限認(rèn)證失敗";}}
5.5 前端頁(yè)面授權(quán)驗(yàn)證
前端可以根據(jù)不同的權(quán)限顯示不同的信息,首先添加依賴
<dependency><groupId>com.github.theborakompanioni</groupId> <artifactId>thymeleaf-extras-shiro</artifactId> <version>2.0.0</version>
</dependency>
配置類,用于解析 thymeleaf 中的 shiro:相關(guān)屬性
@Bean
public ShiroDialect shiroDialect(){return new ShiroDialect();
}
Thymeleaf 中常用的 shiro:屬性
<!--guest 標(biāo)簽:用戶沒有身份驗(yàn)證時(shí)顯示相應(yīng)信息,即游客訪問(wèn)信息-->
<shiro:guest>
</shiro:guest><!--user標(biāo)簽:用戶已經(jīng)身份驗(yàn)證/記住我登錄后顯示相應(yīng)的信息-->
<shiro:user>
</shiro:user><!--authenticated 標(biāo)簽:用戶已經(jīng)身份驗(yàn)證通過(guò),即 Subject.login 登錄成功,不是記住我登錄的-->
<shiro:authenticated>
</shiro:authenticated><!--notAuthenticated 標(biāo)簽:用戶已經(jīng)身份驗(yàn)證通過(guò),即沒有調(diào)用 Subject.login 進(jìn)行登錄,包括記住我自動(dòng)登錄的
也屬于未進(jìn)行身份驗(yàn)證-->
<shiro:notAuthenticated>
</shiro:notAuthenticated><!--principal 標(biāo)簽:當(dāng)于((User)Subject.getPrincipals()).getUsername()-->
<shiro: principal/>
<shiro:principal property="username"/><!--lacksPermission 標(biāo)簽:如果當(dāng)前 Subject 沒有權(quán)限將顯示 body 體內(nèi)容-->
<shiro:lacksPermission name="org:create">
</shiro:lacksPermission><!--hasRole標(biāo)簽:如果當(dāng)前 Subject 有角色將顯示 body 體內(nèi)容-->
<shiro:hasRole name="admin">
</shiro:hasRole><!--hasAnyRoles 標(biāo)簽標(biāo)簽:如果當(dāng)前 Subject 有任意一個(gè)角色(或的關(guān)系)將顯示 body 體內(nèi)容-->
<shiro:hasAnyRoles name="admin,user">
</shiro:hasAnyRoles><!--lacksRole標(biāo)簽:如果當(dāng)前 Subject 沒有角色將顯示 body 體內(nèi)容-->
<shiro:lacksRole name="abc">
</shiro:lacksRole><!--lacksPermission 標(biāo)簽:如果當(dāng)前 Subject 有權(quán)限將顯示 body 體內(nèi)容-->
<shiro:hasPermission name="user:create">
</shiro:hasPermission>
6、緩存管理
6.1 緩存工具EhCache
EhCache是一種廣泛使用的開源Java分布式緩存。主要面向通用緩存,Java EE和輕量級(jí)容器??梢院痛蟛糠諮ava項(xiàng)目無(wú)縫整合,例如:Hibernate中的緩存就是基于EhCache實(shí)現(xiàn)的。EhCache支持內(nèi)存和磁盤存儲(chǔ),默認(rèn)存儲(chǔ)在內(nèi)存中,如內(nèi)存不夠時(shí)把緩存數(shù)據(jù)同步到磁盤中。EhCache支持基于Filter的Cache實(shí)現(xiàn),也支持Gzip壓縮算法。
EhCache直接在JVM虛擬機(jī)中緩存,速度快,效率高;EhCache缺點(diǎn)是緩存共享麻煩,集群分布式應(yīng)用使用不方便
6.2 Ehcache簡(jiǎn)單搭建
引入依賴
<dependency><groupId>net.sf.ehcache</groupId><artifactId>ehcache</artifactId><version>2.6.11</version><type>pom</type>
</dependency>
創(chuàng)建配置文件
<?xml version="1.0" encoding="UTF-8"?>
<ehcache><!--磁盤的緩存位置--><diskStore path="java.io.tmpdir/ehcache"/><!--默認(rèn)緩存--><defaultCachemaxEntriesLocalHeap="10000"eternal="false"timeToIdleSeconds="120"timeToLiveSeconds="120"maxEntriesLocalDisk="10000000"diskExpiryThreadIntervalSeconds="120"memoryStoreEvictionPolicy="LRU"><persistence strategy="localTempSwap"/></defaultCache><!--helloworld緩存--><cache name="HelloWorldCache"maxElementsInMemory="1000"eternal="false"timeToIdleSeconds="5"timeToLiveSeconds="5"overflowToDisk="false"memoryStoreEvictionPolicy="LRU"/><!--defaultCache:默認(rèn)緩存策略,當(dāng)ehcache找不到定義的緩存時(shí),則使用這個(gè)緩存策略。只能定義一個(gè)。--><!--name:緩存名稱。maxElementsInMemory:緩存最大數(shù)目maxElementsOnDisk:硬盤最大緩存?zhèn)€數(shù)。eternal:對(duì)象是否永久有效,一但設(shè)置了,timeout將不起作用。overflowToDisk:是否保存到磁盤,當(dāng)系統(tǒng)宕機(jī)時(shí)timeToIdleSeconds:設(shè)置對(duì)象在失效前的允許閑置時(shí)間(單位:秒)。僅當(dāng)eternal=false對(duì)象不是永久有效時(shí)使用,可選屬性,默認(rèn)值是0,也就是可閑置時(shí)間無(wú)窮大。timeToLiveSeconds:設(shè)置對(duì)象在失效前允許存活時(shí)間(單位:秒)。最大時(shí)間介于創(chuàng)建時(shí)間和失效時(shí)間之間。僅當(dāng)eternal=false對(duì)象不是永久有效時(shí)使用,默認(rèn)是0.,也就是對(duì)象存活時(shí)間無(wú)窮大。diskPersistent:是否緩存虛擬機(jī)重啟期數(shù)據(jù) Whether the disk store persists between restarts of the Virtual Machine. The default value is false.diskSpoolBufferSizeMB:這個(gè)參數(shù)設(shè)置DiskStore(磁盤緩存)的緩存區(qū)大小。默認(rèn)是30MB。每個(gè)Cache都應(yīng)該有自己的一個(gè)緩沖區(qū)。diskExpiryThreadIntervalSeconds:磁盤失效線程運(yùn)行時(shí)間間隔,默認(rèn)是120秒。memoryStoreEvictionPolicy:當(dāng)達(dá)到maxElementsInMemory限制時(shí),Ehcache將會(huì)根據(jù)指定的策略去清理內(nèi)存。默認(rèn)策略是LRU(最近最少使用)。你可以設(shè)置為FIFO(先進(jìn)先出)或是LFU(較少使用)。clearOnFlush:內(nèi)存數(shù)量最大時(shí)是否清除。memoryStoreEvictionPolicy:可選策略有:LRU(最近最少使用,默認(rèn)策略)、FIFO(先進(jìn)先出)、LFU(最少訪問(wèn)次數(shù))。FIFO,first in first out,這個(gè)是大家最熟的,先進(jìn)先出。LFU, Less Frequently Used,就是上面例子中使用的策略,直白一點(diǎn)就是講一直以來(lái)最少被使用的。如上面所講,緩存的元素有一個(gè)hit屬性,hit值最小的將會(huì)被清出緩存。LRU,Least Recently Used,最近最少使用的,緩存的元素有一個(gè)時(shí)間戳,當(dāng)緩存容量滿了,而又需要騰出地方來(lái)緩存新的元素的時(shí)候,那么現(xiàn)有緩存元素中時(shí)間戳離當(dāng)前時(shí)間最遠(yuǎn)的元素將被清出緩存。--></ehcache>
測(cè)試類
public static void main(String[] args) {//獲取編譯目錄下的資源的流對(duì)象InputStream input = TestEH.class.getClassLoader().getResourceAsStream("ehcache.xml");//獲取EhCache的緩存管理對(duì)象CacheManager cacheManager = new CacheManager(input);//獲取緩存對(duì)象Cache cache = cacheManager.getCache("HelloWorldCache");//創(chuàng)建緩存數(shù)據(jù)Element element = new Element("name","shawn");//存入緩存cache.put(element);//從緩存中取出數(shù)據(jù)輸出Element element1 = cache.get("name");System.out.println("緩存中數(shù)據(jù) = " + element1.getObjectValue());
}
6.3 Shiro整合EhCache
<!--Shiro整合EhCache-->
<dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-ehcache</artifactId><version>1.10.0</version>
</dependency><dependency><groupId>commons-io</groupId><artifactId>commons-io</artifactId><version>2.11.0</version>
</dependency>
然后在resources下添加配置文件ehcache/ehcache-shiro.xml
<?xml version="1.0" encoding="UTF-8"?>
<ehcache name="ehcache" updateCheck="false"> <!--磁盤的緩存位置--><diskStore path="java.io.tmpdir"/> <!--默認(rèn)緩存--><defaultCachemaxEntriesLocalHeap="1000"eternal="false"timeToIdleSeconds="3600"timeToLiveSeconds="3600"overflowToDisk="false"></defaultCache> <!--登錄認(rèn)證信息緩存:緩存用戶角色權(quán)限--><cache name="loginRolePsCache"maxEntriesLocalHeap="2000"eternal="false"timeToIdleSeconds="600"timeToLiveSeconds="0"overflowToDisk="false"statistics="true"/>
</ehcache>
最后修改Shiro配置
@Configuration
public class ShiroConfig {@Autowiredprivate MyRealm myRealm;//配置SecurityManager@Beanpublic DefaultWebSecurityManager defaultWebSecurityManager(){//1創(chuàng)建defaultWebSecurityManager 對(duì)象DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();//2創(chuàng)建加密對(duì)象,設(shè)置相關(guān)屬性HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();//2.1采用md5加密matcher.setHashAlgorithmName("md5");//2.2迭代加密次數(shù)matcher.setHashIterations(3);//3將加密對(duì)象存儲(chǔ)到myRealm中myRealm.setCredentialsMatcher(matcher);//4將myRealm存入defaultWebSecurityManager 對(duì)象defaultWebSecurityManager.setRealm(myRealm);//4.5設(shè)置rememberMedefaultWebSecurityManager.setRememberMeManager(rememberMeManager());//4.6設(shè)置緩存管理器defaultWebSecurityManager.setCacheManager(getEhCacheManager());//5返回return defaultWebSecurityManager;}//緩存管理器public EhCacheManager getEhCacheManager(){EhCacheManager ehCacheManager = new EhCacheManager();InputStream is =null;try {is = ResourceUtils.getInputStreamForPath("classpath:ehcache/ehcache-shiro.xml");} catch (IOException e) {e.printStackTrace();}CacheManager cacheManager = new CacheManager(is);ehCacheManager.setCacheManager(cacheManager);return ehCacheManager;}//cookie 屬性設(shè)置public SimpleCookie rememberMeCookie() {SimpleCookie cookie = new SimpleCookie("rememberMe");//設(shè)置跨域//cookie.setDomain(domain);cookie.setPath("/");cookie.setHttpOnly(true);cookie.setMaxAge(30 * 24 * 60 * 60);return cookie;}//創(chuàng)建 Shiro 的 cookie 管理對(duì)象public CookieRememberMeManager rememberMeManager() {CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();cookieRememberMeManager.setCookie(rememberMeCookie());cookieRememberMeManager.setCipherKey("1234567890987654".getBytes());return cookieRememberMeManager;}//配置 Shiro 內(nèi)置過(guò)濾器攔截范圍@Beanpublic DefaultShiroFilterChainDefinition shiroFilterChainDefinition(){DefaultShiroFilterChainDefinition definition = new DefaultShiroFilterChainDefinition();//設(shè)置不認(rèn)證可以訪問(wèn)的資源definition.addPathDefinition("/myController/userLogin","anon");definition.addPathDefinition("/myController/login","anon");//配置登出過(guò)濾器definition.addPathDefinition("/logout","logout");//設(shè)置需要進(jìn)行登錄認(rèn)證的攔截范圍definition.addPathDefinition("/**","authc");//添加存在用戶的過(guò)濾器(rememberMe)definition.addPathDefinition("/**","user");return definition;}
}
最后訪問(wèn)權(quán)限測(cè)試,發(fā)現(xiàn)其成功緩存
7、會(huì)話管理
7.1 SessionManager
會(huì)話管理器,負(fù)責(zé)創(chuàng)建和管理用戶的會(huì)話(Session)生命周期,它能夠在任何環(huán)境中在本地管理用戶會(huì)話,即使沒有Web/Servlet/EJB容器,也一樣可以保存會(huì)話。默認(rèn)情況下,Shiro會(huì)檢測(cè)當(dāng)前環(huán)境中現(xiàn)有的會(huì)話機(jī)制(比如Servlet容器)進(jìn)行適配,如果沒有(比如獨(dú)立應(yīng)用程序或者非Web環(huán)境),它將會(huì)使用內(nèi)置的企業(yè)會(huì)話管理器來(lái)提供相應(yīng)的會(huì)話管理服務(wù),其中還涉及一個(gè)名為SessionDAO的對(duì)象。SessionDAO負(fù)責(zé)Session的持久化操作(CRUD),允許Session數(shù)據(jù)寫入到后端持久化數(shù)據(jù)庫(kù)
7.2 會(huì)話管理實(shí)現(xiàn)
SessionManager由SecurityManager管理。Shiro提供了三種實(shí)現(xiàn)
- DefaultSessionManager:用于JavaSE環(huán)境
- ServletContainerSessionManager:用于web環(huán)境,直接使用Servlet容器的會(huì)話
- DefaultWebSessionManager:用于web環(huán)境,自己維護(hù)會(huì)話(不使用Servlet容器的會(huì)話管理)
7.3 獲得session方式
- 實(shí)現(xiàn)
Session session = SecurityUtils.getSubject().getSession();session.setAttribute("key","value")
-
說(shuō)明
Controller中的request,在shiro過(guò)濾器中的doFilerInternal方法,被包裝成ShiroHttpServletRequest。
SecurityManager和SessionManager會(huì)話管理器決定session來(lái)源于ServletRequest還是由Shiro管理的會(huì)話。 無(wú)論是通過(guò)request.getSession或subject.getSession獲取到session,操作session,兩者都是等價(jià)的。
參考:
shiro框架如何保持登錄狀態(tài)
https://www.bilibili.com/video/BV11e4y1n7BH