1 Realm簡介
1.1 Realm作用
shiro最終是通過Realm獲取安全數據的(如用戶、角色、權限),也就是說認證或者授權都會通過Realm進行數據操作
1.2 Realm接口
1.2.1 源代碼
1.2.2 方法說明
》getName:返回一個唯一的 Realm 名字
》supports:判斷此 Realm 是否支持此 Token
》getAuthenticationInfo:根據 Token 獲取認證信息,該方法就是用來實現認證邏輯的(從Realm的實現類org.apache.shiro.realm.AuthenticatingRealm#getAuthenticationInfo中可以看出)
1.3 AuthenticationToken
》層級關系
》關系圖
》開發時一般將用戶名和密碼封裝成一個UsernamePasswordToken對象
1.4 注意
》supports需要對Token類型進行判斷,判斷實參類型是否滿足條件;這里指定的是AuthenticationToken類型(任何Token類型都可以傳入,因為AuthenticationToken是一個父接口),所以在實現類中只需要判斷實參是否是指定的Token類型即可(實際開發時傳入的實參一般都是UsernamePasswordToken類型,所以在supports方法中只需要判定實參是否是這個類型即可)。
》實現認證的邏輯就是寫在org.apache.shiro.realm.Realm#getAuthenticationInfo這個方法中的(從Realm的實現類org.apache.shiro.realm.AuthenticatingRealm#getAuthenticationInfo中可以看出)
1.5 簡易自定義Realm
1.5.1 思路
》實現Realm接口
》getName返回一個唯一的Realm名稱即可
》supports中判定實參類型是否是UsernamePasswordToken類型
》getAuthenticationInfo中實現認證邏輯
1.5.2 代碼實現
package com.xunyji.demo03.shirotest.realm; import org.apache.shiro.authc.*; import org.apache.shiro.realm.Realm; /** * @author AltEnter * @create 2019-01-20 20:11 * @desc 自定義簡易Realm **/ public class MySimpleRealm implements Realm { public String getName() { return "mySimpleRealm"; } public boolean supports(AuthenticationToken token) { if (token instanceof UsernamePasswordToken) { return true; } return false; } public AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { String username = (String) token.getPrincipal(); String password = new String((char[])token.getCredentials()); System.out.println(String.format("用戶名為:%s, 用戶密碼為:%s", username, password)); if (!"fury".equals(username)) { System.out.println("用戶名錯誤"); return null; } if (!"111111".equals(password)) { System.out.println("密碼錯誤"); return null; } return new SimpleAuthenticationInfo(username, password, getName()); } }
1.5.3 單元測試類
package com.xunyji.demo03.shirotest.realm; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.mgt.DefaultSecurityManager; import org.apache.shiro.subject.Subject; import org.junit.Test; import static org.junit.Assert.*; public class MySimpleRealmTest { @Test public void test01() { MySimpleRealm mySimpleRealm = new MySimpleRealm(); DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager(); defaultSecurityManager.setRealm(mySimpleRealm); SecurityUtils.setSecurityManager(defaultSecurityManager); Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken token = new UsernamePasswordToken("fury", "111111"); subject.login(token); System.out.println(String.format("認證結果:%s", subject.isAuthenticated())); } }
2 Realm源碼分析
Realm接口主要是認證的主接口,但是它的一些實現類既可以進行認證也可以進行授權邏輯
2.1 Realm相關類層次圖
todo: 貼圖
2.2 Realm相關類說明
》CachingRealm:帶有緩存實現的Realm,相當於Realm的擴展
》AuthenticatingRealm:專門做認證的Realm,它繼承自CachingRealm
》AuthorizingRealm:專門做授權的Realm,因為它集成自AuthenticatingRealm,所以它也可以實現認證邏輯;自定義的Realm一般都是繼承該類,然后重寫里面的認證方法和授權方法即可。
》IniRealm:用ini文件存儲用戶信息時使用,[users]部分指定用戶名/密碼及其角色; [roles]部分指 定角色即權限信息;
》PropertiesRealm:用properties文件存儲用戶信息時使用,user.username=password,role1,role2 指定用戶 名/密碼及其角色;role.role1=permission1,permission2 指定角色及權限信息;
》JdbcRealm:用數據庫存儲用戶信息時使用,通過 sql 查詢相應的信息,如“select password from users where username = ?”獲取用戶密碼,“select password, password_salt from users where username = ?”獲取用戶密碼及鹽;“select role_name from user_roles where username = ?” 獲取用戶角色;“select permission from roles_permissions where role_name = ?”獲取角色對 應的權限信息;也可以調用相應的 api 進行自定義 sql;
2.3 AuthenticatingRealm詳解
》getAuthenticationInfo:該方法是Realm中getAuthenticationInfo的實現,該方法是實現認證邏輯的;該方法是一個final方法,所以AuthenticatingRealm的子類不能重寫該方法;
》doGetAuthenticationInfo:getAuthenticationInfo方法調用doGetAuthenticationInfo實現認證邏輯,該方法是一個protected方法,專門暴露給子類進行重寫的,而且是子類實現認證邏輯必須重寫的方法。
2.4 Realm主要實現類
2.4.1 IniRealm
》用戶相關信息存儲在一個ini文件中
2.4.2 PropertiesRealm
》用戶相關信息存儲在一個properties文件中
2.4.3 JdbcRealm
》用戶信息存儲在數據庫中
2.5 IniRealm使用教程
2.5.1 繼承關系圖
2.5.2 創建一個maven工程並引入shiro、junit相關依賴
<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>1.4.0</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency>
2.5.3 在resources目錄下創建ini文件用於存放用戶信息
》項目目錄結構
https://blog.csdn.net/u011781521/article/details/74892074
》ini文件內容
[users]
fury=111111,role1,role2
zeus=222222,role1
[roles]
role1=user:delete,user:update,user:create,user:read
role2=car:create
》測試代碼
package com.xunyji.demo03.shirotest.realm; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.mgt.DefaultSecurityManager; import org.apache.shiro.realm.text.IniRealm; import org.apache.shiro.subject.Subject; import org.junit.Test; import java.util.ArrayList; import java.util.Arrays; /** * @author AltEnter * @create 2019-01-17 22:07 * @desc IniRealm測試類 **/ public class IniRealmDemo { @Test public void iniRealmTest() { // 01 創建Realm IniRealm iniRealm = new IniRealm("classpath:shiro.ini"); // 02 創建SecurityManager並將Realm設置到SecurityManager中 DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager(); defaultSecurityManager.setRealm(iniRealm); // 03 將SecurityManager設置到SecurityUtils中 SecurityUtils.setSecurityManager(defaultSecurityManager); // 04 從SecurityUtils中獲取Subject Subject subject = SecurityUtils.getSubject(); // 05 將用戶名和用戶密碼封裝成一個Token UsernamePasswordToken token = new UsernamePasswordToken("zeus", "222222"); // 06 通過Subject進行登錄認證 try { subject.login(token); } catch (Exception e) { e.printStackTrace(); } // 07 通過Subject判斷登錄認證結果 System.out.println(String.format("認證結果為:%s", subject.isAuthenticated())); ArrayList<String> roleList = new ArrayList<String>(); roleList.add("role1"); System.out.println("是否有role1角色:" + Arrays.toString(subject.hasRoles(roleList))); System.out.println("是否有role1角色:" + subject.hasRole("role1")); subject.checkPermission("user:create"); } }
2.6 PropertiesRealm使用教程
參見2.5 + 百度
2.7 JdbcRealm使用教程
》繼承關系圖
》引入shiro、junit、mysql、druid依賴
<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>1.4.0</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.45</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.6</version> </dependency>
》根據JdbcRealm源碼創建相關表的SQL

/* Navicat MySQL Data Transfer Source Server : mysql5.4 Source Server Version : 50540 Source Host : localhost:3306 Source Database : shiro Target Server Type : MYSQL Target Server Version : 50540 File Encoding : 65001 Date: 2019-01-17 21:07:56 */ SET FOREIGN_KEY_CHECKS=0; -- ---------------------------- -- Table structure for `roles_permissions` -- ---------------------------- DROP TABLE IF EXISTS `roles_permissions`; CREATE TABLE `roles_permissions` ( `id` int(11) NOT NULL AUTO_INCREMENT, `role_name` varchar(255) NOT NULL, `permission` varchar(255) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4; -- ---------------------------- -- Records of roles_permissions -- ---------------------------- INSERT INTO `roles_permissions` VALUES ('1', 'admin', 'user:update'); -- ---------------------------- -- Table structure for `user_roles` -- ---------------------------- DROP TABLE IF EXISTS `user_roles`; CREATE TABLE `user_roles` ( `id` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(255) NOT NULL, `role_name` varchar(255) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4; -- ---------------------------- -- Records of user_roles -- ---------------------------- INSERT INTO `user_roles` VALUES ('1', 'fury', 'admin'); INSERT INTO `user_roles` VALUES ('2', 'fury', 'user'); -- ---------------------------- -- Table structure for `users` -- ---------------------------- DROP TABLE IF EXISTS `users`; CREATE TABLE `users` ( `id` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(255) NOT NULL, `password` varchar(255) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4; -- ---------------------------- -- Records of users -- ---------------------------- INSERT INTO `users` VALUES ('1', 'fury', '1111');
》測試類
package com.xunyji.demo03.shirotest.realm; import com.alibaba.druid.pool.DruidDataSource; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.mgt.DefaultSecurityManager; import org.apache.shiro.realm.jdbc.JdbcRealm; import org.apache.shiro.subject.Subject; import org.junit.Test; /** * @author AltEnter * @create 2019-01-20 21:07 * @desc JdbcRealm使用Demo **/ public class JdbcRealmDemo { DruidDataSource data =new DruidDataSource(); { data.setUrl("jdbc:mysql://localhost:3306/shiro?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC"); data.setUsername("root"); data.setPassword("182838"); } @Test public void testAuthentication(){ JdbcRealm jdbcRealm = new JdbcRealm(); jdbcRealm.setDataSource(data); jdbcRealm.setPermissionsLookupEnabled(true); // 開啟權限查詢功能 String sql="select password from users where username= ?"; jdbcRealm.setAuthenticationQuery(sql); String roleSql ="select role_name from user_roles where username = ?"; jdbcRealm.setUserRolesQuery(roleSql); //1.構建securtymanager DefaultSecurityManager manager = new DefaultSecurityManager(); manager.setRealm(jdbcRealm); //2.主體提交認證請求 SecurityUtils.setSecurityManager(manager); Subject subject = SecurityUtils.getSubject(); // UsernamePasswordToken token =new UsernamePasswordToken("Mark","123456"); UsernamePasswordToken token =new UsernamePasswordToken("fury","111111"); subject.login(token); //是否認證的一個方法 boolean authenticated = subject.isAuthenticated(); System.out.println("authenticated==============="+authenticated); subject.checkRole("user"); subject.checkRole("admin"); subject.checkPermission("user:update"); } }
2.8 SimpleAccountRealm使用教程
》層次關系圖
》說明
這種類型是通過硬編碼來存儲用戶數據的
》測試代碼
package com.xunyji.demo03.shirotest.realm; import com.sun.org.apache.bcel.internal.generic.NEW; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.mgt.DefaultSecurityManager; import org.apache.shiro.realm.SimpleAccountRealm; import org.apache.shiro.subject.Subject; import org.junit.Before; import org.junit.Test; /** * @author AltEnter * @create 2019-01-20 21:15 * @desc SimpleAccountRealm使用Demo類 **/ public class SimpleAccountRealmDemo { private SimpleAccountRealm simpleAccountRealm = new SimpleAccountRealm(); @Before public void addUser() { simpleAccountRealm.addAccount("fury", "111111"); } @Test public void test01() { DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager(); defaultSecurityManager.setRealm(simpleAccountRealm); SecurityUtils.setSecurityManager(defaultSecurityManager); Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken token = new UsernamePasswordToken("fury", "111111"); subject.login(token); System.out.println(String.format("認證結果為:%s", subject.isAuthenticated())); subject.logout(); System.out.println(String.format("認證結果為:%s", subject.isAuthenticated())); } }