Shiro框架 (原理分析与简单实现)

Shiro框架(原理分析与简单实现)html

有兴趣的同窗也能够阅读我以前分享的:Java权限管理(受权与认证)CRM权限管理   (PS : 这篇博客里面的实现方式没有使用框架,彻底是手写的受权与认证,能够帮助理解Shiro框架)前端

http://www.javashuo.com/article/p-oopmnwnq-du.html  java

若是发现分享的内容有不合理或者的不对地方,请留言,我会及时定位分析,感谢!!!redis

1.介绍

1.1 什么是权限管理?

 

基本上涉及到用户参与的系统都要进行权限管理,权限管理属于系统安全的范畴,权限管理实现对用户访问系统的控制,按照安全规则或者安全策略控制用户能够访问并且只能访问本身被受权的资源。算法

权限管理包括用户身份认证和受权两部分,简称认证受权。对于须要访问控制的资源用户首先通过身份认证,认证经过后用户具备该资源的访问权限方可访问。数据库

 

  • 认证

 

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

 

上边的流程图中须要理解如下关键对象:编程

Subject:主体 (user)(抽象的概念:保存登陆后的相关信息)数组

       访问系统的用户,主体能够是用户、程序等,进行认证的都称为主体;缓存

 

Principal:身份信息 (username)

       是主体(subject)进行身份认证的标识,标识必须具备惟一性,如用户名、手机号、邮箱地址等,一个主体能够有多个身份,可是必须有一个主身份(Primary Principal)(相似于数据库表中的id,保持惟一)。

 

credential:凭证信息 (password)

       是只有主体本身知道的安全信息,如密码、证书等。

 

匿名访问:相似于淘宝首页

是否定证经过:淘宝下单,检查是否登陆,没有登陆请登陆后下单

 

  • 受权

 

受权,即访问控制,控制谁能访问哪些资源。主体进行身份认证后须要分配权限方可访问系统的资源,对于某些资源没有权限是没法访问的。(前提:必须认证经过)

 

例如:假如你恰好看中了一个高清无码的种子,而你准备要下载了,这个时候会弹出一个很是恶心的框,让你登陆才行,好,你辛辛苦苦注册登陆后,点击下载,这时有弹出一个框,告诉你必须是VIP用户才能下载,这时候就是分配权限启最用,这时你就会去充值一个VIP,而后能够点击下载,这时他们就会检查你这个用户是否有权限,有权限下载,没有权限不容许下载。

 

1.2 什么是Apache Shiro?

Apache Shiro是Java的一个安全框架。目前,使用的人愈来愈多,由于它至关简单,对比Spri9ng Security,功能就没那么强大,可是在实际工做时可能并不须要那么复杂的东西,因此使用小而简单的Shiro就足够了。这里咱们就不纠结它俩哪一个好哪一个坏,能更简单的解决项目问题就行了。

 

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

 

1.3 从功能角度分析Shiro能作什么事情?

 

Shiro能够很是容易的开发出足够好的应用,其不只能够用在JavaSE环境,也能够用在JavaEE环境。Shiro能够帮助咱们完成:认证、受权、加密、会话管理、与Web集成、缓存等。

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

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

Session Manager:会话管理,即用户登陆后就是一次会话,在没有退出以前,它的全部信息都在会话中;会话能够是普通JavaSE环境的,也能够是如Web环境的;

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

Web Support:Web支持,能够很是容易的集成到Web环境;

Caching:缓存,好比用户登陆后,其用户信息、拥有的角色/权限没必要每次去查,这样能够提升效率;

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

Testing:提供测试支持;

Run As:容许一个用户伪装为另外一个用户(若是他们容许)的身份进行访问;

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

 

1.4 Shiro核心概念?

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

 

下图仅供临时理解

 

 

Subject :访问系统的用户,主体能够是用户、程序等,进行认证的都称为主体;

        Subject一词是一个安全术语,其基本意思是“当前的操做用户”。它是一个抽象的概念,能够是人,也能够是第三方进程或其余相似事物,如爬虫,机器人等。

    在程序任意位置:Subject currentUser = SecurityUtils.getSubject(); 获取shiro

   一旦得到Subject,你就能够当即得到你但愿用Shiro为当前用户作的90%的事情,如登陆、登出、访问会话、执行受权检查等

SecurityManager

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

Realms

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

 

  • Subject

在考虑应用安全时,你最常问的问题多是“当前用户是谁?”或“当前用户容许作X吗?”。当咱们写代码或设计用户界面时,问本身这些问题很日常:应用一般都是基于用户故事构建的,而且你但愿功能描述(和安全)是基于每一个用户的。因此,对于咱们而言,考虑应用安全的最天然方式就是基于当前用户。Shiro的API用它的Subject概念从根本上体现了这种思考方式。

 

Subject一词是一个安全术语,其基本意思是“当前的操做用户”。称之为“用户”并不许确,由于“用户”一词一般跟人相关。在安全领域,术语“Subject”能够是人,也能够是第三方进程或其余相似事物,如爬虫,机器人等。它仅仅意味着“当前跟软件交互的东西”。但考虑到大多数目的和用途,你能够把它认为是Shiro的“用户”概念。在代码的任何地方,你都能轻易的得到Shiro Subject。

Subject currentUser = SecurityUtils.getSubject();

 

一旦得到Subject,你就能够当即得到你但愿用Shiro为当前用户作的90%的事情,如登陆、登出、访问会话、执行受权检查等 - 稍后还会看到更多。这里的关键点是Shiro的API很是直观,由于它反映了开发者以‘每一个用户’思考安全控制的天然趋势。同时,在代码的任何地方都能很轻松地访问Subject,容许在任何须要的地方进行安全操做。

 

  • SecurityManager

Subject的“幕后”推手是SecurityManager。Subject表明了当前用户的安全操做,SecurityManager则管理全部用户的安全操做。它是Shiro框架的核心,充当“保护伞”,引用了多个内部嵌套安全组件,它们造成了对象图。可是,一旦SecurityManager及其内部对象图配置好,它就会退居幕后,应用开发人员几乎把他们的全部时间都花在Subject API调用上。

 

那么,如何设置SecurityManager呢?嗯,这要看应用的环境。例如,Web应用一般会在Web.xml中指定一个Shiro Servlet Filter,这会建立SecurityManager实例,若是你运行的是一个独立应用,你须要用其余配置方式,但有不少配置选项。

 

一个应用几乎老是只有一个SecurityManager实例。它实际是应用的Singleton。跟Shiro里几乎全部组件同样,SecurityManager的缺省实现是POJO,并且可用POJO兼容的任何配置机制进行配置 - 普通的Java代码、Spring XML、YAML、.properties和.ini文件等。基原本讲,可以实例化类和调用JavaBean兼容方法的任何配置形式均可使用。

 

能够看出它是Shiro的核心,它负责与后边介绍的其余组件进行交互,若是学习过SpringMVC,你能够把它当作DispatcherServlet前端控制器。

 

  • Realms

Shiro的第三个也是最后一个概念是Realm。Realm充当了Shiro与应用安全数据间的“桥梁”或者“链接器”。也就是说,当切实与像用户账户这类安全相关数据进行交互,执行认证(登陆)和受权(访问控制)时,Shiro会从应用配置的Realm中查找不少内容。

 

从这个意义上讲,Realm实质上是一个安全相关的DAO:它封装了数据源的链接细节,并在须要时将相关数据提供给Shiro。当配置Shiro时,你必须至少指定一个Realm,用于认证和(或)受权。配置多个Realm是能够的,可是至少须要一个。

 

Realm 能够理解为读取用户信息、角色及权限的 DAO,就是说SecurityManager要验证用户身份与权限,那么它须要从Realm获取相应的信息进行比较以肯定用户身份是否合法;能够把Realm当作DataSource,即安全数据源。

 

 最简单的一个Shiro应用:

一、应用代码经过Subject来进行认证和受权,而Subject又委托给SecurityManager;

二、咱们须要给Shiro的SecurityManager注入Realm,从而让SecurityManager能获得数据库中的用户及其权限进行判断;

1.5 从系统结构角度理解什么是权限管理?

简单理解下图流程:最上层表明其余程序,进来后,先进入Security Manager 进行受权或者认证(authenticator   authorizer),而受权或者认证的数据是从最底层的DB中查询出来的,而后放到不一样的realms中(realms中的这些值用来和程序中传入的数据(程序中传入的数据被封装成subject主题)进行比对。

 

 

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提升了一些常见的加密组件用于如密码加密/解密的。

 

2.认证

2.1 认证流程

 

2.2 使用ini完成认证

2.2.1 配置依赖

 

<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> 

 

2.2.2 添加shiro.ini配置文件

首先准备一些用户身份/凭据(shiro.ini):

 

[users] 

zhangsan=666 

lisi=888

 

2.2.3 登陆/退出

package com.ssm.test;

 

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.apache.shiro.util.Factory;

import org.junit.Test;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

 

public class ShiroTest {

  

   private final static Logger logger = LoggerFactory.getLogger(ShiroTest.class);

  

   @Test

   public void testLogin() {

     

      // 一、建立SecurityManager工厂对象:加载配置文件,建立工厂对象

      Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");

     

      //二、建立SecurityManager

      SecurityManager securityManager = factory.getInstance();

     

      //三、将securityManager设置到运行环境中:目的是让系统随时随地能够访问securityManger对象

      SecurityUtils.setSecurityManager(securityManager);

     

      //四、建立当前登陆的主体,注意:此时主体没有通过认证

      Subject subject = SecurityUtils.getSubject();

     

      //五、收集主体登陆的身份 / 凭证 (建立token令牌),记录用户认证的身份和凭证即帐号和密码

      //参数1:将要登陆的用户名,参数2:登陆用户的密码

      // 若是参数1出错 :抛异常 org.apache.shiro.authc.UnknownAccountException

      // 若是参数2出错 :抛异常org.apache.shiro.authc.IncorrectCredentialsException

      UsernamePasswordToken token = new UsernamePasswordToken("zhangsan", "666");

     

      //六、主题登陆

      try {

         logger.info("主体登陆");

         subject.login(token);

      } catch (Exception e) {

         // 登陆失败

         System.out.println("testLogin exception value : " + e.getMessage());

         System.out.println("testLogin exception value e : " + e);

      }

     

      //七、判断登陆是否成功

      System.out.println("验证登陆是否成功 :" + subject.isAuthenticated());

     

      //八、登出(注销)

      subject.logout();

      System.out.println("验证登陆是否成功 :" + subject.isAuthenticated());

     

   }

}

 

登陆常见异常 :

 

若是身份验证失败请捕获AuthenticationException或其子类,常见的如: DisabledAccountException(禁用的账号)、LockedAccountException(锁定的账号)、UnknownAccountException(错误的账号)、ExcessiveAttemptsException(登陆失败次数过多)、IncorrectCredentialsException (错误的凭证)、ExpiredCredentialsException(过时的凭证)等

 

2.2.4 执行流程分析

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

二、securityManager经过Authenticator(认证器)进行认证;

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

四、IniRealm先根据token中的帐号去ini中找该帐号,若是找不到则给ModularRealmAuthenticator返回null,若是找到则匹配密码,匹配密码成功则认证经过。

五、最后调用Subject.logout进行退出操做。

 

2.2.5 存在的问题

一、用户名/密码硬编码在ini配置文件,之后须要改为如数据库存储,且密码须要加密存储;

 

 

 

2.3 Realm体系

 

 

Realm:域,Shiro从从Realm获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,那么它须要从Realm获取相应的用户进行比较以肯定用户身份是否合法;也须要从Realm获得用户相应的角色/权限进行验证用户是否能进行操做;能够把Realm当作DataSource,即安全数据源。如咱们以前的ini配置方式将使用org.apache.shiro.realm.text.IniRealm。

 

org.apache.shiro.realm.Realm接口以下:

------------------------------------------------------------------------------------------------------------------------------------------------------

String getName(); //返回一个惟一的Realm名字 

boolean supports(AuthenticationToken token); //判断此Realm是否支持此Token 

AuthenticationInfo getAuthenticationInfo(AuthenticationToken token)  throws AuthenticationException;  //根据Token获取认证信息

 

Realm体系:看图.

 

最基础的是Realm接口,CachingRealm负责缓存处理,AuthenticationRealm负责认证,AuthorizingRealm负责受权,一般自定义的realm继承AuthorizingRealm。

 

2.3.1 自定义Realm

1. 自定义realm,继承AuthorizingRealm  重写3个方法:getName  doGetAuthorizationInfo  doGetAuthenticaionInfo

2. 配置ini文件,指定使用自定义realm

3. 加载配置文件shiro-realm.ini,执行登陆操做

package com.ssm.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;

 

public class MyRealm extends AuthorizingRealm {

 

   // 一个系统可能有不一样的realm,为了区分使用

   @Override

   public String getName() {

      return "MyRealm";

   }

  

   // 受权操做

   protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {

      // TODO Auto-generated method stub

      return null;

   }

 

   // 认证操做

   protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {

     

      System.out.println("token value :" + token);

     

      // 参数token:表示登陆时包装的UsernamePasswordToken

     

      // 经过用户名到数据库中查询用户信息,封装成一个AuthenticationInfo对象返回,方便认证器进行对比

      // 获取token中的用户名

      String username = (String) token.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("info value :" + info);

      return info;

   }

}

 

2.3.1 添加shiro-realm.ini配置文件

ini配置文件指定自定义Realm实现(shiro-realm.ini) 

 

#声明一个realm 

myRealm=com.ssm.realm.MyRealm

#指定securityManager的realms实现 

securityManager.realms=$myRealm

 

2.3.1 登陆/退出

       @Test

   public void testLoginByRealm() {

     

      // 一、建立SecurityManager工厂对象:加载配置文件,建立工厂对象

      Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro-realm.ini");

     

      //二、建立SecurityManager

      SecurityManager securityManager = factory.getInstance();

     

      //三、将securityManager设置到运行环境中:目的是让系统随时随地能够访问securityManger对象

      SecurityUtils.setSecurityManager(securityManager);

     

      //四、建立当前登陆的主体,注意:此时主体没有通过认证

      Subject subject = SecurityUtils.getSubject();

     

      //五、收集主体登陆的身份 / 凭证 (建立token令牌),记录用户认证的身份和凭证即帐号和密码

      //参数1:将要登陆的用户名,参数2:登陆用户的密码

      // 若是参数1出错 :抛异常 org.apache.shiro.authc.UnknownAccountException

      // 若是参数2出错 :抛异常org.apache.shiro.authc.IncorrectCredentialsException

      UsernamePasswordToken token = new UsernamePasswordToken("zhangsa1n", "666");

     

      //六、主题登陆

      try {

        logger.info("主体登陆");

        subject.login(token);

      } catch (Exception e) {

        // 登陆失败

        e.printStackTrace();

        //System.out.println("testLogin exception value : " + e.getMessage());

        //System.out.println("testLogin exception value e : " + e);

      }

     

      //七、判断登陆是否成功

      System.out.println("验证登陆是否成功 :" + subject.isAuthenticated());

     

      //八、登出(注销)

      subject.logout();

      System.out.println("验证登陆是否成功 :" + subject.isAuthenticated());

   }

 

2.4 使用Realm进行密码散列

在涉及到密码存储问题上,应该加密/生成密码摘要存储,而不是存储明文密码。好比以前的600w csdn帐号泄露对用户可能形成很大损失,所以应加密/生成不可逆的摘要方式存储。

 

2.4.1 散列算法

散列算法通常用于生成数据的摘要信息,是一种不可逆的算法,通常适合存储密码之类的数据,常见的散列算法如MD五、SHA等。通常进行散列时最好提供一个salt(盐),好比加密密码“admin”,产生的散列值是“21232f297a57a5a743894a0e4a801fc3”,能够到一些md5解密网站很容易的经过散列值获得密码“admin”,即若是直接对密码进行散列相对来讲破解更容易,此时咱们能够加一些只有系统知道的干扰数据,如用户名和ID(即盐);这样散列的对象是“密码+用户名+ID”,这样生成的散列值相对来讲更难破解。

package com.ssm.test;

 

import org.apache.shiro.crypto.hash.Md5Hash;

import org.junit.Test;

 

public class MD5Test {

  

   @Test

   public void testMD5() throws Exception {

     

      String password = "666";  // 密码:明文

     

      // 加密:md5

      Md5Hash md5Hash = new Md5Hash(password);

     

      System.out.println(md5Hash); // fae0b27c451c728867a567e8c1bb4e53

     

      // 加密:md5 + 盐salt

      md5Hash = new Md5Hash(password, "zhangsan");

      System.out.println(md5Hash); // 2f1f526e25fdefa341c7a302b47dd9df

  

      // 加密:md5 + 盐salt + 散列次数

      md5Hash = new Md5Hash(password, "zhangsan", 3);

      System.out.println(md5Hash); // cd757bae8bd31da92c6b14c235668091

   }

}

 

2.4.2 在Realm中应用

实际应用是将盐和散列后的值存在数据库中,自动realm从数据库取出盐和加密后的值由shiro完成密码校验。

 

1. 自定义加密以后realm,重写3个方法

2. 配置ini配置文件

3. 加载配置文件,测试

 

2.4.2.1 自定义Realm

package com.ssm.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;

import org.apache.shiro.util.ByteSource;

 

public class PasswordRealm extends AuthorizingRealm {

 

   @Override

   public String getName() {

      return "PasswordRealm";

   }

  

   // 受权操做

   protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {

      return null;

   }

 

   // 认证操做

   protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {

      System.out.println("token value :" + token);

     

      // 参数token:表示登陆时包装的UsernamePasswordToken

     

      // 经过用户名到数据库中查询用户信息,封装成一个AuthenticationInfo对象返回,方便认证器进行对比

      // 获取token中的用户名

      String username = (String) token.getPrincipal();

     

      // 经过用户名查询数据库,将该用户对应数据查询返回:帐号与密码

      // 假设查询数据库返回数据时:zhangsan  666

      if(!"zhangsan".equals(username)) {

        return null;

      }

     

      // 模拟数据库中保存加密以后密文:666 + 帐号  + 散列次数

      String password = "cd757bae8bd31da92c6b14c235668091";

     

      // info对象表示realm登陆比对信息:参数1:用户信息(真实登陆中是登陆对象user对象),参数2:密码,参数3:盐,参数4:当前realm名字

     

      SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(username, password, ByteSource.Util.bytes("zhangsan"), getName());

      System.out.println("info value :" + info);

      return info;

   }

}

 

2.4.2.2 添加shiro-cryptography.ini配置文件

[main]

#定义凭证匹配器

credentialsMatcher=org.apache.shiro.authc.credential.HashedCredentialsMatcher

#散列算法

credentialsMatcher.hashAlgorithmName=md5

#散列次数

credentialsMatcher.hashIterations=3

 

#将凭证匹配器设置到realm

myRealm=com.ssm.realm.PasswordRealm

myRealm.credentialsMatcher=$credentialsMatcher

securityManager.realms=$myRealm

 

2.4.2.3 登陆/退出

       @Test

   public void testLoginByPasswordRealm() {

     

      // 一、建立SecurityManager工厂对象:加载配置文件,建立工厂对象

      Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro-cryptography.ini");

     

      //二、建立SecurityManager

      SecurityManager securityManager = factory.getInstance();

     

      //三、将securityManager设置到运行环境中:目的是让系统随时随地能够访问securityManger对象

      SecurityUtils.setSecurityManager(securityManager);

     

      //四、建立当前登陆的主体,注意:此时主体没有通过认证

      Subject subject = SecurityUtils.getSubject();

     

      //五、收集主体登陆的身份 / 凭证 (建立token令牌),记录用户认证的身份和凭证即帐号和密码

      //参数1:将要登陆的用户名,参数2:登陆用户的密码

      // 若是参数1出错 :抛异常 org.apache.shiro.authc.UnknownAccountException

      // 若是参数2出错 :抛异常org.apache.shiro.authc.IncorrectCredentialsException

      UsernamePasswordToken token = new UsernamePasswordToken("zhangsan", "666");

     

      //六、主题登陆

      try {

        logger.info("主体登陆");

        subject.login(token);

      } catch (Exception e) {

        // 登陆失败

        e.printStackTrace();

        //System.out.println("testLogin exception value : " + e.getMessage());

        //System.out.println("testLogin exception value e : " + e);

      }

     

      //七、判断登陆是否成功

      System.out.println("验证登陆是否成功 :" + subject.isAuthenticated());

     

      //八、登出(注销)

      subject.logout();

      System.out.println("验证登陆是否成功 :" + subject.isAuthenticated());

   }

 

3.受权

3.1 受权模式:RBAC

shiro 受权(权限控制)实现是居于RBAC而实现的

谁(user)扮演什么角色(role), 能够作什么事情(permission)

 

RBAC: 基于角色的权限管理

简单理解为:谁扮演什么角色, 被容许作什么操做

 

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

 

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

 

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

 

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

 

张三--->普通用户--->受权---->VIP用户----->下载种子

 

3.2 受权流程

 

 

3.3 受权方式

Shiro支持三种方式的受权

 

3.3.1 编程式

经过写if/else受权代码块完成

 

Subject subject = SecurityUtils.getSubject(); 

if(subject.hasRole(“admin”)) { 

    //有权限 

} else { 

    //无权限 

 

 

3.3.2 注解式

经过在执行的Java方法上放置相应的注解完成

 

@RequiresRoles("admin") 

public void hello() { 

    //有权限 

}

 

 

3.3.3 JSP标签

在JSP页面经过相应的标签完成

 

<shiro:hasRole name="admin"> 

       <!— 有权限 —> 

</shiro:hasRole>

 

 

3.4 使用ini完成受权

权限表达式定义以下:

 

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

 

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

例子:

用户建立权限:user:create,或user:create:*

用户修改实例001的权限:user:update:001

用户实例001的全部权限:user:*:001

 

通常而已,咱们操做只须要关注前面两节:

资源:操做  :  

*:* : 全部资源的全部操做权限--->admin

 

3.4.1 添加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

 

3.4.2 角色测试

@Test

   public void testHasRole() throws Exception {

     

      Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro-permission.ini");

      SecurityManager securityManager = factory.getInstance();

      SecurityUtils.setSecurityManager(securityManager);

      Subject subject = SecurityUtils.getSubject();

      UsernamePasswordToken token = new UsernamePasswordToken("zhangsan", "666");

      subject.login(token);

     

      // 进行受权操做时前提:用户必须经过认证

     

      // 判断当前用户是否拥有某个角色

      System.out.println(subject.hasRole("role1"));

     

      // 判断当前用户是否拥有一些角色:返回true表示所有拥有,false 表示不所有拥有

      System.out.println(subject.hasAllRoles(Arrays.asList("role1", "role2")));

     

      // 判断当前用户是否拥有一些角色:返回boolean类型数据,true表示拥有某个角色,false表示没有

      System.out.println(Arrays.toString(subject.hasRoles(Arrays.asList("role1", "role2"))));

     

      // 判断当前用户是否拥有一些角色:没有返回值, 若是有角色,不作任何操做,没有报异常:org.apache.shiro.authz.UnauthorizedException

      subject.checkRole("role1");

     

      try {

        // 判断当前用户是否拥有一些角色

        subject.checkRoles("role1", "role2");

      } catch (Exception e) {

        e.printStackTrace();

      }

   }

 

3.4.3 权限测试

@Test

   public void testHasRole() throws Exception {

     

      Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro-permission.ini");

      SecurityManager securityManager = factory.getInstance();

      SecurityUtils.setSecurityManager(securityManager);

      Subject subject = SecurityUtils.getSubject();

      UsernamePasswordToken token = new UsernamePasswordToken("zhangsan", "666");

      subject.login(token);

     

      // 进行受权操做时前提:用户必须经过认证

      // 判断当前用户是否拥有某个权限,返回true表示拥有指定权限,false表示没有

      System.out.println(subject.isPermitted("user:delete"));

      // 判断当前用户是否拥有一些权限,返回true表示都拥有,返回false表示不全拥有

      System.out.println(subject.isPermittedAll("user:list", "user:delete"));

      // 判断当前用户是否拥有一些权限,返回boolean数组,true表示有false表示没有

      System.out.println(Arrays.toString(subject.isPermitted("user:list", "user:delete")));

     

      // 判断当前用户是否拥有某个权限,没有返回值,若是没有拥有指定权限报异常:org.apache.shiro.authz.UnauthorizedException

      try {

        subject.checkPermission("user:crea1te");

      } catch (Exception e) {

        e.printStackTrace();

      }

     

      // 判断当前用户是否拥有某个角色

      //System.out.println(subject.hasRole("role1"));

     

      // 判断当前用户是否拥有一些角色:返回true表示所有拥有,false 表示不所有拥有

      //System.out.println(subject.hasAllRoles(Arrays.asList("role1", "role2")));

     

      // 判断当前用户是否拥有一些角色:返回boolean类型数据,true表示拥有某个角色,false表示没有

      //System.out.println(Arrays.toString(subject.hasRoles(Arrays.asList("role1", "role2"))));

     

      // 判断当前用户是否拥有一些角色:没有返回值, 若是有角色,不作任何操做,没有报异常:org.apache.shiro.authz.UnauthorizedException

      subject.checkRole("role1");

     

      /*try {

        // 判断当前用户是否拥有一些角色

        subject.checkRoles("role1", "role2");

      } catch (Exception e) {

        e.printStackTrace();

      }*/

   }

 


3.5 自定义Realm完成受权

3.5.1 自定义Realm

package com.ssm.realm;

 

import java.util.ArrayList;

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;

 

public class PermissionRealm extends AuthorizingRealm {

 

       // 一个系统可能有不一样的realm,为了区分使用

       @Override

       public String getName() {

              return "PermissionRealm";

       }

      

       // 受权操做

       protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {

             

              // 传入参数:principals : 用户认证凭证信息:

              // SimpleAuthenticationInfo:认证方法返回封装认证信息中第一个参数:用户信息(username)

             

              // 当前登陆用户名信息:用户凭证

              String username = (String) principals.getPrimaryPrincipal();

            

              // 模拟查询数据库:查询用户实现指定的角色,以及用户权限

              List<String> roles = new ArrayList<String>(); // 角色集合

             

              List<String> permission = new ArrayList<String>(); // 权限集合

             

              //假设用户在数据库中有role1角色

              roles.add("role1");

             

              //假设用户在数据库中拥有user:delete权限

              //permission.add("user:delete");

              permission.add("user:*");

             

              // 返回用户在数据库中的权限与角色

              SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();

             

              info.addRoles(roles);

              info.addStringPermissions(permission);

             

              return info;

       }

 

       // 认证操做

       protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {

             

              System.out.println("token value :" + token);

             

              // 参数token:表示登陆时包装的UsernamePasswordToken

             

              // 经过用户名到数据库中查询用户信息,封装成一个AuthenticationInfo对象返回,方便认证器进行对比

              // 获取token中的用户名

              String username = (String) token.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("info value :" + info);

              return info;

       }

}

 

3.5.2 添加shiro-permission-realm.ini配置文件

[main]

#声明一个realm 

myReal=com.ssm.realm.PermissionRealm

#指定securityManager的realms实现 

securityManager.realms=$myReal

 

3.5.3 角色测试

       @Test

   public void testHasRoleByRealm() throws Exception {

     

      Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro-permission-realm.ini");

      SecurityManager securityManager = factory.getInstance();

      SecurityUtils.setSecurityManager(securityManager);

      Subject subject = SecurityUtils.getSubject();

      UsernamePasswordToken token = new UsernamePasswordToken("zhangsan", "666");

      subject.login(token);

     

      // 进行受权操做时前提:用户必须经过认证

      // 判断当前用户是否拥有某个权限,返回true表示拥有指定权限,false表示没有

      System.out.println("000"+subject.isPermitted("user:delete"));

     

      // 判断当前用户是否拥有某个角色

      System.out.println("111"+subject.hasRole("role1"));

   }

 

3.5.4 权限测试

       @Test

   public void testHasRole() throws Exception {

     

      Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro-permission.ini");

      SecurityManager securityManager = factory.getInstance();

      SecurityUtils.setSecurityManager(securityManager);

      Subject subject = SecurityUtils.getSubject();

      UsernamePasswordToken token = new UsernamePasswordToken("zhangsan", "666");

      subject.login(token);

     

      // 进行受权操做时前提:用户必须经过认证

      // 判断当前用户是否拥有某个权限,返回true表示拥有指定权限,false表示没有

      System.out.println(subject.isPermitted("user:delete"));

      // 判断当前用户是否拥有一些权限,返回true表示都拥有,返回false表示不全拥有

      System.out.println(subject.isPermittedAll("user:list", "user:delete"));

      // 判断当前用户是否拥有一些权限,返回boolean数组,true表示有false表示没有

      System.out.println(Arrays.toString(subject.isPermitted("user:list", "user:delete")));

     

      // 判断当前用户是否拥有某个权限,没有返回值,若是没有拥有指定权限报异常:org.apache.shiro.authz.UnauthorizedException

      try {

        subject.checkPermission("user:crea1te");

      } catch (Exception e) {

        e.printStackTrace();

      }

     

      // 判断当前用户是否拥有某个角色

      //System.out.println(subject.hasRole("role1"));

     

      // 判断当前用户是否拥有一些角色:返回true表示所有拥有,false 表示不所有拥有

      //System.out.println(subject.hasAllRoles(Arrays.asList("role1", "role2")));

     

      // 判断当前用户是否拥有一些角色:返回boolean类型数据,true表示拥有某个角色,false表示没有

      //System.out.println(Arrays.toString(subject.hasRoles(Arrays.asList("role1", "role2"))));

     

      // 判断当前用户是否拥有一些角色:没有返回值, 若是有角色,不作任何操做,没有报异常:org.apache.shiro.authz.UnauthorizedException

      subject.checkRole("role1");

     

      /*try {

        // 判断当前用户是否拥有一些角色

        subject.checkRoles("role1", "role2");

      } catch (Exception e) {

        e.printStackTrace();

      }*/

   }

 

3.6 受权流程分析

 

 

一、首先调用Subject.isPermitted*/hasRole*接口,其会委托给SecurityManager,而SecurityManager接着会委托给Authorizer

二、Authorizer是真正的受权者,若是咱们调用如isPermitted(“user:view”),其首先会经过PermissionResolver把字符串转换成相应的Permission实例;

三、在进行受权以前,其会调用相应的Realm获取Subject相应的角色/权限用于匹配传入的角色/权限;

四、Authorizer会判断Realm的角色/权限是否和传入的匹配,若是有多个Realm,会委托给ModularRealmAuthorizer进行循环判断,若是匹配如isPermitted*/hasRole*会返回true,不然返回false表示受权失败。

相关文章
相关标签/搜索