Shiro安全框架 的使用 & Spring 整合 Shiro [1]

基本使用

什么是權(quán)限管理?

基本上涉及到用戶參與的系統(tǒng)都要進(jìn)行權(quán)限管理,權(quán)限管理屬于系統(tǒng)安全的范疇,權(quán)限管理能實現(xiàn)對用戶訪問系統(tǒng)的控制个绍,按照安全規(guī)則或者安全策略限制用戶操作,只允許用戶訪問被授權(quán)的資源浪汪。權(quán)限管理包括用戶身份認(rèn)證和授權(quán)兩部分,簡稱認(rèn)證授權(quán)

身份認(rèn)證
就是判斷一個用戶是否為合法用戶的處理過程.醉常用的簡單身份認(rèn)證方式是系統(tǒng)通過核對用戶輸入的用戶名和口令,看其是否與系統(tǒng)中存儲的該用戶的用戶名和口令一致,來判斷用戶身份是否正確.對于采用指紋等系統(tǒng),則出示指紋凛虽;對于硬件Key等刷卡系統(tǒng),則需要刷卡.

身份認(rèn)證

認(rèn)證

上邊的流程圖中需 要理解以下關(guān)鍵對象:
Subject:主體 user
訪問系統(tǒng)的用戶死遭,主體可以是用戶、程序等凯旋,進(jìn)行認(rèn)證的都稱為主體呀潭;
Principal:身份信息(username)
是主體(subject)進(jìn)行身份認(rèn)證的標(biāo)識,標(biāo)識必須具有唯一性至非,如用戶名钠署、手機(jī)號、郵箱地址等荒椭,一個主體可以有多個身份谐鼎,但是必須有一個主身份(Primary Principal)。
credential:憑證信息(password)
是只有主體自己知道的安全信息趣惠,如密碼狸棍、證書等。

授權(quán)味悄,即訪問控制草戈,控制誰能訪問哪些資源。主體進(jìn)行身份認(rèn)證后需要分配權(quán)限方可訪問系統(tǒng)的資源侍瑟,對于某些資源沒有權(quán)限是無法訪問的唐片。

Apache Shiro是Java的一個安全框架。幫助我們完成:認(rèn)證涨颜、授權(quán)费韭、加密、會話管理咐低、與Web集成揽思、緩存等。

功能圖

Authentication:身份認(rèn)證/登錄见擦,驗證用戶是不是擁有相應(yīng)的身份钉汗;

Authorization:授權(quán)羹令,即權(quán)限驗證,驗證某個已認(rèn)證的用戶是否擁有某個權(quán)限损痰;即判斷用戶是否能做事情福侈,常見的如:驗證某個用戶是否擁有某個角色÷矗或者細(xì)粒度的驗證某個用戶對某個資源是否具有某個權(quán)限肪凛;

Session Manager:會話管理,即用戶登錄后就是一次會話辽社,在沒有退出之前伟墙,它的所有信息都在會話中;會話可以是普通JavaSE環(huán)境的滴铅,也可以是Web環(huán)境的戳葵;

Cryptography:加密,保護(hù)數(shù)據(jù)的安全性汉匙,如密碼加密存儲到數(shù)據(jù)庫拱烁,而不是明文存儲;

Web Support:Web支持噩翠,可以非常容易的集成到Web環(huán)境戏自;

Caching:緩存,比如用戶登錄后伤锚,其用戶信息擅笔、擁有的角色/權(quán)限不必每次去查,這樣可以提高效率见芹;

Concurrency:shiro支持多線程應(yīng)用的并發(fā)驗證剂娄,即如在一個線程中開啟另一個線程,能把權(quán)限自動傳播過去玄呛;

Testing:提供測試支持阅懦;

Run As:允許一個用戶假裝為另一個用戶(如果他們允許)的身份進(jìn)行訪問;

Remember Me:記住我徘铝,這個是非常常見的功能耳胎,即一次登錄后,下次再來的話不用登錄了惕它。


Shiro架構(gòu)有三個主要概念 - Subject怕午,SecurityManager,Realms

架構(gòu)

Subject : 訪問系統(tǒng)的用戶淹魄,主體可以是用戶郁惜、程序等,進(jìn)行認(rèn)證的都稱為主體甲锡;
Subject 一詞是一個安全術(shù)語兆蕉,其基本意思是“當(dāng)前的操作用戶”羽戒。它是一個抽象的概念,可以是人虎韵,也可以是第三方進(jìn)程或其他類似事物易稠,如爬蟲,機(jī)器人等包蓝。

在程序任意位置:Subject currentUser = SecurityUtils.getSubject(); 獲取shiro 一旦獲得Subject驶社,你就可以立即獲得你希望用Shiro為當(dāng)前用戶做的90%的事情,如登錄测萎、登出亡电、訪問會話、執(zhí)行授權(quán)檢查等

SecurityManager
安全管理器硅瞧,它是shiro功能實現(xiàn)的核心逊抡,負(fù)責(zé)與后邊介紹的其他組件(認(rèn)證器/授權(quán)器/緩存控制器)進(jìn)行交互,實現(xiàn)subject委托的各種功能零酪。有點類似于spirngmvc中的DispatcherServlet前端控制器。

Realms
Realm充當(dāng)了Shiro與應(yīng)用安全數(shù)據(jù)間的“橋梁”或者“連接器”拇勃。四苇;可以把Realm看成DataSource,即安全數(shù)據(jù)源方咆。執(zhí)行認(rèn)證(登錄)和授權(quán)(訪問控制)時月腋,Shiro會從應(yīng)用配置的Realm中查找相關(guān)的比對數(shù)據(jù)。以確認(rèn)用戶是否合法瓣赂,操作是否合理

從系統(tǒng)結(jié)構(gòu)角度看:shiro

系統(tǒng)結(jié)構(gòu)角度

Subject:主體榆骚,可以看到主體可以是任何可以與應(yīng)用交互的“用戶”;

SecurityManager:相當(dāng)于SpringMVC中的DispatcherServlet或者Struts2中的

FilterDispatcher煌集;是Shiro的心臟妓肢;所有具體的交互都通過SecurityManager進(jìn)行控制;它管理著所有Subject苫纤、且負(fù)責(zé)進(jìn)行認(rèn)證和授權(quán)碉钠、及會話、緩存的管理卷拘。

Authenticator:認(rèn)證器喊废,負(fù)責(zé)主體認(rèn)證的,這是一個擴(kuò)展點栗弟,如果用戶覺得Shiro默認(rèn)的不好污筷,可以自定義實現(xiàn);其需要認(rèn)證策略(Authentication Strategy)乍赫,即什么情況下算用戶認(rèn)證通過了瓣蛀;

Authorizer:授權(quán)器陆蟆,或者訪問控制器,用來決定主體是否有權(quán)限進(jìn)行相應(yīng)的操作揪惦;即控制著用戶能訪問應(yīng)用中的哪些功能遍搞;

Realm:可以有1個或多個Realm,可以認(rèn)為是安全實體數(shù)據(jù)源器腋,即用于獲取安全實體的溪猿;可以是JDBC實現(xiàn),也可以是LDAP實現(xiàn)纫塌,或者內(nèi)存實現(xiàn)等等诊县;由用戶提供;注意:Shiro不知道你的用戶/權(quán)限存儲在哪及以何種格式存儲措左;所以我們一般在應(yīng)用中都需要實現(xiàn)自己的Realm依痊;

SessionManager:如果寫過Servlet就應(yīng)該知道Session的概念,Session呢需要有人去管理它的生命周期怎披,這個組件就是SessionManager胸嘁;而Shiro并不僅僅可以用在Web環(huán)境,也可以用在如普通的JavaSE環(huán)境凉逛、EJB等環(huán)境性宏;所有呢,Shiro就抽象了一個自己的Session來管理主體與應(yīng)用之間交互的數(shù)據(jù)状飞;可以實現(xiàn)分布式的會話管理毫胜;

SessionDAO:DAO大家都用過,數(shù)據(jù)訪問對象诬辈,用于會話的CRUD酵使,比如我們想把Session保存到數(shù)據(jù)庫,那么可以實現(xiàn)自己的SessionDAO焙糟,通過如JDBC寫到數(shù)據(jù)庫口渔;比如想把Session放到redis中,可以實現(xiàn)自己的redis SessionDAO穿撮;另外SessionDAO中可以使用Cache進(jìn)行緩存搓劫,以提高性能;

CacheManager:緩存控制器混巧,來管理如用戶枪向、角色、權(quán)限等的緩存的咧党;因為這些數(shù)據(jù)基本上很少去改變秘蛔,放到緩存中后可以提高訪問的性能

Cryptography:密碼模塊,Shiro提高了一些常見的加密組件用于如密碼加密/解密的。

入門案例
認(rèn)證流程

創(chuàng)建 maven 項目 添加依賴

  <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</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>

添加shiro所必須的配置文件信息
log4j.properties

log4j.rootLogger=INFO, stdout

log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m %n

# General Apache libraries
log4j.logger.org.apache=WARN

# Spring
log4j.logger.org.springframework=WARN

# Default Shiro logging
log4j.logger.org.apache.shiro=TRACE

# Disable verbose logging
log4j.logger.org.apache.shiro.util.ThreadContext=WARN
log4j.logger.org.apache.shiro.cache.ehcache.EhCache=WARN

shiro.ini

[users]
#模擬數(shù)據(jù)庫用戶
zhangsan=666

hello world 代碼

package cn.icanci.shiro.helloworld;

import org.apache.shiro.SecurityUtils;
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 org.junit.Test;
import sun.security.smartcardio.SunPCSC;

/**
 * @Author: icanci
 * @ProjectName: shiro
 * @PackageName: cn.icanci.shiro.helloworld
 * @Date: Created in 2020/2/25 11:13
 * @ClassAction: 測試 Shiro 驗證
 */
public class TestShiro {

    @Test
    public void testLogin() throws Exception {
        //1.創(chuàng)建 SecurityManager工廠對象:加載配置文件 創(chuàng)建工廠對象
        IniSecurityManagerFactory factory = new IniSecurityManagerFactory("classpath:shiro.ini");
        //2.通過工廠對象 創(chuàng)建SecurityManager對象
        SecurityManager instance = factory.getInstance();
        //3.將SecurityManager綁定到當(dāng)選運(yùn)行環(huán)境中,目的使系統(tǒng)隨時隨地都可以訪問
        SecurityUtils.setSecurityManager(instance);
        //4.創(chuàng)建當(dāng)前登陸的主體 注意 此時主題沒有經(jīng)過認(rèn)證
        Subject subject = SecurityUtils.getSubject();
        //5.綁定主題所需的身份 參數(shù)1 用戶名 參數(shù)2 密碼
        UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("zhangsan", "6626");
        //6.主體登陸
        subject.login(usernamePasswordToken);
        //7.判斷身份登陸成功
        System.out.println("判斷身份登陸成功:" + subject.isAuthenticated());
        //7.判斷身份注銷成功
        subject.logout();
        System.out.println("判斷身份登陸成功:" + subject.isAuthenticated());

        //沒有賬戶異常  UnknownAccountException
        //賬戶正確  密碼錯誤異常 IncorrectCredentialsException
    }
}
shiro登錄登出流程分析
shiro登陸登出流程分析

1深员、調(diào)用subject.login方法進(jìn)行登錄负蠕,其會自動委托給securityManager.login方法進(jìn)行登錄;

2倦畅、securityManager通過Authenticator(認(rèn)證器)進(jìn)行認(rèn)證;

3遮糖、Authenticator的實現(xiàn)ModularRealmAuthenticator調(diào)用realm從ini配置文件取用戶真實的賬號和密碼,這里使用的是IniRealm(shiro自帶,相當(dāng)于數(shù)據(jù)源)叠赐;

4欲账、IniRealm先根據(jù)token中的賬號去ini中找該賬號,如果找不到則給ModularRealmAuthenticator返回null芭概,如果找到則匹配密碼赛不,匹配密碼成功則認(rèn)證通過。

5罢洲、最后調(diào)用Subject.logout進(jìn)行退出操作踢故。

存在的問題 1、用戶名/密碼硬編碼在ini配置文件惹苗,以后需要改成如數(shù)據(jù)庫存儲殿较,且密碼需要加密存儲;

自定義realm
realm繼承體系

步驟:
1:自定義reaml桩蓉,繼承 AuthorizingRealm 重寫3個方法:getName doGetAuthorizationInfo doGetAuthenticationInfo

shiro-realm.ini 配置文件

#聲明一個realm
myRealm=cn.icanci.shiro.realm.MyRealm
#指定securityManager的realms實現(xiàn)
securityManager.realms=$myRealm

實例代碼

package cn.icanci.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.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;


/**
 * @Author: icanci
 * @ProjectName: shiro
 * @PackageName: cn.icanci.shiro.realm
 * @Date: Created in 2020/2/25 12:47
 * @ClassAction:
 */
public class MyRealm extends AuthorizingRealm {


    @Override
    public String getName() {
        return "myRealm";
    }

    /**
     * 授權(quán)操作
     * @param principalCollection
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        return null;
    }

    /**
     * 認(rèn)證操作
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {

        //authenticationToken表示登陸的時候 包裝的 UsernamePasswordToken

        //通過用戶名查找用戶到數(shù)據(jù)庫中查找用戶信息,封裝成一個AuthenticationToken對象,方便認(rèn)證器進(jìn)行認(rèn)證
        //獲取參數(shù) authenticationToken 中的用戶名

        String username = (String) authenticationToken.getPrincipal();
        //通過用戶名查詢數(shù)據(jù)庫 將用戶對象數(shù)據(jù)查詢返回:賬戶和密碼
        //假設(shè)查詢數(shù)據(jù)庫返回的數(shù)據(jù)是:zhangsan 666
        if(!"zhangsan".equals(username)){
            return null;
        }
        String password = "666";

        //info對象表示realm登陸比對信息:參數(shù)1 用戶信息 (真實登陸中式對象user對象) 參數(shù)2 密碼 參數(shù)3 當(dāng)然 realm名字
        SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(username, password, getName());
        System.out.println(authenticationToken);
        return info;
    }
}

測試

    @Test
    public void testMyRealm() throws Exception {
        //1.創(chuàng)建 SecurityManager工廠對象:加載配置文件 創(chuàng)建工廠對象
        IniSecurityManagerFactory factory = new IniSecurityManagerFactory("classpath:shiro-realm.ini");
        //2.通過工廠對象 創(chuàng)建SecurityManager對象
        SecurityManager instance = factory.getInstance();
        //3.將SecurityManager綁定到當(dāng)選運(yùn)行環(huán)境中,目的使系統(tǒng)隨時隨地都可以訪問
        SecurityUtils.setSecurityManager(instance);
        //4.創(chuàng)建當(dāng)前登陸的主體 注意 此時主題沒有經(jīng)過認(rèn)證
        Subject subject = SecurityUtils.getSubject();
        //5.綁定主題所需的身份 參數(shù)1 用戶名 參數(shù)2 密碼
        UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("zhangsan", "666");
        //6.主體登陸
        subject.login(usernamePasswordToken);
        //7.判斷身份登陸成功
        System.out.println("判斷身份登陸成功:" + subject.isAuthenticated());
        //7.判斷身份注銷成功
        subject.logout();
        System.out.println("判斷身份登陸成功:" + subject.isAuthenticated());

        //沒有賬戶異常  UnknownAccountException
        //賬戶正確  密碼錯誤異常 IncorrectCredentialsException
    }
shiro加密操作

散列算法 一般用于生成數(shù)據(jù)的摘要信息斜脂,是一種不可逆的算法,一般適合存儲密碼之類的數(shù)據(jù)触机,常見的散列算法如MD5、SHA等玷或。一般進(jìn)行散列時最好提供一個salt(鹽)儡首,比如加密密碼“admin”,產(chǎn)生的散列值是“21232f297a57a5a743894a0e4a801fc3”偏友,可以到一些md5解密網(wǎng)站很容易的通過散列值得到密碼“admin”蔬胯,即如果直接對密碼進(jìn)行散列相對來說破解更容易,此時我們可以加一些只有系統(tǒng)知道的干擾數(shù)據(jù)位他,如用戶名和ID(即鹽)氛濒;這樣散列的對象是“密碼+用戶名+ID”,這樣生成的散列值相對來說更難破解鹅髓。

 @Test
    public void testMD5() {
        //明文密碼
        String password = "666";

        //加密
        Md5Hash md5Hash = new Md5Hash(password);
        System.out.println(md5Hash);
        //fae0b27c451c728867a567e8c1bb4e53

        //加密 yan
        md5Hash = new Md5Hash(password,"張三");
        System.out.println(md5Hash);
        //e0d3fb0671e4d6305c5e5ab24dd05e51

        //加密 yan + 散列次數(shù)
        md5Hash = new Md5Hash(password,"張三",3);
        System.out.println(md5Hash);
        //7658aa971a52a0b0b2b2d89c5e5a0a0b
    }

配置文件 shiro-cryptography.ini

[main]
#定義憑證匹配器
credentialsMatcher=org.apache.shiro.authc.credential.HashedCredentialsMatcher
#散列算法
credentialsMatcher.hashAlgorithmName=md5
#散列次數(shù)
credentialsMatcher.hashIterations=3


#將憑證匹配器設(shè)置到realm
myRealm=cn.icanci.shiro.helloworld.PasswordRealm
myRealm.credentialsMatcher=$credentialsMatcher
securityManager.realms=$myRealm

鹽值加密

package cn.icanci.shiro.helloworld;

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.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;

/**
 * @Author: icanci
 * @ProjectName: shiro
 * @PackageName: cn.icanci.shiro.helloworld
 * @Date: Created in 2020/2/25 13:28
 * @ClassAction: 加密PasswordRealm
 */
public class PasswordRealm extends AuthorizingRealm {

    @Override
    public String getName() {
        return super.getName();
    }

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        return null;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {

        //authenticationToken表示登陸的時候 包裝的 UsernamePasswordToken

        //通過用戶名查找用戶到數(shù)據(jù)庫中查找用戶信息,封裝成一個AuthenticationToken對象,方便認(rèn)證器進(jìn)行認(rèn)證
        //獲取參數(shù) authenticationToken 中的用戶名

        String username = (String) authenticationToken.getPrincipal();
        //通過用戶名查詢數(shù)據(jù)庫 將用戶對象數(shù)據(jù)查詢返回:賬戶和密碼
        //假設(shè)查詢數(shù)據(jù)庫返回的數(shù)據(jù)是:zhangsan 666
        if(!"zhangsan".equals(username)){
            return null;
        }
        String password = "666";

        //模擬數(shù)據(jù)庫保存得加密之后的密碼 賬號 zhangsan : 666+張三+散列次數(shù) 3
        password = "7658aa971a52a0b0b2b2d89c5e5a0a0b";
        //info對象表示realm登陸比對信息:參數(shù)1 用戶信息 (真實登陸中式對象user對象) 參數(shù)2 密碼 參數(shù)3 鹽 參數(shù)4 當(dāng)然 realm名字
        SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(username, password, ByteSource.Util.bytes("張三"), getName());
        System.out.println(authenticationToken);
        return info;
    }
}

測試

@Test
    public void testPassword() throws Exception {
        //1.創(chuàng)建 SecurityManager工廠對象:加載配置文件 創(chuàng)建工廠對象
        IniSecurityManagerFactory factory = new IniSecurityManagerFactory("classpath:shiro-cryptography.ini");
        //2.通過工廠對象 創(chuàng)建SecurityManager對象
        SecurityManager instance = factory.getInstance();
        //3.將SecurityManager綁定到當(dāng)選運(yùn)行環(huán)境中,目的使系統(tǒng)隨時隨地都可以訪問
        SecurityUtils.setSecurityManager(instance);
        //4.創(chuàng)建當(dāng)前登陸的主體 注意 此時主題沒有經(jīng)過認(rèn)證
        Subject subject = SecurityUtils.getSubject();
        //5.綁定主題所需的身份 參數(shù)1 用戶名 參數(shù)2 密碼
        UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("zhangsan", "666");
        //6.主體登陸
        subject.login(usernamePasswordToken);
        //7.判斷身份登陸成功
        System.out.println("判斷身份登陸成功:" + subject.isAuthenticated());
        //7.判斷身份注銷成功
        subject.logout();
        System.out.println("判斷身份登陸成功:" + subject.isAuthenticated());

        //沒有賬戶異常  UnknownAccountException
        //賬戶正確  密碼錯誤異常 IncorrectCredentialsException
    }
授權(quán)方式
RBAC: 基于角色的權(quán)限管理
簡單理解為:誰扮演什么角色舞竿, 被允許做什么操作

用戶對象:user: 當(dāng)前操作用戶

角色對象:role:表示權(quán)限操作許可權(quán)的集合

權(quán)限對象:permission: 資源操作許可權(quán)

例子:張三(user) 下載(permission)一個高清無碼的種子(資源), 需要VIP權(quán)限(role)

張三--->普通用戶--->授權(quán)---->VIP用戶----->下載種子
編程方式:
通過寫if/else授權(quán)代碼塊完成

Subject subject = SecurityUtils.getSubject();  
if(subject.hasRole(“admin”)) {  
    //有權(quán)限  
} else {  
    //無權(quán)限  
}  
注解方式:
通過在執(zhí)行的Java方法上放置相應(yīng)的注解完成

@RequiresRoles("admin")  
@RequiresPermission(“employee:save”)
public void hello() {  
    //有權(quán)限  
} 
jsp標(biāo)簽方式:
在JSP頁面通過相應(yīng)的標(biāo)簽完成
<shiro:hasRole name="admin">  
         <!— 有權(quán)限 —>  
</shiro:hasRole> 
使用 ini 授權(quán)方式

增加 shiro-permission.ini 配置文件

[users]
#用戶zhang的密碼是123窿冯,此用戶具有role1和role2兩個角色
zhangsan=666,role1,role2
lisi=888,role2

[roles]
#角色role1對資源user擁有create骗奖、update權(quán)限
role1=user:create,user:update
#角色role2對資源user擁有create、delete權(quán)限
role2=user:create,user:delete
#角色role3對資源user擁有create權(quán)限
role3=user:create

權(quán)限表達(dá)式定義

在ini文件中用戶、角色执桌、權(quán)限的配置規(guī)則是:“用戶名=密碼鄙皇,角色1,角色2...” “角色=權(quán)限1仰挣,權(quán)限2...”伴逸,首先根據(jù)用戶名找角色,再根據(jù)角色找權(quán)限膘壶,角色是權(quán)限集合错蝴。

權(quán)限字符串的規(guī)則是:“資源標(biāo)識符:操作:資源實例標(biāo)識符”,意思是對哪個資源的哪個實例具有什么操作香椎,“:”是資源/操作/實例的分割符漱竖,權(quán)限字符串也可以使用*通配符。

例子:
用戶創(chuàng)建權(quán)限:user:create畜伐,或user:create:*
用戶修改實例001的權(quán)限:user:update:001
用戶實例001的所有權(quán)限:user:*:001

一般的馍惹,我們操作只需要關(guān)注前面兩節(jié):
資源:操作 :
*:* : 所有資源的所有操作權(quán)限--->admin

角色測試

    @Test
    public void testHasRole() throws Exception {
        IniSecurityManagerFactory factory = new IniSecurityManagerFactory("classpath:shiro-permission.ini");
        SecurityManager instance = factory.getInstance();
        SecurityUtils.setSecurityManager(instance);
        Subject subject = SecurityUtils.getSubject();
        UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("zhangsan", "666");
        subject.login(usernamePasswordToken);
        //進(jìn)行授權(quán)操作之前,用戶必須通過認(rèn)真
        //判斷當(dāng)前用戶是否有某個角色  返回true表示擁有
        boolean role1 = subject.hasRole("role1");
        System.out.println(role1);
        //判斷當(dāng)前用戶是否擁有這些權(quán)限 返回true表示全部擁有  false 表示不全部擁有
        boolean b = subject.hasAllRoles(Arrays.asList("role1", "role2", "role3"));
        System.out.println(b);
        //判斷當(dāng)前用戶是否擁有這些權(quán)限 返回 boolean數(shù)組 返回true表示擁有  false 表示不擁有
        boolean[] booleans = subject.hasRoles(Arrays.asList("role1", "role2", "role3"));
        for (int i = 0; i < booleans.length; i++){
            System.out.println(booleans[i]);
        }

        //判斷是否有角色 沒有返回值 如果有角色 不做任何操作 如果沒有 拋出異常  UnauthorizedException
        subject.checkRole("role1");
        //判斷是否擁有一些角色 不是全部擁有就 UnauthorizedException
        subject.checkRoles("role1","role2","role3");
    }
自定義realm完成授權(quán)

使用之前的配置文件

    @Test
    public void testHasRoleSecurity() throws Exception {
        IniSecurityManagerFactory factory = new IniSecurityManagerFactory("classpath:shiro-permission.ini");
        SecurityManager instance = factory.getInstance();
        SecurityUtils.setSecurityManager(instance);
        Subject subject = SecurityUtils.getSubject();
        UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("zhangsan", "666");
        subject.login(usernamePasswordToken);
        //進(jìn)行授操作之前,用戶必須通過認(rèn)證
        //判斷是否有某個權(quán)限 返 true 擁有 false 沒有
        boolean permitted = subject.isPermitted("user:delete");
        System.out.println(permitted);
        //判斷是否有某一些權(quán)限 返 true 都擁有 false 不都擁有
        boolean permittedAll = subject.isPermittedAll("user:create", "user:delete");
        System.out.println(permittedAll);
        //判斷當(dāng)前用戶是否擁有一些權(quán)限 有就返回 true 沒有就返回 false
        boolean[] permitted1 = subject.isPermitted("user:create", "user:delete");
        System.out.println("-----------------------");
        for (int i = 0; i < permitted1.length; i++){
            System.out.println(permitted1[i]);
        }
        //判斷用戶是否擁有 有就沒有返回值 沒有就報異常  UnauthorizedException
        subject.checkPermission("user:delete");
        subject.checkPermission("user:updateAll");
    }

步驟:1:自定義PermissionRealm 繼承 AuthorizingRealm 重寫3個方法: getName doGetAuthorizationInfo doGetAuthenticationInfo

配置文件 shiro-permission-realm.ini

[main]
#聲明一個realm  
myReal=cn.icanci.shiro.realm.PermissionRealm
#指定securityManager的realms實現(xiàn)  
securityManager.realms=$myReal
package cn.icanci.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 java.util.ArrayList;
import java.util.List;


/**
 * @Author: icanci
 * @ProjectName: shiro
 * @PackageName: cn.icanci.shiro.realm
 * @Date: Created in 2020/2/25 12:47
 * @ClassAction:
 */
public class PermissionRealm extends AuthorizingRealm {


    @Override
    public String getName() {
        return "PermissionRealm";
    }

    /**
     * 授權(quán)操作
     * @param principalCollection
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        //闖入?yún)?shù) principalCollection 憑證信息
        //SimpleAuthenticationInfo 認(rèn)證方法返回封裝認(rèn)證信息種第一個參數(shù):用戶信息(username)
        //當(dāng)前登陸的用戶名信息
        String username = (String) principalCollection.getPrimaryPrincipal();
        //模擬查詢數(shù)據(jù)庫 查詢用戶的角色以及用戶權(quán)限
        //角色集合
        List<String> roles = new ArrayList<>();
        //權(quán)限集合
        List<String> permission = new ArrayList<>();
        //假設(shè)數(shù)據(jù)庫種 有role角色
        roles.add("role1");
        //假設(shè)用戶在數(shù)據(jù)庫擁有刪除權(quán)限
        permission.add("user:delete");
        //返回當(dāng)前用戶在數(shù)據(jù)庫的權(quán)限和角色
        SimpleAuthorizationInfo info= new SimpleAuthorizationInfo();
        info.addRoles(roles);
        info.addStringPermissions(permission);
        return info;
    }

    /**
     * 認(rèn)證操作
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {

        //authenticationToken表示登陸的時候 包裝的 UsernamePasswordToken

        //通過用戶名查找用戶到數(shù)據(jù)庫中查找用戶信息,封裝成一個AuthenticationToken對象,方便認(rèn)證器進(jìn)行認(rèn)證
        //獲取參數(shù) authenticationToken 中的用戶名

        String username = (String) authenticationToken.getPrincipal();
        //通過用戶名查詢數(shù)據(jù)庫 將用戶對象數(shù)據(jù)查詢返回:賬戶和密碼
        //假設(shè)查詢數(shù)據(jù)庫返回的數(shù)據(jù)是:zhangsan 666
        if(!"zhangsan".equals(username)){
            return null;
        }
        String password = "666";

        //info對象表示realm登陸比對信息:參數(shù)1 用戶信息 (真實登陸中式對象user對象) 參數(shù)2 密碼 參數(shù)3 當(dāng)然 realm名字
        SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(username, password, getName());
        System.out.println(authenticationToken);
        return info;
    }
}

測試類

    @Test
    public void testRoleSecurity() throws Exception {
        IniSecurityManagerFactory factory = new IniSecurityManagerFactory("classpath:shiro-permission-realm.ini");
        SecurityManager instance = factory.getInstance();
        SecurityUtils.setSecurityManager(instance);
        Subject subject = SecurityUtils.getSubject();
        UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("zhangsan", "666");
        subject.login(usernamePasswordToken);
        //進(jìn)行授操作之前,用戶必須通過認(rèn)證
        //判斷是否有某個權(quán)限 返 true 擁有 false 沒有
        boolean permitted = subject.isPermitted("user:delete");
        System.out.println(permitted);
        //判斷當(dāng)前用戶是否有某個角色  返回true表示擁有
        boolean role1 = subject.hasRole("role1");
        System.out.println(role1);
    }
授權(quán)流程分析

1、首先調(diào)用Subject.isPermitted/hasRole接口玛界,其會委托給SecurityManager万矾,而SecurityManager接著會委托給Authorizer
2、Authorizer是真正的授權(quán)者慎框,如果我們調(diào)用如isPermitted(“user:view”)良狈,其首先會通過PermissionResolver把字符串轉(zhuǎn)換成相應(yīng)的Permission實例;
3笨枯、在進(jìn)行授權(quán)之前薪丁,其會調(diào)用相應(yīng)的Realm獲取Subject相應(yīng)的角色/權(quán)限用于匹配傳入的角色/權(quán)限;
4馅精、Authorizer會判斷Realm的角色/權(quán)限是否和傳入的匹配严嗜,如果有多個Realm,會委托給ModularRealmAuthorizer進(jìn)行循環(huán)判斷洲敢,如果匹配如isPermitted/hasRole會返回true漫玄,否則返回false表示授權(quán)失敗。

授權(quán)源碼分析
執(zhí)行流程

授權(quán)執(zhí)行流程

1压彭、首先調(diào)用Subject.isPermitted/hasRole接口睦优,其會委托給SecurityManager,而SecurityManager接著會委托給Authorizer
2壮不、Authorizer是真正的授權(quán)者汗盘,如果我們調(diào)用如isPermitted(“user:view”),其首先會通過PermissionResolver把字符串轉(zhuǎn)換成相應(yīng)的Permission實例询一;
3衡未、在進(jìn)行授權(quán)之前尸执,其會調(diào)用相應(yīng)的Realm獲取Subject相應(yīng)的角色/權(quán)限用于匹配傳入的角色/權(quán)限;
4缓醋、Authorizer會判斷Realm的角色/權(quán)限是否和傳入的匹配如失,如果有多個Realm,會委托給ModularRealmAuthorizer進(jìn)行循環(huán)判斷送粱,如果匹配如isPermitted/hasRole會返回true褪贵,否則返回false表示授權(quán)失敗。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末抗俄,一起剝皮案震驚了整個濱河市脆丁,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌动雹,老刑警劉巖槽卫,帶你破解...
    沈念sama閱讀 221,695評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異胰蝠,居然都是意外死亡歼培,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評論 3 399
  • 文/潘曉璐 我一進(jìn)店門茸塞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來躲庄,“玉大人,你說我怎么就攤上這事钾虐≡刖剑” “怎么了?”我有些...
    開封第一講書人閱讀 168,130評論 0 360
  • 文/不壞的土叔 我叫張陵效扫,是天一觀的道長倔监。 經(jīng)常有香客問我,道長菌仁,這世上最難降的妖魔是什么浩习? 我笑而不...
    開封第一講書人閱讀 59,648評論 1 297
  • 正文 為了忘掉前任,我火速辦了婚禮掘托,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘籍嘹。我一直安慰自己闪盔,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 68,655評論 6 397
  • 文/花漫 我一把揭開白布辱士。 她就那樣靜靜地躺著泪掀,像睡著了一般。 火紅的嫁衣襯著肌膚如雪颂碘。 梳的紋絲不亂的頭發(fā)上异赫,一...
    開封第一講書人閱讀 52,268評論 1 309
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼塔拳。 笑死鼠证,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的靠抑。 我是一名探鬼主播量九,決...
    沈念sama閱讀 40,835評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼颂碧!你這毒婦竟也來了荠列?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,740評論 0 276
  • 序言:老撾萬榮一對情侶失蹤载城,失蹤者是張志新(化名)和其女友劉穎肌似,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體诉瓦,經(jīng)...
    沈念sama閱讀 46,286評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡川队,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,375評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了垦搬。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片呼寸。...
    茶點故事閱讀 40,505評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖猴贰,靈堂內(nèi)的尸體忽然破棺而出对雪,到底是詐尸還是另有隱情,我是刑警寧澤米绕,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布瑟捣,位于F島的核電站,受9級特大地震影響栅干,放射性物質(zhì)發(fā)生泄漏迈套。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,873評論 3 333
  • 文/蒙蒙 一碱鳞、第九天 我趴在偏房一處隱蔽的房頂上張望桑李。 院中可真熱鬧,春花似錦窿给、人聲如沸贵白。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,357評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽禁荒。三九已至,卻和暖如春角撞,著一層夾襖步出監(jiān)牢的瞬間呛伴,已是汗流浹背勃痴。 一陣腳步聲響...
    開封第一講書人閱讀 33,466評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留热康,地道東北人沛申。 一個月前我還...
    沈念sama閱讀 48,921評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像褐隆,于是被迫代替她去往敵國和親污它。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,515評論 2 359