shiro目錄
- 前言
- 一、shiro簡介
- 二、shiro簡單使用
- 三、身份驗證
- 3.1.環境準備
- 3.2.登錄/退出
- 四、shiro授權
- 4.1.shiro授權的三種方式:
- 4.2.實作
- 4.3.自定義Realm進行授權
- 五、與SpringBoot集成案例
- 六、shiro各個攔截器對比及使用配置
- 6.1.各種攔截器的特點
- 6.2.INI檔案配置攔截器
- 6.3.代碼形式配置攔截器
- 6.4.簡單使用
- 七、集成Shiro之Shiro標簽(.jsp和.ftl)
- 7.1.JSP的Shiro標簽
- 7.2.Freemark的Shiro標簽
- 八、會話管理
- 8.1.會話相關API
- 8.2.會話監聽器(SessionListener介面)
- 8.3.SessionDao(會話持久化、提供CRUD操作)
- 8.4.會話驗證
- 九、快取
- 9.1. 集中式快取
- 9.2. 本地快取
- 十、shiro修仙進階
前言
目前,使用 Apache Shiro 的人越來越多,因為它相當簡單,對比 Spring Security,可能沒有 Spring Security 做的功能強大,但是在實際作業時可能并不需要那么復雜的東西,所以使用小而簡單的 Shiro 就足夠了,對于它倆到底哪個好,這個不必糾結,能更簡單的解決專案問題就好了
一、shiro簡介
Apache Shiro 是 Java 的一個安全框架,可以非常容易的開發出足夠好的應用,其不僅可以用在 JavaSE 環境,也可以用在JavaEE 環境,Shiro 可以幫助我們完成:認證、授權、加密、會話管理、與 Web 集成、快取等,這不就是我們想要的嘛,而且 Shiro 的 API 也是非常簡單;其基本功能點如下圖所示:

Authentication :身份認證/登錄,驗證用戶是不是擁有相應的身份;
Authorization: :授權,即權限驗證,驗證某個已認證的用戶是否擁有某個權限;即判斷用
戶是否能做事情,常見的如:驗證某個用戶是否擁有某個角色,或者細粒度的驗證某個用
戶對某個資源是否具有某個權限;
Session Manager: :會話管理,即用戶登錄后就是一次會話,在沒有退出之前,它的所有信
息都在會話中;會話可以是普通 JavaSE 環境的,也可以是如 Web 環境的;
Cryptography :加密,保護資料的安全性,如密碼加密存盤到資料庫,而不是明文存盤;
Web Support :Web 支持,可以非常容易的集成到 Web 環境;
Caching:快取,比如用戶登錄后,其用戶資訊、擁有的角色/權限不必每次去查,這樣可以
提高效率;
Concurrency :shiro 支持多執行緒應用的并發驗證,即如在一個執行緒中開啟另一個執行緒,能
把權限自動傳播過去;
Testing :提供測驗支持;
Run As :允許一個用戶假裝為另一個用戶(如果他們允許)的身份進行訪問;
Remember Me: :記住我,這個是非常常見的功能,即一次登錄后,下次再來的話不用登錄
了,
注意:Shiro 不會去維護用戶、維護權限;這 些需要我們 自己去 設計/ 提供 ; 然后通過
相應的 介面注入給 給 Shiro 即可,
shiro外部及內部結構:


具體引數配置什么意思可查看此文:Shiro完整教程, 附帶各種配置
二、shiro簡單使用
1.Idea穿件一個Maven專案
引入pom依賴:
<dependencies>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.6.1</version>
</dependency>
</dependencies>
2.任意創建一個包,在里面創建一個Tutorial類
package me.aihe;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Tutorial {
private static final transient Logger log = LoggerFactory.getLogger(Tutorial.class);
public static void main(String[] args) {
log.info("My First Apache Shiro Application");
System.exit(0);
}
}
3.使用Shiro
Shiro提供了一個通用的方案通過 INI 進行配置 ,當然也可以通過XML,YMAL,JSON等進行配置,
在resource目錄下面,創建一個shiro.ini的檔案,內容如下:
# -----------------------------------------------------------------------------
# Users and their (optional) assigned roles
# username = password, role1, role2, ..., roleN
# -----------------------------------------------------------------------------
[users]
root = secret, admin
guest = guest, guest
presidentskroob = 12345, president
darkhelmet = ludicrousspeed, darklord, schwartz
aihe = aihe, goodguy, client
# -----------------------------------------------------------------------------
# Roles with assigned permissions
# roleName = perm1, perm2, ..., permN
# -----------------------------------------------------------------------------
[roles]
admin = *
client = look:*
goodguy = winnebago:drive:eagle5
4.參考Shiro.ini配置進行測驗
現在改變我們的Tutorial類檔案,內容如下
package me.aihe;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Created by aihe on 2017/6/14.
*/
public class Tutorial {
private static final transient Logger log = LoggerFactory.getLogger(Tutorial.class);
public static void main(String[] args) {
log.info("My First Apache Shiro Application");
//1. 這里的SecurityManager是org.apache.shiro.mgt.SecurityManager,而不是java.lang.SecurityManager
// 加載組態檔
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
//2.決議組態檔,并且回傳一些SecurityManger實體
SecurityManager securityManager = factory.getInstance();
//3.設定SecurityManager到靜態記憶體區,單例模式
SecurityUtils.setSecurityManager(securityManager);
// 安全操作
Subject currentUser = SecurityUtils.getSubject();
// 在應用的當前會話中設定屬性
Session session = currentUser.getSession();
session.setAttribute("key","value");
//當前我們的用戶是匿名的用戶,我們嘗試進行登錄,
if (!currentUser.isAuthenticated()){
UsernamePasswordToken token = new UsernamePasswordToken("aihe", "aihe");
//this is all you have to do to support 'remember me' (no config - built in!):
token.setRememberMe(true);
//嘗試進行登錄用戶,如果登錄失敗了,我們進行一些處理
try{
currentUser.login(token);
//當我們獲登錄用戶之后
log.info("User [" + currentUser.getPrincipal() + "] logged in successfully.");
// 查看用戶是否有指定的角色
if ( currentUser.hasRole( "client" ) ) {
log.info("Look is in your role" );
} else {
log.info( "....." );
}
// 查看用戶是否有某個權限
if ( currentUser.isPermitted( "look:desk" ) ) {
log.info("You can look. Use it wisely.");
} else {
log.info("Sorry, you can't look.");
}
if ( currentUser.isPermitted( "winnebago:drive:eagle5" ) ) {
log.info("You are permitted to 'drive' the 'winnebago' with license plate (id) 'eagle5'. " +
"Here are the keys - have fun!");
} else {
log.info("Sorry, you aren't allowed to drive the 'eagle5' winnebago!");
}
//登出
currentUser.logout();
}
catch ( UnknownAccountException uae ) {
//賬戶不存在的操作
} catch ( IncorrectCredentialsException ice ) {
//密碼不正確
} catch ( LockedAccountException lae ) {
//用戶被鎖定了
} catch ( AuthenticationException ae ) {
//無法判斷的情形
}
}
System.exit(0);
}
}
這個相對來說是一個簡單的程式,但也證明了一些shiro的基本用法,我們可以通過shiro進行認證,權限控制等
三、身份驗證
身份驗證,即在應用中誰能證明他就是他本人,一般提供如他們的身份 ID 一些標識資訊來
表明他就是他本人,如提供身份證,用戶名/密碼來證明,
在 shiro 中,用戶需要提供 principals (身份)和 credentials(證明)給 shiro,從而應用能
驗證用戶身份:
principals:身份,即主體的標識屬性,可以是任何東西,如用戶名、郵箱等,唯一即可,
一個主體可以有多個 principals,但只有一個 Primary principals,一般是用戶名/密碼/手機號,
credentials:證明/憑證,即只有主體知道的安全值,如密碼/數字證書等,
最常見的 principals 和 credentials 組合就是用戶名/密碼了,接下來先進行一個基本的身份認
證,
另外兩個相關的概念是之前提到的 Subject 及 Realm,分別是主體及驗證主體的資料源,
3.1.環境準備
1.引入pom依賴( junit、common-logging 及 shiro-core 依賴)
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.9</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.1.3</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.2.2</version>
</dependency>
</dependencies>
3.2.登錄/退出
1.原理:
使用** FormAuthenticationFilter過濾器實作, 原理如下:
1.用戶沒有認證時, 就會請求 loginurl進行認證, 用戶身份和用戶密碼提交資料到loginurl地址.
2.資料提交到 loginurl 地址后, 由 FormAuthenticationFilter進行攔截, 并取出 request 中的username 和 password.
3.然后 FormAuthenticationFilter** 會呼叫 realm, 在進行呼叫時會傳入一個 token, 也就是會傳入username 和 password.
4.最后 realm 認真時根據 username 查詢用戶資訊.例如我們之前查詢了用戶選單和 url.
如果查詢不到, realm 回傳 null, ** FormAuthenticationFilter**向 request 域填充一個引數, 這個引數記錄了例外資訊.
2.實作
//登陸提交地址,和applicationContext-shiro.xml中配置的loginurl一致
@RequestMapping("login")
public String login(HttpServletRequest request)throws Exception{
//如果登陸失敗從request中獲取認證例外資訊,shiroLoginFailure就是shiro例外類的全限定名
String exceptionClassName = (String) request.getAttribute("shiroLoginFailure");
//根據shiro回傳的例外類路徑判斷,拋出指定例外資訊
if(exceptionClassName!=null){
if (UnknownAccountException.class.getName().equals(exceptionClassName)) {
//最侄訓拋給例外處理器
throw new CustomException("賬號不存在");
} else if (IncorrectCredentialsException.class.getName().equals(
exceptionClassName)) {
throw new CustomException("用戶名/密碼錯誤");
} else if("randomCodeError".equals(exceptionClassName)){
throw new CustomException("驗證碼錯誤 ");
}else {
throw new Exception();//最終在例外處理器生成未知錯誤
}
}
//此方法不處理登陸成功(認證成功),shiro認證成功會自動跳轉到上一個請求路徑
//登陸失敗還到login頁面
return "login";
}
這里一定要注意我們寫的這個 login() 方法, 只是負責處理認證失敗的結果, 如果用戶認證成功那么就會跳轉到上一個請求路徑.
什么是上一個請求路徑呢?
答: 如果你要訪問 xxxx.jsp 但是 shiro 發現你還沒有登錄, 就會進行攔截, 并跳轉到登錄界面例如login.jsp, 當認證成功后會跳轉到xxxx.jsp
3.退出
不用我們實作退出, 只要訪問一個退出 url, 由LogoutFilter攔截住, 清除 session.
/logout.action = logout
4.從 Shiro 的 session 中獲取認證資訊
//因為我們用過 Shiro 用戶認證后是存放在 Shiro 的 session 中.
Subject subject = SecurityUtils.getSubject();
//取出身份資訊
subject.getPrincipal();
四、shiro授權
4.1.shiro授權的三種方式:
1.編程式:通過寫if/else授權代碼塊完成:
Subject subject = SecurityUtils.getSubject();
if(subject.hasRole(“admin”)) {
//有權限
} else {
//無權限
}
2.注解式:通過在執行的Java方法上放置相應的注解完成(沒有權限將拋出相應的例外):
@RequiresRoles("admin")
public void hello() {
//有權限
}
3.JSP/GSP標簽:在JSP/GSP頁面通過相應的標簽完成:
<shiro:hasRole name="admin">
<!— 有權限 —>
</shiro:hasRole>
4.2.實作
1.在src包下創建一個shiro-permission.ini組態檔,用于模擬資料庫中的權限資料,內容如下:
#基于用戶的訪問控制
[users]
#用戶zhangsan的密碼是123,此用戶具有role1和role2兩個角色;用戶wang具有role2一個角色
zhangsan=123,role1,role2
wang=123,role2
#基于權限的訪問控制
[roles]
#角色role1對資源user擁有create、update權限
role1=user:create,user:update
#角色role2對資源user擁有create、delete權限
role2=user:create,user:delete
#角色role3對資源user擁有create權限
role3=user:create
對組態檔中的解釋如下:
權限識別符號號規則:資源:操作:實體(中間使用半角:分隔),如下:
user:create:01,表示對用戶資源的01實體進行create操作,
user:create,表示對用戶資源進行create操作,相當于user:create:,對所有用戶資源實體進行create操作,
user::01,表示對用戶資源實體01進行所有操作,
然后寫一個測驗類Authorization.java,代碼如下:
public class AuthorizationTest
{
//角色授權測驗和資源授權測驗
@Test
public void testAuthorization()
{
//第一步,創建SecurityManager工廠
Factory<SecurityManager> factory=new IniSecurityManagerFactory("classpath:shiro-permission.ini");
//第二步:創建SecurityManager
SecurityManager securityManager=factory.getInstance();
//第三步,將SecurityManager設定到系統運行環境,和spring整合后會將SecurityManager配置到spring容器中,一般單例管理
SecurityUtils.setSecurityManager(securityManager);
//第四步,創建subject
Subject subject=SecurityUtils.getSubject();
//創建token令牌,這里的用戶名和密碼以后由用戶輸入
UsernamePasswordToken token=new UsernamePasswordToken("zhangsan","123");
try {
//執行認證,將用戶輸入的資訊同資料庫(即.ini組態檔)中資訊進行對比
subject.login(token);
}catch (AuthenticationException e)
{
e.printStackTrace();
}
System.out.println("認證狀態:"+subject.isAuthenticated());
//認證通過后才能執行授權
//第一種授權方式是基于角色的授權,hasRole傳入角色的標識
boolean ishasRole=subject.hasRole("role1");//該用戶是否有role1這個角色
System.out.println("單個角色判斷"+ishasRole);
//hasAllRoles是否擁有多個角色
boolean hasAllRoles=subject.hasAllRoles(Arrays.asList("role1","role2"));
System.out.println("多個角色判斷"+hasAllRoles);//角色的就講到這里了,后面我們都是通過資源進行權限講解
//使用check方法進行授權,如果授權不通過會拋出例外
subject.checkRole("role3");
//第二種授權方式是基于資源的授權,isPermitted傳入權限識別符號
boolean isPermitted=subject.isPermitted("user:create");//該用戶是否有對user資源進行創建的權限
System.out.println("單個權限判斷"+isPermitted);
//多個權限判斷
boolean isPermittedAll=subject.isPermittedAll("user:create:1","user:update");
System.out.println("多個權限判斷:"+isPermittedAll);
}
}
從測驗代碼中我們可以知道,只有當用戶資訊得到認證后才能對用戶進行授權操作,
上面只是一個簡單的入門程式,從代碼中直接讀取資料庫(即組態檔)中的權限資料,然后將其與代碼中的給定權限進行判斷看該用戶是否具有該權限,而實際操作中我們是通過Realm進行讀取資料庫中的資料的,所以接下來講自定義Realm進行授權,
4.3.自定義Realm進行授權
1.需求
上邊的程式通過shiro-permission.ini對權限資訊進行靜態配置,實際開發中從資料庫中獲取權限資料,就需要自定義realm,由realm從資料庫查詢權限資料,
realm根據用戶身份查詢權限資料,將權限資料回傳給authorizer(授權器),
2.自定義Realm
在上篇文章自定義的realm中即CustomRealm.java,修改doGetAuthorizationInfo()方法,代碼如下:
//上邊是進行認證的方法,在上篇文章中已完成
//用于授權,當然首先要實作認證
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
//從principals獲取主身份資訊
//將getPrimaryPrincipal方法回傳值轉為真實身份型別(在上邊的goGetAuthenticationInfo認證通過填充到SimpleAuthenticationInfo)
String userCode= (String) principals.getPrimaryPrincipal();
//根據身份資訊獲取權限資訊,
//模擬從資料庫中獲取到的動態權限資料
List<String> permissions=new ArrayList<>();
permissions.add("user:create");//模擬user的創建權限
permissions.add("items:add");//模擬商品的添加權限
//查到權限資料,回傳授權資訊(包括上邊的permissions)
SimpleAuthorizationInfo simpleAuthorizationInfo=new SimpleAuthorizationInfo();
//將上邊查詢到授權資訊填充到simpleAuthorizationInfo物件中
simpleAuthorizationInfo.addStringPermissions(permissions);
return simpleAuthorizationInfo;
}
然后要對該自定義Realm進行配置,即將自定義Realm放入SecurityManager中,上篇文章中的shiro-realm.ini組態檔中我們已實作,
然后便可以進行測驗了,測驗代碼如下:
//自定義realm進行資源授權測驗
@Test
public void testAuthorizationCustomRealm()
{
//第一步,創建SecurityManager工廠
Factory<SecurityManager> factory=new IniSecurityManagerFactory("classpath:config/shiro-realm.ini");
//第二步:創建SecurityManager
SecurityManager securityManager=factory.getInstance();
//第三步,將SecurityManager設定到系統運行環境,和spring整合后會將SecurityManager配置到spring容器中,一般單例管理
SecurityUtils.setSecurityManager(securityManager);
//第四步,創建subject
Subject subject=SecurityUtils.getSubject();
//創建token令牌,這里的用戶名和密碼以后由用戶輸入
UsernamePasswordToken token=new UsernamePasswordToken("zhangsan","111111");
try {
//執行認證,將用戶輸入的資訊同資料庫(即.ini組態檔)中資訊進行對比
subject.login(token);
}catch (AuthenticationException e)
{
e.printStackTrace();
}
System.out.println("認證狀態:"+subject.isAuthenticated());
//基于資源的授權,呼叫isPermitted方法會呼叫CustomRealm從資料庫查詢正確的權限資料
// isPermitted傳入權限識別符號,判斷user:create:1是否在CustomRealm查詢到的權限資料之內
boolean isPermitted=subject.isPermitted("user:create:1");//該用戶是否有對user的1資源進行創建的權限
System.out.println("單個權限判斷"+isPermitted);
//多個權限判斷
boolean isPermittedAll=subject.isPermittedAll("user:create:1","user:create");
System.out.println("多個權限判斷:"+isPermittedAll);
}
授權流程講解:
1.對
subject進行授權,呼叫方法isPermitted(“permission串”),
2.SecurityManager執行授權,通過ModularRealmAuthorizer執行授權,3.
3.ModularRealmAuthorizer執行realm(自定義的CustomRealm)從資料庫查詢權限資料,即呼叫realm的授權方法:doGetAuthorizationInfo(),
4.realm從資料庫查詢權限資料,回傳ModularRealmAuthorizer,
5.ModularRealmAuthorizer呼叫PermissionResolver進行權限串比對,
6.如果比對后,isPermitted中"permission串"在realm查詢到權限資料中,說明用戶訪問permission串有權限,否則 沒有權限,拋出例外,
五、與SpringBoot集成案例
SpringBoot整合Shiro(Java安全框架)案例(含原始碼)
六、shiro各個攔截器對比及使用配置
6.1.各種攔截器的特點
| 簡寫(加粗為常用) | 名稱 | 優先級(1為最高) | 說明 | 對應Java類 |
|---|---|---|---|---|
| anon | 匿名攔截器 | 1 | 不需要登錄就能訪問,一般用于靜態資源,或者移動端介面 | org.apache.shiro.web.filter.authc.AnonymousFilter |
| authc | 登錄攔截器 | 2 | 需要登錄認證才能訪問的資源 | org.apache.shiro.web.filter.authc.FormAuthenticationFilter |
| authcBasic | Http攔截器 | 3 | Http身份驗證攔截器,非常用型別,不太了解 | org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter |
| logout | 登出攔截器 | 4 | 用戶登出攔截器,主要屬性:redirectURL退出登錄后重定向的地址 | org.apache.shiro.web.filter.authc.LogoutFilter |
| noSessionCreation | 不創建會話攔截器 | 5 | 呼叫 subject.getSession(false) 不會有什么問題,但是如果 subject.getSession(true) 將拋出 DisabledSessionException 例外 | org.apache.shiro.web.filter.authc.NoSessionCreationFilter |
| prems | 權限攔截器 | 6 | 驗證用戶是否擁有資源權限 | org.apache.shiro.web.filter.authc.PermissionsAuthorizationFilter |
| port | 埠攔截器 | 7 | 其主要屬性: port(80) 如果用戶訪問該頁面是非 80,將自動將請求埠改為 80 并重定向到該 80 埠 | org.apache.shiro.web.filter.authc.PortFilter |
| rest | rest風格攔截器 | 8 | rest 風格攔截器,自動根據請求方法構建權限字串構建權限字串;非常用型別攔截器 | org.apache.shiro.web.filter.authc.HttpMethodPermissionFilter |
| roles | 角色攔截器 | 9 | 驗證用戶是否擁有資源角色 | org.apache.shiro.web.filter.authc.RolesAuthorizationFilter |
| ssl | SSL攔截器 | 10 | 只有請求協議是https才能通過,否則你會自動跳轉到https埠(443) | org.apache.shiro.web.filter.authc.SslFilter |
| user | 用戶攔截器 | 11 | 用戶攔截器,用戶已經身份驗證 / 記住我登錄的都可; | org.apache.shiro.web.filter.authc.UserFilter |
6.2.INI檔案配置攔截器
1.配置之前,我們還需要講一下 攔截器的通配符的寫法,如下所示:
?:匹配一個字符
*:匹配零個或多個字符
**:匹配零個或多個路徑
2.然后,我們看一下[urls]模塊的示例.這里我只用到了一些常用的攔截器:
[urls]
#不需要登錄
/login=anon
/static/**=anon
#需要登錄
/home=authc
#需要角色
/deleteUser = roles["superAdmin"]
#需要用戶權限
/addUser = perms["user:create"]
3.但是,我們發現一個問題,假設用戶沒有該權限和沒有該角色,或者沒有登錄的時候,還有登出的時候,我們都需要給他們配置對應的重定向路徑.這時候,我們就不能在[urls]模塊中使用了,我們需要在[main]模塊中進行各個情況路徑的重定向設定了.示例代碼如下所示:
[main]
#用戶登錄的地址
authc.loginUrl = /login
#用戶沒有對應角色的跳轉重指向
roles.unauthorizedUrl = /login
#用戶沒有對應權限的跳轉重指向
perms.unauthorizedUrl = /login
#用戶登出的跳轉重指向
logout.redirectUrl = /login
注意:INI檔案中 [urls] 模塊攔截順序是從上往下依次執行.
6.3.代碼形式配置攔截器
使用INI檔案配置攔截器是較為簡潔的配置形式,其實質就是通過Bean注入的形式配置攔截器 接下來我們看一下我們如何通過代碼形式配置攔截器.
配置了核心安全事務管理器 和自定義的權限登錄器.代碼如下所示:
@Configuration
public class ShiroConfiguration {
//配置核心安全事務管理器
@Bean(name="securityManager")
public DefaultWebSecurityManager securityManager(@Qualifier("myShiroRealm") MyShiroRealm myShiroRealm) {
DefaultWebSecurityManager manager=new DefaultWebSecurityManager();
manager.setRealm(myShiroRealm);
return manager;
}
//配置自定義的權限登錄器
@Bean(name="myShiroRealm")
public MyRealm authRealm() {
MyRealm myShiroRealm=new MyRealm();
return myShiroRealm;
}
}
我們繼續配置攔截器模塊的代碼.整體代碼如下所示:
@Bean(name="shiroFilter")
public ShiroFilterFactoryBean shiroFilter(@Qualifier("securityManager") DefaultWebSecurityManager manager) {
ShiroFilterFactoryBean bean=new ShiroFilterFactoryBean();
bean.setSecurityManager(manager);
//配置登錄的url和登錄成功的url以及驗證失敗的url
bean.setLoginUrl("/login");
bean.setSuccessUrl("/home");
bean.setUnauthorizedUrl("/login");
//配置訪問權限
LinkedHashMap<String, String> filterChainDefinitionMap=new LinkedHashMap<>();
filterChainDefinitionMap.put("/loginUser", "anon");
filterChainDefinitionMap.put("/static/*", "anon");
filterChainDefinitionMap.put("/logout*","anon");
bean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return bean;
}
比較過來,依然發現,使用INI檔案形式配置攔截器要比代碼形式更加簡潔.
6.4.簡單使用
現在我們來拿/html/login = anon和/html/home = authc以及設定重定向的authc.loginUrl = /html/login來簡單說明一下,具體應該如何使用.
我們想讓SpringBoot專案支持Html格式的訪問,那么我們需要在pom.xml檔案中加入如下的Maven依賴.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
同時,我們要在application.properties檔案中配置如下資訊.設定HTML的存放路徑
spring.thymeleaf.prefix=classpath:/templates/
spring.mvc.view.suffix=.html
目錄結構如下:

然后,我們把ShiroConfiguration組態檔進行如下的配置:
@Configuration
public class ShiroConfiguration {
@Bean(name="shiroFilter")
public ShiroFilterFactoryBean shiroFilter(@Qualifier("securityManager") DefaultWebSecurityManager manager) {
ShiroFilterFactoryBean bean=new ShiroFilterFactoryBean();
bean.setSecurityManager(manager);
//配置登錄的url和登錄成功的url以及驗證失敗的url
bean.setLoginUrl("/index");
bean.setSuccessUrl("/home");
bean.setUnauthorizedUrl("/index");
//配置訪問權限
LinkedHashMap<String, String> filterChainDefinitionMap=new LinkedHashMap<>();
filterChainDefinitionMap.put("/index", "anon");
filterChainDefinitionMap.put("/home", "authc");
filterChainDefinitionMap.put("/**","anon");
bean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return bean;
}
//配置核心安全事務管理器
@Bean(name="securityManager")
public DefaultWebSecurityManager securityManager() {
DefaultWebSecurityManager manager=new DefaultWebSecurityManager();
return manager;
}
}
我們在resources目錄下創建兩個html頁面,一個login.html,一個home.html,其中代碼如下所示.
login:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>用戶登錄</title>
</head>
<body>
<div style="text-align: center;margin-top: 100px; font-size: 20px;">用戶登錄界面</div>
</body>
</html>
home:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>主頁面</title>
</head>
<body>
<div style="text-align: center;margin-top: 100px; font-size: 20px;">Home界面</div>
</body>
</html>
然后我們需要撰寫兩個介面來訪問login.html和home.html.這里我創建了一個名為HtmlController的控制器.整體代碼較為簡單,這里就直接黏貼出所有代碼了.如下所示:
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.Map;
@Controller
public class HtmlController {
@RequestMapping(value = "/index",method = RequestMethod.GET)
public String userLoginHtmlAction (){
return "index";
}
@RequestMapping(value = "/home",method = RequestMethod.GET)
public String userHomeHtmlAction (){
return "home";
}
}
然后我們就會猜想有如下情況出現,假設我們可以直接通過瀏覽器訪問/html/login,那么我們會直接訪問到login.html ,但是當我們訪問/html/home的時候,由于我們沒有進行用戶登錄認證(根本就沒有做登錄介面.😁),所以我們不可能通過認證,通過登錄重指向,瀏覽器仍然會展示login.html 頁面,下面我們就啟動專案來驗證一下.如下所示,攔截成功.


七、集成Shiro之Shiro標簽(.jsp和.ftl)
7.1.JSP的Shiro標簽
在JSP中使用Shiro標簽比較簡單,我們只需要注意用法即可.下面我就把所以Shiro標簽放在下面了,各位看官自行查考:
<shiro:guest>
游客訪問 <a href = "login.jsp"></a>
</shiro:guest>
user 標簽:用戶已經通過認證\記住我 登錄后顯示回應的內容
<shiro:user>
歡迎[<shiro:principal/>]登錄 <a href = "logout">退出</a>
</shiro:user>
authenticated標簽:用戶身份驗證通過,即 Subjec.login 登錄成功 不是記住我登錄的
<shiro:authenticted>
用戶[<shiro:principal/>] 已身份驗證通過
</shiro:authenticted>
notAuthenticated標簽:用戶未進行身份驗證,即沒有呼叫Subject.login進行登錄,包括"記住我"也屬于未進行身份驗證
<shiro:notAuthenticated>
未身份驗證(包括"記住我")
</shiro:notAuthenticated>
principal 標簽:顯示用戶身份資訊,默認呼叫
Subjec.getPrincipal()獲取,即Primary Principal
<shiro:principal property = "username"/>
hasRole標簽:如果當前Subject有角色將顯示body體內的內容
<shiro:hashRole name = "admin">
用戶[<shiro:principal/>]擁有角色admin
</shiro:hashRole>
hasAnyRoles標簽:如果Subject有任意一個角色(或的關系)將顯示body體里的內容
<shiro:hasAnyRoles name = "admin,user">
用戶[<shiro:pricipal/>]擁有角色admin 或者 user
</shiro:hasAnyRoles>
lacksRole:如果當前 Subjec沒有角色將顯示body體內的內容
<shiro:lacksRole name = "admin">
用戶[<shiro:pricipal/>]沒有角色admin
</shiro:lacksRole>
hashPermission:如果當前Subject有權限將顯示body體內容
<shiro:hashPermission name = "user:create">
用戶[<shiro:pricipal/>] 擁有權限user:create
</shiro:hashPermission>
lacksPermission:如果當前Subject沒有權限將顯示body體內容
<shiro:lacksPermission name = "org:create">
用戶[<shiro:pricipal/>] 沒有權限org:create
</shiro:lacksPermission>
7.2.Freemark的Shiro標簽
1.在SpringBoot里面并不是直接支持JSP檔案的,然后我就在專案中使用了Freemark,但是Shiro標簽并不能直接支持.ftl檔案,所以我們需要先引入一個Maven依賴:
<dependency>
<groupId>net.mingsoft</groupId>
<artifactId>shiro-freemarker-tags</artifactId>
<version>0.1</version>
</dependency>
2.然后,我們寫一個名為ShiroTagsFreeMarkerCfg配置類來對Freemark使用Shiro標簽進行配置.當然了,要確定配置類能被正確注入到Bean中,代碼如下所示:
import com.jagregory.shiro.freemarker.ShiroTags;
import freemarker.template.TemplateModelException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer;
import javax.annotation.PostConstruct;
@Component
public class ShiroTagsFreeMarkerCfg {
@Autowired
private FreeMarkerConfigurer freeMarkerConfigurer;
@PostConstruct
public void setSharedVariable() throws TemplateModelException {
freeMarkerConfigurer.getConfiguration().setSharedVariable("shiro", new ShiroTags());
}
}
3.配置類搞好之后,我們就可以在.ftl檔案中使用Shiro的權限標簽了,標簽格式如下所示:
<@shiro.guest>
游客訪問 <a href = "login.jsp"></a>
</@shiro.guest>
user 標簽:用戶已經通過認證\記住我 登錄后顯示回應的內容
<@shiro.user>
歡迎[<@shiro.principal/>]登錄,<a href="/logout.html">退出</a>
</@shiro.user>
authenticated標簽:用戶身份驗證通過,即 Subjec.login 登錄成功 不是記住我登錄的
<@shiro.authenticated>
用戶[<@shiro.principal/>]已身份驗證通過
</@shiro.authenticated>
notAuthenticated標簽:用戶未進行身份驗證,即沒有呼叫Subject.login進行登錄,包括"記住我"也屬于未進行身份驗證
<@shiro.notAuthenticated>
當前身份未認證(包括記住我登錄的)
</@shiro.notAuthenticated>
principal 標簽:顯示用戶身份資訊,默認呼叫
Subjec.getPrincipal()獲取,即Primary Principal
<@shiro.principal property="username"/>
hasRole標簽:如果當前Subject有角色將顯示body體內的內容
<@shiro.hasRole name="admin">
用戶[<@shiro.principal/>]擁有角色admin<br/>
</@shiro.hasRole>
hasAnyRoles標簽:如果Subject有任意一個角色(或的關系)將顯示body體里的內容
<@shiro.hasAnyRoles name="admin,user,member">
用戶[<@shiro.principal/>]擁有角色admin或user或member<br/>
</@shiro.hasAnyRoles>
lacksRole:如果當前 Subjec沒有角色將顯示body體內的內容
<@shiro.lacksRole name="admin">
用戶[<@shiro.principal/>]不擁有admin角色
</@shiro.lacksRole>
hashPermission:如果當前Subject有權限將顯示body體內容
<@shiro.hasPermission name="user:add">
用戶[<@shiro.principal/>]擁有user:add權限
</@shiro.hasPermission>
lacksPermission:如果當前Subject沒有權限將顯示body體內容
<@shiro.lacksPermission name="user:add">
用戶[<@shiro.principal/>]不擁有user:add權限
</@shiro.lacksPermission>
八、會話管理
會話管理器管理著應用中所有
Subject的會話的創建、維護、洗掉、失效、驗證等作業,是
Shiro 的核心組件,頂層組件SecurityManager直接繼承了SessionManager,且提供了
SessionsSecurityManager 實 現 直 接 把 會 話 管 理 委 托 給 相 應 的SessionManager,
DefaultSecurityManager及DefaultWebSecurityManager默認SecurityManager都繼承了
SessionsSecurityManager,
8.1.會話相關API
①
Subject.getSession()
獲取會話,等價于Subject.getSession(true),即如果當前沒有創建session物件會創建一個;Subject.getSession(false),如果當前沒有創建session物件則回傳null,
②Subject.getSession(true)
③session.getId()
獲取當前會話的唯一標識,
④session.getHost()
獲取當前會話的主機地址,
⑤session.getTimeout() & session.setTimeout(毫秒)
設定/獲取當前Session的過期時間,
⑥session.getStartTimestamp() & session.getLastAccessTime()
獲取會話的啟動時間及最后訪問時間;如果是J2SE環境需要自己定期呼叫session.touch()去更新最后訪問時間;如果是Web環境,每次進入ShiroFilter都會自動呼叫session.touch()來更新最后訪問時間,
⑦session.touch() & session.stop()
更新會話最后訪問時間以及銷毀會話;Subject.logout()會自動呼叫session.stop(),在Web應用中,呼叫HttpSession.invalidate()也會自動呼叫session.stop()來銷毀shiro的會話,
⑧session.setAttribute(key,val) & session.getAttribute(key) & session.removeAttribute(key)
設定/獲取/洗掉 會話屬性,
8.2.會話監聽器(SessionListener介面)
①onStart(Session)
監聽會話創建事件
②onStop(Session)
監聽會話銷毀事件
③onExpiration(Session)
監聽會話過期事件
8.3.SessionDao(會話持久化、提供CRUD操作)

①
AbstractSessionDAO提供了 SessionDAO 的基礎實作,如生成會話ID等,
②CachingSessionDAO提供了對開發者透明的會話快取的功能,需要設定相應的 CacheManager,
③MemorySessionDAO直接在記憶體中進行會話維護,
④EnterpriseCacheSessionDAO提供了快取功能的會話維護,默認情況下使用 MapCache 實作,內部使用ConcurrentHashMap保存快取的會話,
tips:在實際開發中,如果要用到SessionDAO組件,可以自定義類實作自EnterpriseCacheSessionDAO類,為其注入sessionIdGenerator屬性,如果用到快取的話還可以注入一個快取的名字,最后將這個SesionDAO組件注入給SessionManager(會話管理器),最后將SessionManager配置給SecurityManager,下圖是一個完整的配置細節,
8.4.會話驗證
①Shiro提供了會話驗證調度器,用于定期的驗證會話是否已過期,如果過期將停止會話,
②出于性能考慮,一般情況下都是獲取會話的同時來驗證會話是否過期并停止會話的;但是如果在Web環境中,如果用戶不主動退出是不知道會話是否過期的,因此需要定義的檢測會話是否過期,Shiro提供了會話驗證調度器來定期檢查會話是否過期,SessionValidationScheduler,
③Shrio也提供了使用Quartz會話驗證調度器QuartzSessionValidationScheduler,
九、快取
方法一:
在securityManager配置中添加cacheManager配置項,會注入到realm中,

方法二:在realm中配置,
realm本身實作了CacheManagerAware介面
public interface CacheManagerAware {
/**
* Sets the available CacheManager instance on this component.
*
* @param cacheManager the CacheManager instance to set on this component.
*/
void setCacheManager(CacheManager cacheManager);
}
securityManager不只會幫realm注入cacheManager,還會幫sessionManager注入cacheManager
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="cacheManager" ref="redisCacheManager" />
<property name="realm" ref="myRealm" />
<property name="sessionManager" ref="sessionManager"/>
</bean>
這樣的話,realm和sessionManager就不用再配快取
9.1. 集中式快取
基于Redis的集中式快取方案:https://github.com/alexxiyang/shiro-redis
基于Memcached的集中式快取方案:https://github.com/mythfish/shiro-memcached
基于Ehcache集群模式的存放方案:添加鏈接描述
9.2. 本地快取
本地快取的實作有幾種方式:
(1)直接存放到JVM堆記憶體
(2)使用NIO存放在堆外記憶體,自定義實作或者借助于第三方快取組件,
不論是采用集中式快取還是使用本地快取,shiro的權限資料本身都是直接存放在本地的,不同的是快取標志的存放位置,采用本地快取方案是,我們將快取標志也存放在本地,這樣就避免了查詢快取標志的網路請求,能更進一步提升快取效率,
十、shiro修仙進階
本篇對于shiro的學習到這就結束了 如果有不全或者不足的地方
| 說明 | 網址 |
|---|---|
| shiro視頻教程 | shiro視頻教程 |
| shiro系列博客 | shiro系列博客 |
| shiro具體操作 | shiro具體操作 |
| shiro官方檔案 | shiro官方檔案 |
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/299174.html
標籤:java

