shiro 入门

温馨提示:

  • 看懂本文可能需要
    • 会使用 Spring
    • 配合相关视频进行学习
    • 懂中文 :)
  • 代码块过长,可左右滑动哦
  • 您的观看和点赞是对本公众号最大力的支持 ~~

本文章请配合相关视频进行学习 https://www.bilibili.com/video/BV1YW411M7S3

相关的源码在文章发布一两天内将会附上,公众号后台回复 shiro 即可

目录

ps: 目录有点长,不知道怎么换个好点的,只能先截图。。。。

shiro 入门


简介

相关历史和具体简介自行百度

  • Apache Shiro 是 Java 的一个安全(权限)框架
  • Shiro 可以非常容易的开发出足够好的应用,其不仅可以用在 JavaSE 环境,也可以用在 JavaEE 环境
  • Shiro 可以完成:认证、授权、加密、会话管理、与Web 集成、缓存 等
  • 下载:http://shiro.apache.org/

简介功能

shiro 入门
image-20201231133545983
  • Authentication:身份认证/登录,验证用户是不是拥有相应的身份;
  • Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用 户是否能进行什么操作,如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户 对某个资源是否具有某个权限;
  • Session Manager:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有 信息都在会话中;会话可以是普通 JavaSE 环境,也可以是 Web 环境的;
  • Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储;
  • Web Support:Web 支持,可以非常容易的集成到Web 环境;
  • Caching:缓存,比如用户登录后,其用户信息、拥有的角色/权限不必每次去查,这样可 以提高效率;
  • Concurrency:Shiro 支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去;
  • Testing:提供测试支持;
  • Run As:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问;
  • Remember Me:记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了

Shiro 架构

Shiro外部来看

shiro 入门
image-20201231135216493
  • Application Code:应用程序
  • Subject:应用代码直接交互的对象是 Subject,也就是说 Shiro 的对外 API 核心就是 Subject。Subject 代表了当前“用户”, 这个用户不一定 是一个具体的人,与当前应用交互的任何东西都是 Subject,如网络爬虫, 机器人等;与 Subject 的所有交互都会委托给 SecurityManager;Subject 其实是一个门面,SecurityManager 才是实际的执行者;
  • SecurityManager:安全管理器;即所有与安全有关的操作都会与 SecurityManager 交互;且其管理着所有 Subject;可以看出它是 Shiro 的核心,它负责与 Shiro 的其他组件进行交互,它相当于 SpringMVC 中 DispatcherServlet 的角色
  • Realm:Shiro 从 Realm 获取安全数据(如用户、角色、权限),就是说 SecurityManager 要验证用户身份,那么它需要从 Realm 获取相应的用户 进行比较以确定用户身份是否合法;也需要从 Realm 得到用户相应的角色/ 权限进行验证用户是否能进行操作;可以把 Realm 看成 DataSource

Shiro内部来看

shiro 入门
image-20201231150517072
  • Subject:任何可以与应用交互的“用户”;
  • SecurityManager :相当于SpringMVC 中的 DispatcherServlet;是 Shiro 的心脏;所有具体的交互都通过 SecurityManager 进行控制;它管理着所有 Subject、且负责进 行认证、授权、会话及缓存的管理。
  • Authenticator:负责 Subject 认证,是一个扩展点,可以自定义实现;可以使用认证 策略(Authentication Strategy),即什么情况下算用户认证通过了;
  • Authorizer:授权器、即访问控制器,用来决定主体是否有权限进行相应的操作;即控 制着用户能访问应用中的哪些功能;
  • Realm:可以有 1 个或多个 Realm,可以认为是安全实体数据源,即用于获取安全实体 的;可以是JDBC 实现,也可以是内存实现等等;由用户提供;所以一般在应用中都需要 实现自己的 Realm;
  • SessionManager:管理 Session 生命周期的组件;而 Shiro 并不仅仅可以用在 Web 环境,也可以用在如普通的 JavaSE 环境
  • CacheManager:缓存控制器,来管理如用户、角色、权限等的缓存的;因为这些数据 基本上很少改变,放到缓存中后可以提高访问的性能
  • Cryptography:密码模块,Shiro 提高了一些常见的加密组件用于如密码加密/解密。

HelloWorld

建立一个普通的maven工程

快速启动

(解读的内容在当前目录的 ./code/01-HelloWorld)

导入依赖

dependencies>
  <dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
  </dependency>
  <dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-all</artifactId>
    <version>1.3.2</version>
  </dependency>

  <dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.6.1</version>
  </dependency>
  <dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>1.6.1</version>
    <scope>test</scope> <!-- 注意这行要注释掉 -->
  </dependency>
</dependencies>

找到shiro给我们提供的HelloWorld,导入配置文件,导入 Quickstart.java 文件(注意文件编码)(这部分在源码中)

shiro 入门
image-20201231152826590

然后直接启动测试

shiro 入门
image-20201231155230020

解读这个java文件,在代码中

集成Spring

相关代码在 ./code/02-spring

加入Web模块

依赖增加

<dependencies>
    <!--Jackson required包-->
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-core</artifactId>
        <version>2.8.1</version>
    </dependency>
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.8.1</version>
    </dependency>
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-annotations</artifactId>
        <version>2.8.1</version>
    </dependency>

    <dependency>
        <groupId>commons-logging</groupId>
        <artifactId>commons-logging</artifactId>
        <version>1.1.3</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aop</artifactId>
        <version>4.0.0.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>4.0.0.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context-support</artifactId>
        <version>4.0.0.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-core</artifactId>
        <version>4.0.0.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-expression</artifactId>
        <version>4.0.0.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>4.0.0.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-orm</artifactId>
        <version>4.0.0.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-tx</artifactId>
        <version>4.0.0.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-web</artifactId>
        <version>4.0.0.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>4.0.0.RELEASE</version>
    </dependency>
    
    <dependency>
      <groupId>net.sf.ehcache</groupId>
      <artifactId>ehcache-core</artifactId>
      <version>2.4.3</version>
    </dependency>
</dependencies>

配置spring 并测试

然后加入shiro 的相关配置

  1. web.xml中配置  Shiro 的 shiroFilter.

    • <!-- Shiro Filter is defined in the spring application context: -->
      <!--
        1. 配置  Shiro 的 shiroFilter.
        2. DelegatingFilterProxy 实际上是 Filter 的一个代理对象. 默认情况下, Spring 会到 IOC 容器中查找和<filter-name> 对应的 filter bean. 也可以通过 targetBeanName 的初始化参数来配置 filter bean 的 id.
        -->

      <filter>
          <filter-name>shiroFilter</filter-name>
          <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
          <init-param>
              <param-name>targetFilterLifecycle</param-name>
              <param-value>true</param-value>
          </init-param>
      </filter>

      <filter-mapping>
          <filter-name>shiroFilter</filter-name>
          <url-pattern>/*</url-pattern>
      </filter-mapping>
  2. spring配置shiro

    • ShiroFilter 类似于如 Strut2/SpringMVC 这种 web 框架的前端控制器,是安全控制的入口点,其 负责读取配置(如ini 配置文件),然后判断URL 是否需要登录/权限等工作。
    • DelegatingFilterProxy 实际上是 Filter 的一个代理对象. 默认情况下, Spring 会到 IOC 容器中查找和<filter-name> 对应的 filter bean. 也可以通过 targetBeanName 的初始化参数来配置 filter bean 的 id.shiro 入门
    • 我们可以通过查看 org.springframework.web.filter.DelegatingFilterProxy类的注释分析,或者在异常处打个断点查看shiro 入门
  3. 编写一个类,实现 Realm接口shiro 入门

相关代码在 ./code/02-spring

ShiroFilter 的工作原理

shiro 入门
image-20210101202826699

部分细节

  • [urls] 部分的配置,其格式是:“url=拦截器[参数],拦截器[参数]”
  • 如果当前请求的 url 匹配 [urls] 部分的某个 url 模式,将会 执行其配置的拦截器
  • anon(anonymous)拦截器表示匿名访问(即不需要登 录即可访问)
  • authc (authentication)拦截器表示需要身份认证通过后 才能访问

shiro中默认的过滤器

shiro 入门
image-20210101210104228

URL 匹配模式

  • url 模式使用 Ant 风格模式
  • Ant 路径通配符支持 ?、 * 、 **,注意通配符匹配不 包括目录分隔符“/”:**
    • ?:匹配一个字符 ,如 /admin? 将匹配 /admin1,但不 匹配 /admin 或 /admin/;
    • *:匹配零个或多个字符串 ,如 /admin 将匹配 /admin、 /admin123,但不匹配 /admin/1;
    • **:匹配路径中的零个或多个路径 ,如 /admin/将匹 配 /admin/a 或 /admin/a/b

URL 匹配顺序

URL 权限采取第一次匹配优先的方式,即从头开始 使用第一个匹配的 url 模式对应的拦截器链

  • /bb/**=filter1
  • /**/bb/aa=filter2
  • /**=filter3
  • 如果请求的url是“/bb/aa”,因为按照声明顺序进行匹 配,那么将使用 filter1 进行拦截。

认证

直接点就是登陆

认证的过程:

  1. 获取当前的 Subject. 调用 SecurityUtils.getSubject();
  2. 测试当前的用户是否已经被认证. 即是否已经登录. 调用 Subject 的 isAuthenticated()
  3. 若没有被认证, 则把用户名和密码封装为 UsernamePasswordToken 对象
  4. 创建一个表单页面
  5. 把请求提交到 SpringMVC 的 Handler
  6. 获取用户名和密码.
  7. 执行登录: 调用 Subject 的 login(AuthenticationToken) 方法.
  8. 自定义 Realm 的方法, 从数据库中获取对应的记录, 返回给 Shiro.
    • 实际上需要继承 org.apache.shiro.realm.AuthenticatingRealm 类(如果仅仅需要认证,可以继承这个类,实现 doGetAuthenticationInfo(AuthenticationToken) 方法)
    • 步骤
    1. 把 AuthenticationToken 转型为 UsernamePasswordToken
    2. 从 UsernamePasswordToken 中获取 username
    3. 调用数据库的方法,从数据库中查询 username 对应的用户记录(这里就不查询直接给出了)
    4. 如果用户不存在,则可以抛出 UnknownAccountException
    5. 根据用户信息的情况,决定是否需要抛出其他的 AuthenticationException
    6. 根据用户的情况,来构建 AuthenticationInfo 对象返回,这个实现类通常使用 SimpleAuthenticationInfo
  9. 由 shiro 完成对密码的比对.

直接看代码

Subject 的 login(AuthenticationToken) 分析

shiro 入门
image-20210102004239439
shiro 入门
image-20210102004248580
shiro 入门
image-20210102004254695
shiro 入门
image-20210102004303168
shiro 入门
image-20210102004309856
shiro 入门
image-20210102004316734
shiro 入门
image-20210102004324436

密码加密

密码比对分析

比对密码肯定要调用 getPassword方法,因此可以在这个方法上打断点

shiro 入门
image-20210102225522177

往上找

shiro 入门
image-20210102225234585

密码的比对是通过 AuthenticatingRealm 的 credentialsMatcher 属性来进行的密码的比对

MD5加密方式

但是实际上,我们存储密码是不可能通过明文存储,那么对于加密是怎么比对的呢,其实就是用到了 CredentialsMatcher 类

这个 CredentialsMatcher 有多个实现类,其中包括 MD5

shiro 入门
image-20210102225605896

接下来我们需要更换一下我们的 Realm 为 MD5的 Realm

  <bean id="jdbcRealm" class="com.wu.shiro.realms.ShiroRealm">
    <property name="credentialsMatcher">
      <bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
        <property name="hashAlgorithmName" value="MD5"/>
      </bean>
    </property>
  </bean>

之后就会自动使用 MD5 把前台传过来的数据进行加密

注意数据库中存储的密码还是不变,由于我们是直接给出密码,因此我们需要提前将密码用相同的条件进行加密

shiro 入门
image-20210102231114554

那怎么自动呢?同样开始调试(getPassword方法的断点还在)

shiro 入门
image-20210102231549633

可以看到自动加密的,现在要看加密过程,由于加密后是 tokenHashedcredentials 变量,所以直接看hashProvidedCredentials方法

shiro 入门
image-20210102231752849

继续点入

shiro 入门
image-20210102231807815

实际上就是这行的作用,其中我们可以看到加密的次数,默认是一次,密码有些常见,因此我们可以考虑反复加密,及生成的密码再加密多次,安全性更高

反复加密

所以我们还可以指定一下加密次数

  <bean id="jdbcRealm" class="com.wu.shiro.realms.ShiroRealm">
      .....
        <property name="hashIterations" value="1024"/>
  </bean>

盐值加密

但是还有一个问题,就是同一个明文,可以得到同样的密码,可以采用暴力破解的方式进行破解,还是不安全,因此考虑加点佐料,即加盐

需要修改第六步的代码,将盐加进去

// 。。。。
// 加盐
// 6. 根据用户的情况,来构建 AuthenticationInfo 对象返回,这个实现类通常使用 SimpleAuthenticationInfo
//      以下信息是从数据库中获取的
Object principal = username; // 认证实体的信息
Object credentials = null// 密码
if ("admin".equals(username)) {
    credentials = "038bdaf98f2037b31f1e75b5b4c9b26e";
else if ("user".equals(username)) {
    credentials = "098d2c478e9c11555ce2823231e02ec1";
}
ByteSource credentialsSalt = ByteSource.Util.bytes(username); // 盐值
String realmName = getName(); // 当前 realm 对象的 name ,直接调用父类的即可
return new SimpleAuthenticationInfo(principal, credentials, credentialsSalt, realmName);

同样不链接数据库,因此需要明确给出真实密码

public static void main(String[] args) {
    String algorithmName = "MD5";
    Object source = "123456";
    Object adminSalt = ByteSource.Util.bytes("admin");
    Object userSalt = ByteSource.Util.bytes("user");
    int hashIterations = 1024;
    // 下面这行对象是通过调试源码看出来的
    SimpleHash adminSaltHash = new SimpleHash(algorithmName, source, adminSalt, hashIterations);
    SimpleHash userSaltHash = new SimpleHash(algorithmName, source, userSalt, hashIterations);
    System.out.println("admin:" + adminSaltHash);
    System.out.println("user:" + userSaltHash);
}
// admin:038bdaf98f2037b31f1e75b5b4c9b26e
// user:098d2c478e9c11555ce2823231e02ec1

多Realm验证

如果我们现在有多个数据库(MySQL,Oracle),并且采用不同的加密方式,这时候就需要进行多 Realm 验证

先来查看一下多 Realm 是怎么进行验证的

同样登录方法进入

shiro 入门
image-20210103115153944

进入到管理器的实现

shiro 入门
image-20210103121651522
shiro 入门
image-20210103121657071
shiro 入门
image-20210103121703571

找到父级的实现

shiro 入门
image-20210103121727079
shiro 入门
image-20210103121732792

在这个方法中,会获取到所有 Realm 然后判断是多个还是一个,进而执行对应的方法

shiro 入门
image-20210103121738394

这个方法所在的类是ModularRealmAuthenticator,所以我们给这个类的 realms 属性配置多个 Realm,然后稍微的往上看,这个类是AuthenticatingSecurityManager的一个属性,因此还需要将这个类配置给它的authenticator属性

shiro 入门
image-20210103121744701

具体实现代码

  1. 拷贝一份 Realm ,将加密方式更换为 SHA1shiro 入门

  2. 配置bean

<beans>
    ...
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="cacheManager" ref="cacheManager"/>
        <property name="authenticator" ref="authenticator"/>
    </bean>
    
    ...
    <!-- 配置多个 Realm -->
    <bean class="org.apache.shiro.authc.pam.ModularRealmAuthenticator" id="authenticator">
        <property name="realms">
            <list>
                <ref bean="jdbcRealm"/>
                <ref bean="secondRealm"/>
            </list>
        </property>
    </bean>
    <!--
    3. 配置 Realm
    3.1 直接配置实现了 org.apache.shiro.realm.Realm 接口的 bean
  -->

    <bean id="jdbcRealm" class="com.wu.shiro.realms.ShiroRealm">
        <property name="credentialsMatcher">
            <bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
                <property name="hashAlgorithmName" value="MD5"/>
                <property name="hashIterations" value="1024"/>
            </bean>
        </property>
    </bean>
    <bean id="secondRealm" class="com.wu.shiro.realms.SecondRealm">
        <property name="credentialsMatcher">
            <bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
                <property name="hashAlgorithmName" value="SHA1"/>
                <property name="hashIterations" value="1024"/>
            </bean>
        </property>
    </bean>
</beans> 

认证策略

有两个 Realm 怎么样才算认证通过呢,这就需要我们的认证策略(AuthenticationStrategy)

AuthenticationStrategy 实际上是一个接口,有三个实现:

  • FirstSuccessfulStrategy:只要有一个 Realm 验证成功即可,只返回第 一个 Realm 身份验证成功的认证信息,其他的忽略
  • AtLeastOneSuccessfulStrategy:只要有一个Realm验证成功即可,和 FirstSuccessfulStrategy 不同,将返回所有Realm身份验证成功的认证信息
  • AllSuccessfulStrategy:所有Realm验证成功才算成功,且返回所有 Realm身份验证成功的认证信息,如果有一个失败就失败了

ModularRealmAuthenticator 默认是 AtLeastOneSuccessfulStrategy 策略

那怎么修改认证策略呢?

同样 debug 方式查看,以相同的方式进入到这个方法

shiro 入门
image-20210103121738394

由于是多 Realm 认证,因此看doMultiRealnAuthentication (realms, authenticationToken);方法

shiro 入门
image-20210103160643589

然后打断点,调试,发现strategy变量就是我们的认证策略

shiro 入门
image-20210103160705568

同样,这个变量是通过getAuthenticationstrategy ()方法得到,我们可以修改authenticationstrategy属性所在的类即可,this指针指向ModularRealmAuthenticatorshiro 入门

所以在 bean中配置一下即可

  <bean class="org.apache.shiro.authc.pam.ModularRealmAuthenticator" id="authenticator">
    。。。
    <property name="authenticationStrategy">
      <bean class="org.apache.shiro.authc.pam.AllSuccessfulStrategy"/>
    </property>
  </bean>

将Reamls配置给securityManage

但是由于接下来授权的时候需要从securityManage中读 Realm,所以我们考虑将Reamls配置给securityManage

还记得之前配置的情况吗

<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
    <property name="cacheManager" ref="cacheManager"/>
    <property name="realms" ref="jdbcRealm"/>
</bean>

之前这里我们直接把单个 Realm直接配置到securityManager中,也是能成功运行的,其实securityManager还有一个 realms 属性,所以我们也可以把之前的多 Realm 的配置配到 securityManager 中

<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
    <property name="cacheManager" ref="cacheManager"/>
    <property name="authenticator" ref="authenticator"/>
    <!--  换个地方  -->
    <property name="realms">
        <list>
            <ref bean="jdbcRealm"/>
            <ref bean="secondRealm"/>
        </list>
    </property>
    
    <!--  把ModularRealmAuthenticator中配置的多 Realm 删掉  -->
</bean>

运行测试,实际上是可以的,但是我们在 debug 的时候发现了新的问题,就是这个属性并没有调用到!下面是调试的过程(还是同样断点的地方)

shiro 入门
image-20210103203029257
shiro 入门
image-20210103203034876

可以看到他还是调用了同样的方法,但是他有值,要知道我们并没有配置,所以只有一种可能,那就是在初始化的时候 securityManager会把多Realms设置给了ModularRealmAuthenticator,要设置,那么肯定要调用set方法,所以我们可以在ModularRealmAuthenticator的set方法上面打上断点

shiro 入门
image-20210103203300786

然后重新 debug方式运行,之后程序确实卡在了这一行,我们往上找,找到了一个方法

shiro 入门
image-20210103203402557

可以看到在父类中,通过这样代码,将Realm设置给了ModularRealmAuthenticator(先判断authorizer属性是否是ModularRealmAuthenticator类型,如果是就将 Realms 设置进去,因为我们在 xml 文件中给securityManager配置的确实是这种类型,所以进入了这个判断)

更换认证策略

最后,我们修改一下我们的验证方式

<bean class="org.apache.shiro.authc.pam.ModularRealmAuthenticator" id="authenticator">
    <property name="authenticationStrategy">
        <bean class="org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy"/>
    </property>
</bean>

重新运行测试

授权

注意要先将多Realm配置给securityManage(看 多Realm验证 — 将Reamls配置给securityManage 部分)

基本概念

授权,也叫访问控制,即在应用中控制谁访问哪些资源(如访问页面/编辑数据/页面操作 等)。在授权中需了解的几个关键对象:主体(Subject)、资源(Resource)、权限 (Permission)、角色(Role)。

  • 主体(Subject):访问应用的用户,在 Shiro 中使用 Subject 代表该用户。用户只有授权后才允许访问相应的资源
  • 资源(Resource):在应用中用户可以访问的 URL,比如访问 JSP 页面、查看/编辑某些 数据、访问某个业务方法、打印文本等等都是资源。用户只要授权后才能访问。
  • 权限(Permission):安全策略中的原子授权单位,通过权限我们可以表示在应用中用户 有没有操作某个资源的权力。即权限表示在应用中用户能不能访问某个资源,如:访问用 户列表页面查看/新增/修改/删除用户数据(即很多时候都是CRUD(增查改删)式权限控制)等。权限代表了用户有没有操作某个资源的权利,即反映在某个资源上的操作允不允许
    • Shiro 支持粗粒度权限(如用户模块的所有权限)和细粒度权限(操作某个用户的权限, 即实例级别的)
  • 角色(Role):权限的集合,一般情况下会赋予用户角色而不是权限,即这样用户可以拥有 一组权限,赋予权限时比较方便。典型的如:项目经理、技术总监、CTO、开发工程师等 都是角色,不同的角色拥有一组不同的权限

授权方式

Shiro 支持三种方式的授权:

  • 编程式:通过写if/else 授权代码块完成
  • 注解式:通过在执行的Java方法上放置相应的注解完成,没有权限将抛出相应的异常
  • JSP/GSP 标签:在JSP/GSP 页面通过相应的标签完成
shiro 入门
image-20210104002349485

后面解释

默认拦截器

  • Shiro 内置了很多默认的s拦截器,比如身份验证、授权等 相关的。默认拦截器可以参考 org.apache.shiro.web.filter.mgt.DefaultFilter中的枚举拦截器:shiro 入门

身份验证相关的

shiro 入门
image-20210103235553807

授权相关的

shiro 入门
image-20210103235604899

其他

shiro 入门
image-20210103235613346

授权使用

加页面,加配置,具体看代码

<property name="filterChainDefinitions">
    <!--   除了 /login.jsp 可以匿名访问,其他都是需要登录,因此 login.jsp 的配置是必要的  -->
    <value>
        /login.jsp = anon
        /shiro/login = anon
        /shiro/logout = logout
        <!--    配置用户访问权限    -->
        /user.jsp = roles[user]
        /admin.jsp = roles[admin]

        # everything else requires authentication:
        /** = authc
    </value>
</property>

启动测试,user.jsp是没有权限访问的,admin.jsp同理(因为我们没有配置具体权限,只是配置了认证)

接下来我们配置一下具体的权限

  1. 授权需要继承AuthorizingRealm 类, 并实现其 doGetAuthorizationInfo 方法
  2. AuthorizingRealm 类继承自 AuthenticatingRealm, 但没有实现 AuthenticatingRealm 中的doGetAuthenticationInfo, 所以认证和授权只需要继承 AuthorizingRealm 就可以了. 同时实现他的两个抽象方法.
shiro 入门
image-20210103213105894

改写 ShiroRealm 类,让他继承 AuthorizingRealm,然后编写doGetAuthorizationInfo方法

  1. 从 principalCollection 中来获取登录用户的信息
  2. 利用登录的用户的信息来给予当前用户的角色或权限(可能需要查询数据库)
  3. 创建 AuthorizationInfo 的实现类 SimpleAuthorizationInfo,并设置 reles 属性
  4. 返回这个对象
// 授权会被 shiro 回调的方法
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
    // 1. 从 principalCollection 中来获取登录用户的信息
    Object principal = principalCollection.getPrimaryPrincipal();
    // 2. 利用登录的用户的信息来给予当前用户的角色或权限(可能需要查询数据库)
    Set<String> roles = new HashSet<>();
    roles.add("user");
    if ("admin".equals(principal)) { // 如果是管理员,加多一个身份
        roles.add("admin");
    }
    // 3. 创建 AuthorizationInfo 的实现类 SimpleAuthorizationInfo,并设置 roles 属性
    // 4. 返回这个对象
    return new SimpleAuthorizationInfo(roles);
}

然后运行测试,admin能访问所有页面,而user只能访问 user.jsp

授权源码分析

shiro 入门
image-20210103213245052

单 Realm

他是怎么进行授权的呢,点进方法

shiro 入门
image-20210103214752473

继续点进 hasRole 方法

shiro 入门
image-20210103214803382

可以看到有多个类,但是我们使用的是 单 Realm,所以我们点第一个

shiro 入门
image-20210103214846923

第一个是单 Realm,第二个会调用this.authorizer.hasRole方法,而authorizer其实就是ModularRealmAuthorizer,也就是第三,也就是说第二第三个其实没多大区别,都是多 Realm,下面会说

idea的继承看不太清楚这里贴上 eclipse的

shiro 入门
image-20210103215200993

点进 getAuthorizationInfo方法

shiro 入门
image-20210103215302784

往下找到doGetAuthorizationlnfo点入

shiro 入门
image-20210103215332500

这时候就进入到AuthorizingRealm类的doGetAuthorizationInfo方法,查看继承体系,可以看到我们写的类把这个抽象方法实现了

shiro 入门
image-20210103215506623

多 Realm

同样步骤,但是 hasRole方法需要进入到第三个

shiro 入门
image-20210103214846923

继续点刚刚的 hasRole方法,发现又回到了刚刚的接口

shiro 入门
image-20210103222208092

感觉有点像调用链(递归)??

可以发现,只要有一个 Realm 授权通过,就会return true

PrincipalCollection类

形参里面有一个变量,类型是PrincipalCollection,我们看一下他的继承体系图

shiro 入门
image-20210103235340501

实际是第一个类型

shiro 入门
image-20210103235345166

我们看看这个get方法的调用过程

shiro 入门
image-20210103235350456

jsp授权 – shiro标签

由于 jsp 在前后端分离开发中不常见,所以这部分会忽略较多

需要导入一个 shiro的标签库<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>idea会自动导入

shiro 入门shiro 入门

简单举例

修改 list.jsp

<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
  <title>Title</title>
</head>
<body>
<h2>List Page</h2>
Welcome:<shiro:principal/>

<shiro:hasRole name="admin">
  <br><br>
  <a href="admin.jsp">admin page</a>
</shiro:hasRole>

<shiro:hasRole name="user">
  <br><br>
  <a href="user.jsp">user page</a>
</shiro:hasRole>

<br><br>
<a href="shiro/logout">logout</a>
</body>
</html>

注解授权 – 权限注解

  • @RequiresAuthentication:表示当前Subject已经通过login 进行了身份验证;即 Subject. isAuthenticated() 返回 true
  • @RequiresUser:表示当前 Subject 已经身份验证或者通过记 住我登录的
  • @RequiresGuest:表示当前Subject没有身份验证或通过记住 我登录过,即是游客身份
  • @RequiresRoles(value={“admin”, “user”}, logical= Logical.AND):表示当前 Subject 需要角色 admin 和user
  • @RequiresPermissions (value={“user:a”, “user:b”}, logical= Logical.OR):表示当前 Subject 需要权限 user:a 或 user:b。

编写一个Service然后加入bean中,并注入到controller,然后稍微修改一下 list.jsp

service

shiro 入门
image-20210104085036849

controller

shiro 入门
image-20210104085033885

list.jsp

shiro 入门
image-20210104085015460

不知道是不是版本的原因,还需要创建一个默认建议自动代理创建器加入到容器中,才能保证这个注解生效

https://blog.csdn.net/qq_35981283/article/details/78631924 Shiro @RequiresRoles注解不生效解决方案及相应设置

  <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>
  <bean class="com.wu.shiro.service.ShiroServiceImpl" id="shiroService"/>

注意:如果Service加上事务注解,它变成了代理,这时权限注解就需要标注在Controller层,不能让Service变成代理的代理,否则会发生类型转化异常

权限从数据库中获取

我们发现这一块直接卸载配置文件中肯定不太好,一般来说这些玩意保存在数据库中,以便动态更新

shiro 入门
image-20210104092258522

这个内容我们是配置在 filterChainDefinitions属性之上,我们点击去对应的set方法查看细节

shiro 入门
image-20210104092524637

通过最后一行设置进入,继续点进

shiro 入门
image-20210104092613356

发现他最终放入到这个类的这个属性之上,我们打一个断点运行,查看这个具体是什么类型

shiro 入门
image-20210104093143671

可以看到这个属性是 LinkHashMap,因此我们可以考虑将我们通过数据库查询出来的权限设置进这里

所以我们可以把内容配置到一个工厂类中

工厂类

public class FilterChainDefinitionMapBuilder {
    public Map<String, String> buildFilterChainDefinitionMap() {
        Map<String, String> map = new LinkedHashMap<>();
        // 假设下方是查询数据库得到的数据
        map.put("login.jsp""anon");

        map.put("shiro/login""anon");
        map.put("shiro/logout""logout");

        map.put("user.jsp""roles[user]");
        map.put("admin.jsp""roles[admin]");
        map.put("/**""authc");
        return map;
    }
}

配置文件

<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
    。。。
    <property name="filterChainDefinitionMap" ref="filterChainDefinitionMap"/>
</bean>  

<!-- 配置一个 bean,这个 bean 实际上是一个 map,通过实例工厂方法的方式 -->
<bean id="filterChainDefinitionMap" factory-bean="filterChainDefinitionMapBuilder"
      factory-method="buildFilterChainDefinitionMap"/>

<bean class="com.wu.shiro.factory.FilterChainDefinitionMapBuilder"
      id="filterChainDefinitionMapBuilder"/>

会话管理

概述

Shiro 提供了完整的企业级会话管理功能,不依赖于底层容 器(如web容器tomcat),不管 JavaSE 还是 JavaEE 环境 都可以使用,提供了会话管理、会话事件监听、会话存储/ 持久化、容器无关的集群、失效/过期支持、对Web 的透明 支持、SSO 单点登录的支持等特性

会话相关的 API

这个玩意的使用和 web 中的 Session的使用基本一致

  • Subject.getSession():即可获取会话;其等价于 Subject.getSession(true),即如果当前没有创建 Session 对象会创建 一个;Subject.getSession(false),如果当前没有创建 Session 则返回 null
  • session.getId():获取当前会话的唯一标识
  • session.getHost():获取当前Subject的主机地址
  • session.getTimeout() & session.setTimeout(毫秒):获取/设置当 前Session的过期时间
  • session.getStartTimestamp() & session.getLastAccessTime():获取会话的启动时间及最后访问时间;如果是 JavaSE 应用需要自己定 期调用 session.touch() 去更新最后访问时间;如果是 Web 应用,每 次进入 ShiroFilter 都会自动调用 session.touch() 来更新最后访问时间。
  • session.touch() & session.stop():更新会话最后访问时 间及销毁会话;当Subject.logout()时会自动调用 stop 方法 来销毁会话。如果在web中,调用 HttpSession. invalidate() 也会自动调用Shiro Session.stop 方法进行销毁Shiro 的会 话
  • session.setAttribute(key, val) & session.getAttribute(key) & session.removeAttribute(key):设置/获取/删除会话属 性;在整个会话范围内都可以对这些属性进行操作

会话监听器

会话监听器用于监听会话创建、过期及停止事件

shiro 入门
image-20210104144237424

对比一下我们的 HttpSessionListener

shiro 入门
image-20210104144406659

web层还是建议使用原生的HTTPSession,但是这个我们没办法从service去访问httpsession因为controller是顶层的api,但是有了shiro就可以

使用举例

controller层

@RequestMapping("testShiroAnnotation")
public String testShiroAnnotation(HttpSession session) {
    // 注意这个session是 HttpSession
    session.setAttribute("key""value123456");
    shiroService.testMethod();
    return "redirect:/list.jsp";
}

service层

    @RequiresRoles({"admin"})
    public void testMethod() {
        System.out.println("testMethod, time: " + new Date());
        // 获取Session,取得数据并打印输出
        // 注意这个 session 是 shiro 的
        System.out.println("Service SessionVal: " +
                SecurityUtils.getSubject().getSession().getAttribute("key"));
    }

运行测试

shiro 入门
image-20210104144953108

SessionDao

这个类可以把session写到数据库中,之后进行增删改查操作,然后对session进行管理,这部分用的不是很多

继承体系

shiro 入门
image-20210104161659296
  • AbstractSessionDAO 提供了 SessionDAO 的基础实现, 如生成会话ID等
  • CachingSessionDAO 提供了对开发者透明的会话缓存的功能,需要设置相应的 CacheManager
  • MemorySessionDAO 直接在内存中进行会话维护
  • EnterpriseCacheSessionDAO 提供了缓存功能的会话维护,默认情况下使用 MapCache 实现,内部使用 ConcurrentHashMap 保存缓存的会话。开发的时候可以直接继承这个类

查看一下 EnterpriseCacheSessionDAO 的基本API

这个类有基本的增删改查方法

shiro 入门
image-20210104162040142

简单使用(这里我报错了。。)

我们就以EnterpriseCacheSessionDAO为例进行使用,我们看看 doCreate方法

shiro 入门
image-20210104185914933

其中第一行需要传入一个 SessionId,进入看看

shiro 入门
image-20210104185953139

发现,如果不传入,他会报异常,所以这个 ID必须要有,我们看看这个 ID的类型

shiro 入门
image-20210104190025497

看看他的继承体系

shiro 入门
image-20210104190037174

这里我们以 JavaUuidSessionIdGenerator作为 id,所以我们在配置 bean 的时候,需要将这个类配置给 ID 所在的类

bean

<!-- Session Id 生成器 -->
<bean class="org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator" id="sessionIdGenerator"/>
<!-- Session Dao 继承 EnterpriseCacheSessionDAO -->
<bean class="com.wu.shiro.dao.MySessionDao" id="sessionDao">
    <property name="activeSessionsCacheName" value="shiro-activeSessionCache"/>
    <property name="sessionIdGenerator" ref="sessionIdGenerator"/>
</bean>
<!-- 会话管理器 -->
<bean class="org.apache.shiro.session.mgt.DefaultSessionManager" id="sessionManager">
    <property name="globalSessionTimeout" value="1800000"/>
    <property name="deleteInvalidSessions" value="true"/>
    <property name="sessionValidationSchedulerEnabled" value="true"/>
    <property name="sessionDAO" ref="sessionDao"/>
</bean>

具体细节就先忽略,以为我报错了

会话验证

需要开多一个线程,会造成一定的性能问题,用的不多

  • Shiro 提供了会话验证调度器,用于定期的验证会话是否已过期,如果过期将停止会话
  • 出于性能考虑,一般情况下都是获取会话时来验证会话是否过期并停止会话的;但是如在 web 环境中,如果用户不主动退出是不知道会话是否过期的,因此需要定期的检测会话是否过期,Shiro 提供了会话验证调度器 SessionValidationScheduler
  • Shiro 也提供了使用Quartz会话验证调度器:QuartzSessionValidationScheduler

缓存

CacheManagerAware 接口

Shiro 内部相应的组件(DefaultSecurityManager)会自动检测相应的对象(如Realm)是否实现了 CacheManagerAware 并自动注入相应的 CacheManager

Realm 缓存

Shiro 提供了 CachingRealm,其实现了 CacheManagerAware 接口,提供了缓存的一些基础实现

AuthenticatingRealm 及 AuthorizingRealm 也分别提 供了对AuthenticationInfo 和 AuthorizationInfo 信息的缓 存

我们看一下我们配置的 Realm

shiro 入门
image-20210105161604783

看看这个继承的方法

shiro 入门
image-20210105161717510
shiro 入门
image-20210105161726292
shiro 入门
image-20210105161736500

这个接口会自动注入 我们的 cacheManage

shiro 入门
image-20210105161742371
shiro 入门
image-20210105191722879
shiro 入门
image-20210105191752810

如果要去除缓存,可以把上面的 cacaheManage和 securityManager中的CacheManager属性 删了,或者可以利用下面这个属性

shiro 入门
image-20210105191845360

启动调试,如果每次都能进入对应的调试方法(Realm中的验证方法),就说明缓存删除成功

我们配置的缓存配置就是在这个文件中

shiro 入门
image-20210105191855748

Session 缓存

如 SecurityManager 实现了 SessionSecurityManager, 其会判断 SessionManager 是否实现了 CacheManagerAware 接口,如果实现了会把 CacheManager 设置给它

SessionManager 也会判断相应的 SessionDAO(如继承 自CachingSessionDAO)是否实现了 CacheManagerAware,如果实现了会把 CacheManager 设置给它

设置了缓存的 SessionManager,查询时会先查缓存,如 果找不到才查数据库

到后面,可以使用Redis作为 session缓存

RememberMe

Shiro 提供了记住我(RememberMe)的功能,比如访问如淘宝 等一些网站时,关闭了浏览器,下次再打开时还是能记住你是谁, 下次访问时无需再登录即可访问,基本流程如下

  1. 首先在登录页面选中 RememberMe 然后登录成功;如果是 浏览器登录,一般会把 RememberMe 的Cookie 写到客户端并 保存下来
  2. 关闭浏览器再重新打开;会发现浏览器还是记住你的
  3. 访问一般的网页服务器端还是知道你是谁,且能正常访问
  4. 但是比如我们访问淘宝时,如果要查看我的订单或进行支付 时,此时还是需要再进行身份认证的,以确保当前用户还是你
shiro 入门
image-20210107003015059

如果把这行注释掉,可以发现刷新就需要重新登录了

认证和记住我

  • subject.isAuthenticated() 表示用户进行了身份验证登录的, 即使有 Subject.login 进行了登录
  • subject.isRemembered():表示用户是通过记住我登录的, 此时可能并不是真正的你(如你的朋友使用你的电脑,或者 你的cookie 被窃取)在访问的

两者二选一,即 subject.isAuthenticated()==true,则 subject.isRemembered()==false;反之一样

建议

访问一般网页:如个人在主页之类的,我们使用user 拦截 器即可,user 拦截器只要用户登录 (isRemembered() || isAuthenticated())过即可访问成功

访问特殊网页:如我的订单,提交订单页面,我们使用 authc 拦截器即可,authc 拦截器会判断用户是否是通过 Subject.login(isAuthenticated()==true)登录的,如 果是才放行,否则会跳转到登录页面叫你重新登录

简单使用

还记得前面的几张图嘛

shiro 入门
image-20210107004337740

修改配置

shiro 入门
image-20210107003924853

然后启动,打开浏览器访问,用admin登录,发现 admin.jsp  user.jsp 是可以访问

然后关闭浏览器,重新打开,访问 list.jsp,发现可以直接访问,因为记住了 但是  admin.jsp  user.jsp 需要重新登录才可以访问

设置记住我的时间

这里我们打一个断点运行

shiro 入门
image-20210107005905126

找到DefaultWebSecurityManager,查看变量信息,找到this.rememberMeManager.cookie.maxAge发现,有默认时间,31536000秒

shiro 入门
image-20210107005856014

所以我们可以修改这个变量信息

shiro 入门
image-20210107010418016

重新运行,登录后关闭浏览器,10秒后再打开试试,因为十秒内是被记住的,正常是需要重新登录的


end

您的观看和点赞是对本公众号最大力的支持 ~~


原文始发于微信公众号(一个调皮的bug):shiro 入门

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/43178.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!