1.Shiro是什么
Shiro是一個非常強大的、易於使用的、開源的權限框架(安全框架)。它包括了權限校驗、權限授予、會話管理、安全加密等組件。
2.為什么需要使用Shiro
在設計RBAC(Role Based Access Control)基礎系統時,需要編寫大量用於權限控制的代碼。如果使用Shiro就可以大大減少我們的工作量。因為Shiro已經將RBAC系統大量的代碼封裝好。
如:頁面的顯示的HTML控件根據登錄用戶的權限不同而不同。使用Shiro可以輕松解決。
3.Shiro的下載
shiro的下載路徑:http://shiro.apache.org/download.html
Shiro包說明
使用時根據列表的說明,下載需要的jar包即可
| 包 名 | Maven 坐標 | 說 明 |
| shiro-all | 不推薦采用 | 包含Shiro的所有功能 |
| shiro-core |
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.3.2</version>
</dependency>
|
只要使用shiro必須的核心包,它依賴slf4j 和 commons-beanutils 以及還需要一個INI配置文件 |
| shiro-web | <dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>1.3.2</version>
</dependency>
|
支持基於Web的應用 |
| shiro-aspectj | <dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-aspectj</artifactId>
<version>1.3.2</version>
</dependency>
|
AspectJ對Shiro AOP和注釋的支持 |
| shiro-cas | <dependency> |
對cas單點登錄框架的支持 |
| shiro-ehcache | <dependency> |
對echche緩存框架的支持 |
| shiro-hazelcast | <dependency> |
對hazelcast的famework緩存的支持 |
| shiro-features |
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-features</artifactId>
<version>1.3.2</version>
</dependency>
|
Karaf 的集成 |
| shiro-guice | <dependency> |
對谷歌的guice框架的支持(類似spring的ioc框架) |
| shiro-quartz | <dependency> |
對quartz定時任務調度框架的支持 |
| shiro-spring |
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.3.2</version>
</dependency>
|
支持Spring框架集成。 |
標紅部分為常用包
4.Shiro結構圖

Authentication:權限校驗,每次操作校驗用戶是否有訪問權限
Authorization:授權,用戶登錄時,授予用戶對應的權限
Session Management:會話管理,用於記錄用戶的登錄狀態
Cryptography:加密,加密算法的實現(SHA、MD5)
web Support:對Web項目的支持,Shiro的標簽!
5.Shiro入門
5.1 訪問流程圖
登錄流程:

1.應用訪問(如Web請求等)
2.Shiro根據訪問請求創建一個Subject對象來標識當前訪問的身份。
3.SecurityManger 加載配置文件進行身份校驗(驗證賬號密碼)
4.身份校驗通過后SecurityManger 會根據配置文件授予當前Subject對象對應的用戶權限
5.2 入門示例
配置步驟:
第一步:導入jar包

第二步:shiro.ini配置文件
創建一個shiro.ini配置文件,編寫權限認證信息。
注:1.shiro.ini文件名可以任意編寫,但后綴必須是ini
2.shiro.ini配置文件放在classpath根目錄下
shiro.ini文件配置規則說明:
[main] #用於配置SecurityManager里面的對象 對象名=類全限制名 對象名.屬性[.屬性...] = 值 [users] #用於配置用戶名信息 用戶名= 密碼, 角色1, 角色2, …, 角色N [roles] #用於配置角色信息 角色名= 權限1, 權限2, …, 權限N #全部權限使用 * (星號) [urls] #用於配置路徑攔截規則
權限命名格式建議:權限:操作:操作
shiro.ini配置:
##用戶信息 [users] admin=123456,role_admin,role_user ##角色信息 [roles] role_admin=* role_user=modular:to_add,modular:add
測試:
package com.gjs.shiro.test; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.config.IniSecurityManagerFactory; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.subject.Subject; public class ShiroTest { public static void main(String[] args) { //第一步:讀取配置文件創建安全管理器 IniSecurityManagerFactory factory =new IniSecurityManagerFactory("classpath:shiro.ini"); SecurityManager securityManager = factory.createInstance(); //第二步:設置SecurityUtils的安全管理器 SecurityUtils.setSecurityManager(securityManager); //第三步:獲得一個沒有權限身份對象 Subject subject = SecurityUtils.getSubject(); //第四步:構建驗證信息token UsernamePasswordToken token = new UsernamePasswordToken("admin", "123456"); //第四步:身份校驗(驗證賬號密碼) try { Subject resultSubject = securityManager.login(subject, token); System.out.println("校驗通過"); System.out.println("用戶名:"+resultSubject.getPrincipal()); System.out.println("驗證授權:"+resultSubject.isPermitted("modular:add")); } catch (AuthenticationException e) { System.out.println("校驗失敗,用戶名或者密碼不正確"); e.printStackTrace(); } } }
API說明:
IniSecurityManagerFactory:作用加載ini配置文件獲得SecurityManagerFactory對象
SecurityManager:安全管理容器,就是否則整個Shiro框架授權校驗對象的管理
SecurityUtils :SecurityManager對象幫助類
Subject:驗證通過后用於存儲授權信息的身份對象
UsernamePasswordToken :用於設置校驗信息的對象
IncorrectCredentialsException :密碼出錯異常
UnknownAccountException:用戶名出錯異常
6.Realm的使用
在入門示例中,用戶驗證信息來自於ini配置文,這樣的難以符合我們實際的需求。而在實際開發中,我們的用戶信息是存儲在數據庫里面,再從數據庫里面讀取出來。
Shiro是通過Realm機制,實現將配置文件的校驗用戶信息存放在數據庫等數據存儲系統里面。
6.1 訪問流程圖

如圖所示,我們需要在ini配置文件中配置Realm對象,再在Realm中進行權限驗證以及授權
6.2 示例:
配置步驟:
第一步:導入jar包

第二步:編寫Realm
package com.gjs.shiro.realm; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.SimpleAuthenticationInfo; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; /** * 我們自定義Realm繼承授權的Realm AuthorizingRealm。因為授權包括校驗。 * @author gjs * */ public class MyRealm extends AuthorizingRealm{ /** * 權限校驗: 就是驗證訪問者(subject).是否使用有使用權限的身份,即驗證賬號密碼。驗證通過回AuthenticationInfo對象 */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { System.out.println("權限校驗"); System.out.println("用戶名:" + token.getPrincipal()); if (token.getPrincipal().equals("admin")) { //參數1:用於存儲用戶信息,可以填充給Subject對象 //參數2:校驗的密碼。注意Shiro的校驗是SimpleAuthenticationInfo內部完成的。 //參數3:Realm名字,用來標識Realm return new SimpleAuthenticationInfo(token.getPrincipal(), "123456", this.getName()); } return null; } /** * 權限授予:根據通過校驗的身份(subject)將查詢到的權限信息封裝在AuthorizationInfo里面返回 */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); info.addStringPermission("modular:add");//添加權限 info.addRole("RoleAdmin");//添加角色 return info; } }
第三步:創建shiro.ini配置文件
[main] ##聲明Realm對象 myRealm=com.gjs.shiro.realm.MyRealm ##配置securityManager的realm對象。 對象引用需要在對象名前面加上 $ securityManager.realms=$myRealm
第四步:編寫測試類
package com.gjs.shiro.test; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.config.IniSecurityManagerFactory; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.subject.Subject; public class ShiroTest { public static void main(String[] args) { //第一步:讀取配置文件創建安全管理器 IniSecurityManagerFactory factory =new IniSecurityManagerFactory("classpath:shiro.ini"); SecurityManager securityManager = factory.createInstance(); //第二步:設置SecurityUtils的安全管理器 SecurityUtils.setSecurityManager(securityManager); //第三步:獲得一個沒有權限身份對象 Subject subject = SecurityUtils.getSubject(); //第四步:構建驗證信息token UsernamePasswordToken token = new UsernamePasswordToken("admin", "123456"); //第四步:身份校驗(驗證賬號密碼) try { Subject resultSubject = securityManager.login(subject, token); System.out.println("校驗通過"); System.out.println("用戶名:"+resultSubject.getPrincipal()); System.out.println("驗證授權:"+resultSubject.isPermitted("modular:add")); System.out.println("是否有RoleAdmin角色:"+resultSubject.hasRole("RoleAdmin")); } catch (AuthenticationException e) { System.out.println("校驗失敗,用戶名或者密碼不正確"); e.printStackTrace(); } } }
6.3 加密
在開發中我們需要對密碼進行加密,而我們自己編寫的加密工具類無法傳遞給SimpleAuthenticationInfo對象,作為密碼校驗。所以就要用到shiro框架自帶的密碼加密的功能。
SimpleHash類:用於生成指定的Hash算法。
HashedCredentialsMatcher類:用於讓Realm校驗時,校驗指定的Hash算法
ByteSource 用於給Hash算法加鹽的
示例:
生成md5密碼
package com.gjs.shiro.test; import org.apache.shiro.crypto.hash.Md5Hash; import org.apache.shiro.util.ByteSource; /** * 用來創建加密后的密碼,參數與校驗器的參數保持一致 * @author gjs * */ public class Md5Util { public static void main(String[] args) { ByteSource salt = ByteSource.Util.bytes("gjs"); Md5Hash md5=new Md5Hash("123456", salt, 3); String password = md5.toString(); System.out.println(password); } }
修改ini配置文件
[main] ##聲明Realm對象 myRealm=com.gjs.shiro.realm.MyRealm #加密的對象 credentialsMatcher=org.apache.shiro.authc.credential.HashedCredentialsMatcher ##指定加密算法. 屬性對應的是set方法 credentialsMatcher.hashAlgorithmName=md5 ##算法是否加鹽 credentialsMatcher.hashSalted=true ##加密次數 credentialsMatcher.hashIterations=3 ##指定加密的校驗器給MyReam myRealm.credentialsMatcher=$credentialsMatcher ##配置securityManager的realm對象。 對象引用需要在對象名前面加上 $ securityManager.realms=$myRealm
修改Realm類:
package com.gjs.shiro.realm; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.SimpleAuthenticationInfo; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import org.apache.shiro.util.ByteSource; /** * 我們自定義Realm繼承授權的Realm AuthorizingRealm。因為授權包括校驗。 * @author gjs * */ public class MyRealm extends AuthorizingRealm{ /** * 權限校驗: 就是驗證訪問者(subject).是否使用有使用權限的身份,即驗證賬號密碼。驗證通過回AuthenticationInfo對象 */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { System.out.println("權限校驗"); System.out.println("用戶名:" + token.getPrincipal()); if (token.getPrincipal().equals("admin")) { ByteSource salt = ByteSource.Util.bytes("gjs"); //參數1:用於存儲用戶信息,可以填充給Subject對象 //參數2:校驗的密碼。注意Shiro的校驗是SimpleAuthenticationInfo內部完成的。 //參數3:Realm名字,用來標識Realm return new SimpleAuthenticationInfo(token.getPrincipal(), "a0af233bfd499995a8c1bacc4f61c489",salt, this.getName()); } return null; } /** * 權限授予:根據通過校驗的身份(subject)將查詢到的權限信息封裝在AuthorizationInfo里面返回 */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); info.addStringPermission("modular:add");//添加權限 info.addRole("RoleAdmin");//添加角色 return info; } }
6.4 返回的認證信息為一個實體(JavaBean、Map)
上面代碼校驗后返回的認證信息是一個字符串的用戶名,而我們如果將Shiro的校驗功能用到登錄的邏輯里面,明顯需要返回的不是一個用戶名,而是用戶的信息。
用戶的信息,我們需要用一個實體類來封裝。可以是JavaBean或者是Map
我們上面寫的校驗方法返回的SimpleAuthenticationInfo的構建方法的第一個參數就是用於指定,返回的用戶認證信息的。可以將用戶名修改為一個我們指定的實體類對象就可以了
pojo實體類:
package com.gjs.shiro.pojo; import java.util.Date; public class User { private int id; private String name; private String password; private Date createDate; private int status; private Role role; public Role getRole() { return role; } public void setRole(Role role) { this.role = role; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public Date getCreateDate() { return createDate; } public void setCreateDate(Date createDate) { this.createDate = createDate; } public int getStatus() { return status; } public void setStatus(int status) { this.status = status; } @Override public String toString() { return "User [id=" + id + ", name=" + name + ", password=" + password + ", createDate=" + createDate + ", status=" + status + ", role=" + role + "]"; } }
package com.gjs.shiro.pojo; import java.util.List; public class Role { private int roleId; private String roleName; private List<Perm> rolePerms; public int getRoleId() { return roleId; } public void setRoleId(int roleId) { this.roleId = roleId; } public String getRoleName() { return roleName; } public void setRoleName(String roleName) { this.roleName = roleName; } public List<Perm> getRolePerms() { return rolePerms; } public void setRolePerms(List<Perm> rolePerms) { this.rolePerms = rolePerms; } @Override public String toString() { return "Role [roleId=" + roleId + ", roleName=" + roleName + ", rolePerms=" + rolePerms + "]"; } }
package com.gjs.shiro.pojo; public class Perm { private int permId; private String permName; private String permAction; private String permKey; public int getPermId() { return permId; } public void setPermId(int permId) { this.permId = permId; } public String getPermName() { return permName; } public void setPermName(String permName) { this.permName = permName; } public String getPermAction() { return permAction; } public void setPermAction(String permAction) { this.permAction = permAction; } public String getPermKey() { return permKey; } public void setPermKey(String permKey) { this.permKey = permKey; } @Override public String toString() { return "Perm [permId=" + permId + ", permName=" + permName + ", permAction=" + permAction + ", permKey=" + permKey + "]"; } }
修改Realm
package com.gjs.shiro.realm; import java.util.ArrayList; import java.util.Date; import java.util.List; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.SimpleAuthenticationInfo; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import org.apache.shiro.util.ByteSource; import com.gjs.shiro.pojo.Perm; import com.gjs.shiro.pojo.Role; import com.gjs.shiro.pojo.User; /** * 我們自定義Realm繼承授權的Realm AuthorizingRealm。因為授權包括校驗。 * @author gjs * */ public class MyRealm extends AuthorizingRealm{ /** * 權限校驗: 就是驗證訪問者(subject).是否使用有使用權限的身份,即驗證賬號密碼。驗證通過回AuthenticationInfo對象 */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { System.out.println("權限校驗"); User user=new User(); //此處的數據應從數據庫查出來 user.setId(1); user.setName((String)token.getPrincipal()); user.setStatus(0); user.setCreateDate(new Date()); if (token.getPrincipal().equals(user.getName())) { ByteSource salt = ByteSource.Util.bytes("gjs"); //參數1:用於設置認證信息,返回給調用對象的 //參數2:校驗的密碼。注意Shiro的校驗是SimpleAuthenticationInfo內部完成的。 //參數3:密碼的鹽 //參數4:Realm名字,用來標識Realm return new SimpleAuthenticationInfo(user, "a0af233bfd499995a8c1bacc4f61c489",salt, this.getName()); } return null; } /** * 權限授予:根據通過校驗的身份(subject)將查詢到的權限信息封裝在AuthorizationInfo里面返回 */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { //獲取認證信息 User user = (User) principals.getPrimaryPrincipal(); //將授權信息也存到當前用戶對象里面(值傳遞) //角色 Role role=new Role(); role.setRoleId(1); role.setRoleName("RoleAdmin"); user.setRole(role); //權限 List<Perm> perms=new ArrayList<>(); Perm perm1=new Perm(); perm1.setPermId(1); perm1.setPermName("用戶管理"); perm1.setPermAction("/user/toUserList"); perm1.setPermKey("user:to_edit"); perms.add(perm1); role.setRolePerms(perms); SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); info.addStringPermission(user.getRole().getRolePerms().get(0).getPermKey());//添加權限 info.addRole(user.getRole().getName());//添加角色 return info; } }
測試類:
package com.gjs.shiro.test; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.config.IniSecurityManagerFactory; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.subject.Subject; import com.gjs.shiro.pojo.User; public class ShiroTest { public static void main(String[] args) { //第一步:讀取配置文件創建安全管理器 IniSecurityManagerFactory factory =new IniSecurityManagerFactory("classpath:shiro.ini"); SecurityManager securityManager = factory.createInstance(); //第二步:設置SecurityUtils的安全管理器 SecurityUtils.setSecurityManager(securityManager); //第三步:獲得一個沒有權限身份對象 Subject subject = SecurityUtils.getSubject(); //第四步:構建驗證信息token UsernamePasswordToken token = new UsernamePasswordToken("admin", "123456"); //第四步:身份校驗(驗證賬號密碼) try { Subject resultSubject = securityManager.login(subject, token); System.out.println("校驗通過"); User user=(User) resultSubject.getPrincipal();//獲取認證信息 System.out.println("用戶名:"+user.getName()); System.out.println("驗證授權:"+resultSubject.isPermitted("modular:add")); System.out.println("是否有RoleAdmin角色:"+resultSubject.hasRole("RoleAdmin")); System.out.println("獲得角色:"+user.getRole()); System.out.println("獲得第一個權限:"+user.getRole().getRolePerms().get(0)); } catch (AuthenticationException e) { System.out.println("校驗失敗,用戶名或者密碼不正確"); e.printStackTrace(); } } }
7.常用API:
IniSecurityManagerFactory : 用於加載配置文件,創建SecurityManager對象
SecurityManager :就是整個Shiro的控制對象
SecurityUtils :SecurityManager 工具類,用於獲得Subject對象
Subject :身份類,存儲返回的數據信息、提供了校驗的權限的方法
UsernamePasswordToken 身份信息構建類 (Token 令牌,作用就是傳入校驗參數)
AuthorizingRealm 支持校驗與授權的Realm
AuthenticationInfo 校驗成功返回的信息的父接口
SimpleAuthenticationInfo 校驗成功返回信息類
Md5Hash Md5加密類
ByteSource 字節碼處理工具類,我們在構造Md5加鹽時使用到。
HashedCredentialsMatcher Md5算法校驗器,用於支持Md5校驗
AuthorizationInfo 授權成功返回的信息類的父接口
PrincipalCollection 授予是獲得驗證信息的類
SimpleAuthorizationInfo 授權成功返回的信息類的實現類
