Shiro許可權框架

ki16發表於2019-08-01

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>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-cas</artifactId>
<version>1.3.2</version>
</dependency>

 對cas單點登入框架的支援
 shiro-ehcache

<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.3.2</version>
</dependency>

 對echche快取框架的支援
 shiro-hazelcast

<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-hazelcast</artifactId>
<version>1.3.2</version>
</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>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-guice</artifactId>
<version>1.3.2</version>
</dependency>

 對谷歌的guice框架的支援(類似spring的ioc框架)
 shiro-quartz

<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-quartz</artifactId>
<version>1.3.2</version>
</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 授權成功返回的資訊類的實現類

 

相關文章