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

基本使用

什么是权限管理?

基本上涉及到用户参与的系统都要进行权限管理,权限管理属于系统安全的范畴,权限管理能实现对用户访问系统的控制,按照安全规则或者安全策略限制用户操作,只允许用户访问被授权的资源。权限管理包括用户身份认证和授权两部分,简称认证授权

身份认证
就是判断一个用户是否为合法用户的处理过程.醉常用的简单身份认证方式是系统通过核对用户输入的用户名和口令,看其是否与系统中存储的该用户的用户名和口令一致,来判断用户身份是否正确.对于采用指纹等系统,则出示指纹;对于硬件Key等刷卡系统,则需要刷卡.

Shiro安全框架 的使用 & Spring 整合 Shiro [1]_第1张图片
身份认证

Shiro安全框架 的使用 & Spring 整合 Shiro [1]_第2张图片
认证

上边的流程图中需 要理解以下关键对象:
Subject:主体 user
访问系统的用户,主体可以是用户、程序等,进行认证的都称为主体;
Principal:身份信息(username)
是主体(subject)进行身份认证的标识,标识必须具有唯一性,如用户名、手机号、邮箱地址等,一个主体可以有多个身份,但是必须有一个主身份(Primary Principal)。
credential:凭证信息(password)
是只有主体自己知道的安全信息,如密码、证书等。

授权,即访问控制,控制谁能访问哪些资源。主体进行身份认证后需要分配权限方可访问系统的资源,对于某些资源没有权限是无法访问的。

Apache Shiro是Java的一个安全框架。帮助我们完成:认证、授权、加密、会话管理、与Web集成、缓存等。

Shiro安全框架 的使用 & Spring 整合 Shiro [1]_第3张图片
功能图

Authentication:身份认证/登录,验证用户是不是拥有相应的身份;

Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能做事情,常见的如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限;

Session Manager:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通JavaSE环境的,也可以是Web环境的;

Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储;

Web Support:Web支持,可以非常容易的集成到Web环境;

Caching:缓存,比如用户登录后,其用户信息、拥有的角色/权限不必每次去查,这样可以提高效率;

Concurrency:shiro支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去;

Testing:提供测试支持;

Run As:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问;

Remember Me:记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了。


Shiro架构有三个主要概念 - Subject,SecurityManager,Realms

Shiro安全框架 的使用 & Spring 整合 Shiro [1]_第4张图片
架构

Subject : 访问系统的用户,主体可以是用户、程序等,进行认证的都称为主体;
Subject 一词是一个安全术语,其基本意思是“当前的操作用户”。它是一个抽象的概念,可以是人,也可以是第三方进程或其他类似事物,如爬虫,机器人等。

在程序任意位置:Subject currentUser = SecurityUtils.getSubject(); 获取shiro 一旦获得Subject,你就可以立即获得你希望用Shiro为当前用户做的90%的事情,如登录、登出、访问会话、执行授权检查等

SecurityManager
安全管理器,它是shiro功能实现的核心,负责与后边介绍的其他组件(认证器/授权器/缓存控制器)进行交互,实现subject委托的各种功能。有点类似于spirngmvc中的DispatcherServlet前端控制器。

Realms
Realm充当了Shiro与应用安全数据间的“桥梁”或者“连接器”。;可以把Realm看成DataSource,即安全数据源。执行认证(登录)和授权(访问控制)时,Shiro会从应用配置的Realm中查找相关的比对数据。以确认用户是否合法,操作是否合理

从系统结构角度看:shiro

Shiro安全框架 的使用 & Spring 整合 Shiro [1]_第5张图片
系统结构角度

Subject:主体,可以看到主体可以是任何可以与应用交互的“用户”;

SecurityManager:相当于SpringMVC中的DispatcherServlet或者Struts2中的

FilterDispatcher;是Shiro的心脏;所有具体的交互都通过SecurityManager进行控制;它管理着所有Subject、且负责进行认证和授权、及会话、缓存的管理。

Authenticator:认证器,负责主体认证的,这是一个扩展点,如果用户觉得Shiro默认的不好,可以自定义实现;其需要认证策略(Authentication Strategy),即什么情况下算用户认证通过了;

Authorizer:授权器,或者访问控制器,用来决定主体是否有权限进行相应的操作;即控制着用户能访问应用中的哪些功能;

Realm:可以有1个或多个Realm,可以认为是安全实体数据源,即用于获取安全实体的;可以是JDBC实现,也可以是LDAP实现,或者内存实现等等;由用户提供;注意:Shiro不知道你的用户/权限存储在哪及以何种格式存储;所以我们一般在应用中都需要实现自己的Realm;

SessionManager:如果写过Servlet就应该知道Session的概念,Session呢需要有人去管理它的生命周期,这个组件就是SessionManager;而Shiro并不仅仅可以用在Web环境,也可以用在如普通的JavaSE环境、EJB等环境;所有呢,Shiro就抽象了一个自己的Session来管理主体与应用之间交互的数据;可以实现分布式的会话管理;

SessionDAO:DAO大家都用过,数据访问对象,用于会话的CRUD,比如我们想把Session保存到数据库,那么可以实现自己的SessionDAO,通过如JDBC写到数据库;比如想把Session放到redis中,可以实现自己的redis SessionDAO;另外SessionDAO中可以使用Cache进行缓存,以提高性能;

CacheManager:缓存控制器,来管理如用户、角色、权限等的缓存的;因为这些数据基本上很少去改变,放到缓存中后可以提高访问的性能

Cryptography:密码模块,Shiro提高了一些常见的加密组件用于如密码加密/解密的。

入门案例
Shiro安全框架 的使用 & Spring 整合 Shiro [1]_第6张图片
认证流程

创建 maven 项目 添加依赖

  
        
            junit
            junit
            4.12
        
        
            commons-logging
            commons-logging
            1.1.3
        
        
            org.apache.shiro
            shiro-core
            1.2.2
        
    

添加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]
#模拟数据库用户
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.创建 SecurityManager工厂对象:加载配置文件 创建工厂对象
        IniSecurityManagerFactory factory = new IniSecurityManagerFactory("classpath:shiro.ini");
        //2.通过工厂对象 创建SecurityManager对象
        SecurityManager instance = factory.getInstance();
        //3.将SecurityManager绑定到当选运行环境中,目的使系统随时随地都可以访问
        SecurityUtils.setSecurityManager(instance);
        //4.创建当前登陆的主体 注意 此时主题没有经过认证
        Subject subject = SecurityUtils.getSubject();
        //5.绑定主题所需的身份 参数1 用户名 参数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安全框架 的使用 & Spring 整合 Shiro [1]_第7张图片
shiro登陆登出流程分析

1、调用subject.login方法进行登录,其会自动委托给securityManager.login方法进行登录;

2、securityManager通过Authenticator(认证器)进行认证;

3、Authenticator的实现ModularRealmAuthenticator调用realm从ini配置文件取用户真实的账号和密码,这里使用的是IniRealm(shiro自带,相当于数据源);

4、IniRealm先根据token中的账号去ini中找该账号,如果找不到则给ModularRealmAuthenticator返回null,如果找到则匹配密码,匹配密码成功则认证通过。

5、最后调用Subject.logout进行退出操作。

存在的问题 1、用户名/密码硬编码在ini配置文件,以后需要改成如数据库存储,且密码需要加密存储;

自定义realm
Shiro安全框架 的使用 & Spring 整合 Shiro [1]_第8张图片
realm继承体系

步骤:
1:自定义reaml,继承 AuthorizingRealm 重写3个方法:getName doGetAuthorizationInfo doGetAuthenticationInfo

shiro-realm.ini 配置文件

#声明一个realm
myRealm=cn.icanci.shiro.realm.MyRealm
#指定securityManager的realms实现
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";
    }

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

    /**
     * 认证操作
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {

        //authenticationToken表示登陆的时候 包装的 UsernamePasswordToken

        //通过用户名查找用户到数据库中查找用户信息,封装成一个AuthenticationToken对象,方便认证器进行认证
        //获取参数 authenticationToken 中的用户名

        String username = (String) authenticationToken.getPrincipal();
        //通过用户名查询数据库 将用户对象数据查询返回:账户和密码
        //假设查询数据库返回的数据是:zhangsan 666
        if(!"zhangsan".equals(username)){
            return null;
        }
        String password = "666";

        //info对象表示realm登陆比对信息:参数1 用户信息 (真实登陆中式对象user对象) 参数2 密码 参数3 当然 realm名字
        SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(username, password, getName());
        System.out.println(authenticationToken);
        return info;
    }
}

测试

    @Test
    public void testMyRealm() throws Exception {
        //1.创建 SecurityManager工厂对象:加载配置文件 创建工厂对象
        IniSecurityManagerFactory factory = new IniSecurityManagerFactory("classpath:shiro-realm.ini");
        //2.通过工厂对象 创建SecurityManager对象
        SecurityManager instance = factory.getInstance();
        //3.将SecurityManager绑定到当选运行环境中,目的使系统随时随地都可以访问
        SecurityUtils.setSecurityManager(instance);
        //4.创建当前登陆的主体 注意 此时主题没有经过认证
        Subject subject = SecurityUtils.getSubject();
        //5.绑定主题所需的身份 参数1 用户名 参数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加密操作

散列算法 一般用于生成数据的摘要信息,是一种不可逆的算法,一般适合存储密码之类的数据,常见的散列算法如MD5、SHA等。一般进行散列时最好提供一个salt(盐),比如加密密码“admin”,产生的散列值是“21232f297a57a5a743894a0e4a801fc3”,可以到一些md5解密网站很容易的通过散列值得到密码“admin”,即如果直接对密码进行散列相对来说破解更容易,此时我们可以加一些只有系统知道的干扰数据,如用户名和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 + 散列次数
        md5Hash = new Md5Hash(password,"张三",3);
        System.out.println(md5Hash);
        //7658aa971a52a0b0b2b2d89c5e5a0a0b
    }

配置文件 shiro-cryptography.ini

[main]
#定义凭证匹配器
credentialsMatcher=org.apache.shiro.authc.credential.HashedCredentialsMatcher
#散列算法
credentialsMatcher.hashAlgorithmName=md5
#散列次数
credentialsMatcher.hashIterations=3


#将凭证匹配器设置到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

        //通过用户名查找用户到数据库中查找用户信息,封装成一个AuthenticationToken对象,方便认证器进行认证
        //获取参数 authenticationToken 中的用户名

        String username = (String) authenticationToken.getPrincipal();
        //通过用户名查询数据库 将用户对象数据查询返回:账户和密码
        //假设查询数据库返回的数据是:zhangsan 666
        if(!"zhangsan".equals(username)){
            return null;
        }
        String password = "666";

        //模拟数据库保存得加密之后的密码 账号 zhangsan : 666+张三+散列次数 3
        password = "7658aa971a52a0b0b2b2d89c5e5a0a0b";
        //info对象表示realm登陆比对信息:参数1 用户信息 (真实登陆中式对象user对象) 参数2 密码 参数3 盐 参数4 当然 realm名字
        SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(username, password, ByteSource.Util.bytes("张三"), getName());
        System.out.println(authenticationToken);
        return info;
    }
}

测试

@Test
    public void testPassword() throws Exception {
        //1.创建 SecurityManager工厂对象:加载配置文件 创建工厂对象
        IniSecurityManagerFactory factory = new IniSecurityManagerFactory("classpath:shiro-cryptography.ini");
        //2.通过工厂对象 创建SecurityManager对象
        SecurityManager instance = factory.getInstance();
        //3.将SecurityManager绑定到当选运行环境中,目的使系统随时随地都可以访问
        SecurityUtils.setSecurityManager(instance);
        //4.创建当前登陆的主体 注意 此时主题没有经过认证
        Subject subject = SecurityUtils.getSubject();
        //5.绑定主题所需的身份 参数1 用户名 参数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
    }
授权方式
RBAC: 基于角色的权限管理
简单理解为:谁扮演什么角色, 被允许做什么操作

用户对象:user: 当前操作用户

角色对象:role:表示权限操作许可权的集合

权限对象:permission: 资源操作许可权

例子:张三(user) 下载(permission)一个高清无码的种子(资源), 需要VIP权限(role)

张三--->普通用户--->授权---->VIP用户----->下载种子
编程方式:
通过写if/else授权代码块完成

Subject subject = SecurityUtils.getSubject();  
if(subject.hasRole(“admin”)) {  
    //有权限  
} else {  
    //无权限  
}  
注解方式:
通过在执行的Java方法上放置相应的注解完成

@RequiresRoles("admin")  
@RequiresPermission(“employee:save”)
public void hello() {  
    //有权限  
} 
jsp标签方式:
在JSP页面通过相应的标签完成
  
           
 
使用 ini 授权方式

增加 shiro-permission.ini 配置文件

[users]
#用户zhang的密码是123,此用户具有role1和role2两个角色
zhangsan=666,role1,role2
lisi=888,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

权限表达式定义

在ini文件中用户、角色、权限的配置规则是:“用户名=密码,角色1,角色2...” “角色=权限1,权限2...”,首先根据用户名找角色,再根据角色找权限,角色是权限集合。

权限字符串的规则是:“资源标识符:操作:资源实例标识符”,意思是对哪个资源的哪个实例具有什么操作,“:”是资源/操作/实例的分割符,权限字符串也可以使用*通配符。

例子:
用户创建权限:user:create,或user:create:*
用户修改实例001的权限:user:update:001
用户实例001的所有权限:user:*:001

一般的,我们操作只需要关注前面两节:
资源:操作 :
*:* : 所有资源的所有操作权限--->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);
        //进行授权操作之前,用户必须通过认真
        //判断当前用户是否有某个角色  返回true表示拥有
        boolean role1 = subject.hasRole("role1");
        System.out.println(role1);
        //判断当前用户是否拥有这些权限 返回true表示全部拥有  false 表示不全部拥有
        boolean b = subject.hasAllRoles(Arrays.asList("role1", "role2", "role3"));
        System.out.println(b);
        //判断当前用户是否拥有这些权限 返回 boolean数组 返回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完成授权

使用之前的配置文件

    @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);
        //进行授操作之前,用户必须通过认证
        //判断是否有某个权限 返 true 拥有 false 没有
        boolean permitted = subject.isPermitted("user:delete");
        System.out.println(permitted);
        //判断是否有某一些权限 返 true 都拥有 false 不都拥有
        boolean permittedAll = subject.isPermittedAll("user:create", "user:delete");
        System.out.println(permittedAll);
        //判断当前用户是否拥有一些权限 有就返回 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实现  
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";
    }

    /**
     * 授权操作
     * @param principalCollection
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        //闯入参数 principalCollection 凭证信息
        //SimpleAuthenticationInfo 认证方法返回封装认证信息种第一个参数:用户信息(username)
        //当前登陆的用户名信息
        String username = (String) principalCollection.getPrimaryPrincipal();
        //模拟查询数据库 查询用户的角色以及用户权限
        //角色集合
        List roles = new ArrayList<>();
        //权限集合
        List permission = new ArrayList<>();
        //假设数据库种 有role角色
        roles.add("role1");
        //假设用户在数据库拥有删除权限
        permission.add("user:delete");
        //返回当前用户在数据库的权限和角色
        SimpleAuthorizationInfo info= new SimpleAuthorizationInfo();
        info.addRoles(roles);
        info.addStringPermissions(permission);
        return info;
    }

    /**
     * 认证操作
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {

        //authenticationToken表示登陆的时候 包装的 UsernamePasswordToken

        //通过用户名查找用户到数据库中查找用户信息,封装成一个AuthenticationToken对象,方便认证器进行认证
        //获取参数 authenticationToken 中的用户名

        String username = (String) authenticationToken.getPrincipal();
        //通过用户名查询数据库 将用户对象数据查询返回:账户和密码
        //假设查询数据库返回的数据是:zhangsan 666
        if(!"zhangsan".equals(username)){
            return null;
        }
        String password = "666";

        //info对象表示realm登陆比对信息:参数1 用户信息 (真实登陆中式对象user对象) 参数2 密码 参数3 当然 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);
        //进行授操作之前,用户必须通过认证
        //判断是否有某个权限 返 true 拥有 false 没有
        boolean permitted = subject.isPermitted("user:delete");
        System.out.println(permitted);
        //判断当前用户是否有某个角色  返回true表示拥有
        boolean role1 = subject.hasRole("role1");
        System.out.println(role1);
    }
授权流程分析

1、首先调用Subject.isPermitted/hasRole接口,其会委托给SecurityManager,而SecurityManager接着会委托给Authorizer
2、Authorizer是真正的授权者,如果我们调用如isPermitted(“user:view”),其首先会通过PermissionResolver把字符串转换成相应的Permission实例;
3、在进行授权之前,其会调用相应的Realm获取Subject相应的角色/权限用于匹配传入的角色/权限;
4、Authorizer会判断Realm的角色/权限是否和传入的匹配,如果有多个Realm,会委托给ModularRealmAuthorizer进行循环判断,如果匹配如isPermitted/hasRole会返回true,否则返回false表示授权失败。

授权源码分析
Shiro安全框架 的使用 & Spring 整合 Shiro [1]_第9张图片
执行流程

Shiro安全框架 的使用 & Spring 整合 Shiro [1]_第10张图片
授权执行流程

1、首先调用Subject.isPermitted/hasRole接口,其会委托给SecurityManager,而SecurityManager接着会委托给Authorizer
2、Authorizer是真正的授权者,如果我们调用如isPermitted(“user:view”),其首先会通过PermissionResolver把字符串转换成相应的Permission实例;
3、在进行授权之前,其会调用相应的Realm获取Subject相应的角色/权限用于匹配传入的角色/权限;
4、Authorizer会判断Realm的角色/权限是否和传入的匹配,如果有多个Realm,会委托给ModularRealmAuthorizer进行循环判断,如果匹配如isPermitted/hasRole会返回true,否则返回false表示授权失败。

你可能感兴趣的:(Shiro安全框架 的使用 & Spring 整合 Shiro [1])