Shiro学习 —— 基础知识

01 | Shiro框架介绍

  • 知识点
  1. 什么是 Shiro
  2. Shiro 三大关键组件:Subject,SecurityManager 和 Realm
  3. Shiro 的体系结构
1. 什么是Shiro
  1. Apache Shiro 是一个开源安全框架,它为开发人员提供了直观简洁的 API 来支持应用程序中的四个安全性基石:

    1. 身份验证(Authentication)。
    2. 授权(Authorization)。
    3. 会话管理(Session Management)。
    4. 加密(Cryptography)。
      在这里插入图片描述
  2. Shiro 可实现的功能

    管理员可以为应用系统创建不同的角色,角色可以绑定一组权限,之后管理员可以为不同用户分配一个或多个角色,并在应用正常提供服务期间在后台管理网页中随时更改所有这些功能。

2. Shiro 关键概念和术语
  1. 认证(Authentication)
    认证是验证用户身份的过程,在用户访问系统时,通过比对用户提供的身份信息和预先存储在系统中的身份信息,确定用户是其“本人”。 (登陆)
  2. 授权(Authorization)
    授权,也称为访问控制,是通过检查用户的角色和权限确定用户是否被允许做某事的过程。
  3. 凭证 (Credential)
    凭证是用来核实用户身份的一类信息,在验证尝试期间,凭证与用户信息一起提交,应用程序对用户提交的凭证进行验证以确定是相关用户。凭证通常是非常秘密的东西,只有特定的用户才会知道,一般为密码,密钥以及一些生物识别数据,如指纹、面容等。
  4. 权限 (Permission)
    权限是一个描述应用程序中原始功能的声明,权限是安全策略中最底层的结构,它们只定义了应用程序可以做“什么”,而不描述“谁”能够执行这些操作,权限只是一个行为声明。
  5. 用户标识 (Principal)
    Principal 是被应用程序用来识别用户身份的任何具有唯一性的属性,用户标识可以是任何对系统而言“有意义”的东西,如用户名、手机号、用户 ID 等。
  6. 领域 (Realm)
    Realm 是用户身份信息,权限,角色信息的提供者。Realm 从系统数据源(JDBC,File IO 或 LDAP 等)中获取数据,转交给 Shiro 以进行身份认证和权限管理,实际开发中一般不会直接实现 Realm 接口,而是基于其子类 AuthenticatingRealm 或 AuthorizingRealm 进行扩展,以减少工作量。
  7. 角色 (Role)
    可以看成是一组权限的集合,如果一个用户的角色是 A,那就意味着他拥有角色 A 涵盖的所有权限。
  8. 会话(Session)
    会话是一个有状态的数据上下文,它与在一段时间内和应用程序交互的单个用户相关联。当用户登陆系统时,会话被创建,我们可以将数据保存到会话中,并在需要时从会话中读取这些数据,当用户主动登出系统或由于长时间不与系统交互而导致超时被动登出系统时,会话数据将被销毁。
  9. 主题(Subject)
    Subject 只是一个安全术语,一般表示系统用户的一个特定安全 “视图”,Subject 并不总是指代一个人,它也可以代表一个调用系统服务的外部进程,比如在一段时间内会间歇性执行某些任务的守护类系统账户(比如 cron 作业),基本上它是任何实体的代表,这些实体会与应用程序一起做一些事情。
3. Shiro的体系结构
  1. 如图:shiro的最上层主要有三个组件

    最上层结构

  2. Subject:表示会与应用程序交互的任何东西。

    Subject 实例都强制绑定到 SecurityManager 上,当我们调用 Subject 的方法时,具体的操作最终都会转交给相关的 SecurityManager 进行处理。

  3. SecurityManager:核心,负责协调与其他组件的关系

    一旦为应用程序配置了 SecurityManager,通常就不需要再理会它,我们只需要和 Subject 的 API 进行交互就可以了。

  4. Realm:与应用程序数据之间交换的“桥梁”, 当 Shiro 真正需要与安全性相关的数据(例如用户帐户,用户角色,权限等)进行交互以执行身份验证和授权时,Shiro 会从一个或多个为应用程序配置的 Realm 中查找这些数据。

    可以简单地把 Realm 理解为特定于安全性的 DAO,它封装了对安全性相关数据的访问,在配置 Shiro 时,我们必须至少指定一个 Realm 以用于身份验证(或授权)。


SecurityManager实际上并没有做什么实质的东西,因为shiro是高度模块化的,所以SecurityManager相当于一个容器,将几乎所有的行为委托给封装在内部的其它组件。

  • shiro核心体系结构如下:

img

  1. 关于身份认证器Authenticator):负责登录功能验证,Authenticator 知道如何与一个或多个 Realm 进行协调,从这些 Realm 中获取用户的身份信息,然后完成身份认证过程。
  2. 访问控制器Authorizer) : 负责权限验证,Authorizer 也知道如何与多个 Realm 协调,以获取角色和权限信息,Authorizer 使用这些信息来确定用户是否被允许执行某个动作。

02 | Shiro身份认证(Authentication)

  • 主要功能特性:

    1. 一切基于当前用户:即都是基于Subject,所以在代码中任何地方都能很容易的获取到当前用户(SecurityUtils.getSubject())。

    2. 仅一个方法调用就可以完成用户认证:即:currentUser.login(token)

    3. 有快速定位的能力:shiro提供了异常定义,如:

      • UnknownAccountException: 未知账号。
      • IncorrectCredentialsException:密码错误。
      • LockedAccountException:账号被冻结。

      这几个异常类型可能会在登录失败时抛出,根据具体异常类型,我们能够快速定位问题。

    4. 用户身份信息来源可定制化:要对用户进行认证,那么就需要获取到用户的真实身份信息。在 Shiro 中,这部分数据的提供者为 Realm,Realm 充当着 Shiro Authentication 与系统数据源交互的重任,应用需要通过实现 Realm 接口的方式将从系统数据源(JDBC,File IO 或 LDAP 等)中获取到的用户身份信息转交给 Shiro,Shiro Authentication 才能进行身份认证。

  • 与身份验证有关的概念术语:

    1. Principal:用户标识
    2. Credential:凭证
    3. Authentication Token:用户标识与凭证的载体,SecurityManager 的 login(token) 方法内部会直接将 AuthenticationToken 交给 Authenticator,由 Authenticator 最终执行身份验证/登录过程。在一般开发过程中我们使用最多的是 UsernamePasswordToken,它是 AuthenticationToken 的一个具体实现。
  • 原生API实现登录功能:

    1. 创建一个maven项目,目录结构如下图所示
      在这里插入图片描述

    2. 修改pom.xml文件

          <dependencies>
              <dependency>
                  <groupId>org.apache.shiro</groupId>
                  <artifactId>shiro-core</artifactId>
                  <version>1.4.1</version>
              </dependency>
      
              <dependency>
                  <groupId>commons-logging</groupId>
                  <artifactId>commons-logging</artifactId>
                  <version>1.2</version>
              </dependency>
          </dependencies>
      
          <build>
              <plugins>
                  <plugin>
                      <groupId>org.apache.maven.plugins</groupId>
                      <artifactId>maven-compiler-plugin</artifactId>
                      <version>3.8.0</version>
                      <configuration>
                          <source>1.6</source>
                          <target>1.6</target>
                          <encoding>UTF-8</encoding>
                      </configuration>
                  </plugin>
      
                  <plugin>
                      <groupId>org.codehaus.mojo</groupId>
                      <artifactId>exec-maven-plugin</artifactId>
                      <version>1.1</version>
                      <executions>
                          <execution>
                              <goals>
                                  <goal>java</goal>
                              </goals>
                          </execution>
                      </executions>
                      <configuration>
                          <classpathScope>test</classpathScope>
                          <mainClass>com.sample.App</mainClass>
                      </configuration>
                  </plugin>
              </plugins>
          </build>
      
    3. resource下新建文件shiro.ini

      [users]
      root = secret_pwd
      guest = guest_pwd
      
    4. java包下新建类Demo

      import org.apache.shiro.SecurityUtils;
      import org.apache.shiro.authc.*;
      import org.apache.shiro.config.IniSecurityManagerFactory;
      import org.apache.shiro.mgt.SecurityManager;
      import org.apache.shiro.session.Session;
      import org.apache.shiro.subject.Subject;
      import org.apache.shiro.util.Factory;
      
      public class Demo {
          public static void main(String[] args) {
              IniSecurityManagerFactory factory = new IniSecurityManagerFactory("classpath:shiro.ini");
              SecurityManager securityManager = (SecurityManager) factory.getInstance();
              SecurityUtils.setSecurityManager((org.apache.shiro.mgt.SecurityManager) securityManager);
      
              // 获取当前用户
              Subject currentUser = SecurityUtils.getSubject();
              if (!currentUser.isAuthenticated()) {
                  // 使用用户名和密码进行登录
                  UsernamePasswordToken token = new UsernamePasswordToken("guest", "guest_pwd");
                  token.setRememberMe(true);
                  try {
                      currentUser.login(token);
                      System.out.println("登录成功");
                  } catch (UnknownAccountException uae) {
                      System.out.println("用户名为 [" + token.getPrincipal() + "] 的用户不存在");
                  } catch (IncorrectCredentialsException ice) {
                      System.out.println("密码错误");
                  } catch (LockedAccountException lae) {
                      System.out.println("用户已被冻结");
                  } catch (AuthenticationException ae) {
                      System.out.println("未知错误: " + ae);
                  }
              }
          }
      }
      
    5. 运行,控制台打印出相关信息。

      在这里插入图片描述

03 | Shiro授权 (Authrization)

  • 主要功能特性:

    1. 一切基于当前用户:我们在代码的任何地方都能很容易的获取到当前用户(SecurityUtils.getSubject()),并对其身份信息和权限信息进行检索。
    2. 支持基于角色和基于权限的授权方式:在开发时,可选择一种或同时选择两种。
    3. 提供多种方式来实践权限控制:Subject对象、注解、AOP、以及JSP Taglibs
    4. 支持任何数据模型:shiro进行权限检查时只关心最终答案:是否有权限
  • 关键概念与术语:

    • 权限Permission
    • 角色Role
    • 用户Users) : Shiro中的用户指的是Subject,应用程序中用User来指代用户。
  • 基于用户角色的授权:通过检查用户是否拥有某一角色而确定其是否能够执行具体操作。

    • Subject 提供了检查用户角色的能力:

      Subject currentUser = SecurityUtils.getSubject();
      
      if (currentUser.hasRole("administrator")) {
          // 用户拥有 administrator 角色
      } else {
          // 用户没有 administrator 角色
      }
      
      对用户角色进行检查的能力的类似方法:
      hasRoles(List<String> roleNames) 
      hasAllRoles(Collection<String> roleNames)
      
      checkRole(String roleName)checkRoles(Collection<String> roleNames)checkRoles(String... roleNames) 三个方法也能检查用户角色,不同的是,这三个方法在内部如果检查出用户不具有相应角色的话会直接抛出异常(AuthorizationException)。  
      
  • 基于权限的授权 : (更优) 可通过subject的isPermitted()方法来实现

    • isPermitted()方法接受的参数有两类:字符串 或 Permission接口。

      1. 通过实现 Permission 接口来承载权限信息

        Permission 接口提供了对权限表述进行检查的能力,能够确保系统中使用的权限符合你对权限定义的一些限制。比如你希望一个权限的声明是严格对应到系统数据模型中的具体模型(或者说资源)的,对模型的操作只应该是模型支持的几种(比如 CRUD)。

      2. 通过字符串承载权限信息 ( 通配符权限 )

        • "printer:query":字符串分成两个部分,第一部分表示操作者(printer),第二部分表示允许执行的操作(query)。
        • "printer:print,query":一个操作者有执行多个操作权限的方式,第一部分表示操作者(printer),第二部分表示允许执行的操作(query 和 print)。
        • "printer:*"* 表示所有,即操作者(printer)拥有所有操作权限。
        • "printer:print:epsoncolor":具体到实例的方式,表示操作者(printer)能够执行(print)操作,另外对操作的对象(epsoncolor)也进行了定义。
  • 使用原生API实现权限检查 : 在原生API实现登录功能的基础上

    1. 修改shiro.ini

      [users]
      root = secret_pwd, admin
      guest = guest_pwd, writer, reader
      
      [roles]
      admin = *
      writer = book:write
      reader = lib:view:java-in-action
      
    2. 在demo.java中增加语句

      //1. 基于角色的权限检查,检查用户是否拥有 writer 角色。
      if (currentUser.hasRole("writer")) {
          System.out.println("拥有「writer」角色");
      } else {
          System.out.println("没有「writer」角色");
      }
      
       //2. 基于权限(对具体资源的操作权限)的权限检查,检查用户是否有“写书”权限。
       if (currentUser.isPermitted("book:write")) {
           System.out.println("拥有「写书」权限");
       } else {
           System.out.println("没有「写书」权限");
       }
      
      
       //3. 对具体资源实例的操作权限检查。
       if (currentUser.isPermitted("lib:view:java-in-action")) {
           System.out.println("有权阅读 java-in-action");
       } else {
           System.out.println("无权阅读 java-in-action");
       }
      
    3. 在实际开发过程中往往不会直接通过 Subject 的 isPermitted*(arg)hasRole(arg) 等方法进行权限检查,更多的是使用注解(@RequiresRoles,@RequiresPermissions等)进行权限检查,结合 Spring 和 AOP,使用注解的方式能够显著提高我们的开发效率

04 | Shiro会话管理 (Session Manager)

  • Session Listener : 监听会话中发生的生命周期事件,可以通过实现 SessionListener 接口,或继承 SessionListenerAdapter 来接收这些通知。主要有如下三种通知:

    1. void onStart(Session session),会话开始。
    2. void onStop(Session session),会话结束,在主动调用 Session.stop()Subject.logout() 时发生。
    3. void onExpiration(Session session),会话过期。
  • Session Storage : 对会话进行持久化(创建或更新时),当会话过期或不再使用时这些持久化数据需要被删除(释放占用空间),SessionManager 实现这些 CRUD 操作的具体组件是 SessionDAO。同样的,有必要的话我们也可以实现自己的 SessionDAO,来接管会话数据的持久化行为。

  • Session的相关API

    • 如下代码所示:

      		// 获取当前用户
              Subject currentUser = SecurityUtils.getSubject();
      
              Session session = currentUser.getSession();
              session.setAttribute("username","guest");
      
      		if(session != null){
                  System.out.println("当前登陆用户为:" + session.getAttribute("username"));
              }else{
                  System.out.println("会话未创建");
              }
      
      ===> 登录成功
      当前登陆用户为:guest
      

完成。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值