shiro学习一:了解shiro,学习执行shiro的流程。使用springboot的测试模块学习shiro单应用(demo 6个)

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
RDS MySQL Serverless 高可用系列,价值2615元额度,1个月
简介: 这篇文章是关于Apache Shiro权限管理框架的详细学习指南,涵盖了Shiro的基本概念、认证与授权流程,并通过Spring Boot测试模块演示了Shiro在单应用环境下的使用,包括与IniRealm、JdbcRealm的集成以及自定义Realm的实现。

前言

  • 以前开发过springboot整合shiro的权限部分,但是后来不开发,都忘了。

  • 现在整理一下,从shiro的单应用到shiro开发,一步步到项目的整合开发,以及源码的分析。

  • 代码地址https://github.com/fengfanli/springboot-shiro

一、初识 shiro

1. 简介

  • shiro是apache的一个开源框架,而且呢是一个权限管理的框架,用于实现用户认证、用户授权。

  • spring 中也有一个权限框架 spring security (原名Acegi),它和 spring 依赖过于紧密,没有 shiro 使用简单。

  • shiro 不依赖于 spring,shiro 不仅可以实现 web应用的权限 管理,还可以实现c/s系统,分布式系统权限管理,shiro属于轻量框架,越来越多企业项目开始使用shiro。使用shiro实现系统的权限 管理,有效提高开发效率,从而降低开发成本。

2. 逻辑与关键字

在这里插入图片描述

  • subject:主体,可以是用户也可以是程序,主体要访问系统,系统需要对主体进行认证、授权。
  • security Manager:安全管理器,主体进行认证和授权都是通过securityManager进行。
  • authenticator:认证器,主体进行认证最终通过authenticator进行的。
  • authorizer:授权器,主体进行授权最终通过authorizer进行的。
  • sessionManager:web应用中一般是用web容器对session进行管理,shiro也提供一套session管理的方式。
  • SessionDao: 通过SessionDao管理session数据,针对个性化的session数据存储需要使用sessionDao。
  • cache Manager:缓存管理器,主要对session和授权数据进行缓存,比如将授权数据通过cacheManager进行缓存管理,和ehcache整合对缓存数据进行管理。
  • Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储。
  • realm:域,领域,相当于数据源,通过realm存取认证、授权相关数据。

记住一点,Shiro 不会去维护用户、维护权限;这些需要我们自己去设计/提供;然后通过相应的接口注入给Shiro 即可。

3. 认证流程

在这里插入图片描述

  1. 构建SecurityManager环境
  2. 主体提交认证
  3. SecurityManager 处理
  4. 流转到 Authenticator 执行认证
  5. 通过 Realm 获取相关的用户信息(获取验证数据进行验证)

4. 授权流程

在这里插入图片描述

  1. 创建构建SecurityManager环境
  2. 主体提交授权认证
  3. SecurityManager 处理
  4. 流转到 Authorizor 授权器执行授权认证
  5. 通过 Realm 从数据库或配置文件获取角色权限数据返回给授权器,进行授权。

二、使用springboot 测试单元行模拟测试

1. 创建项目

New----->Project----->Spring Initializr 一路默认后就创建好一个以spring boot构建的web项目了
主要界面:
在这里插入图片描述
在这里插入图片描述

2. 加入依赖包 1.4.1

        <!-- shiro spring. -->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-core</artifactId>
            <version>1.2.2</version>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.4.1</version>
        </dependency>

三、 shiro 用户认证 (demo1)

1. 流程逻辑

  1. 构建安全管理器环境 DefaultWebSecurityManager
  2. 构建数据源 这里先使用 SimpleAccountRealm (后面可以做成读取动态读取数据库)
  3. 获取主体
  4. 用户名和密码生成token(这里也就是 用户输入的用户名密码 )
  5. 进行登入(提交认证)

这里先熟悉下流程,认证和源码过程再下面讲

2. 编写 Ch01_authentication 测试类

package com.feng;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.realm.SimpleAccountRealm;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
public class Ch01_authentication {
    /**
     * shiro 认证 初使用
     */
    @Test
    public void authentication() {
        // 1. 构建安全管理器环境
        /*
        * 如果爆这个错:java.lang.IllegalArgumentException: SessionContext must be an HTTP compatible implementation.
        * 1). 因为 版本低,要 1.4.1以上,
        * 2). 或者使用 DefaultSecurityManager 这个类,这个是单机版,DefaultWebSecurityManager 这个是 web 版本
        * */
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        // 2. 构建数据源,创建一个SimpleAccountRealm 域
        SimpleAccountRealm simpleAccountRealm = new SimpleAccountRealm();
        // 添加一个测试账号(后面可以做成读取动态读取数据库)
        simpleAccountRealm.addAccount("feng", "123456");
        // 设置Realm
        securityManager.setRealm(simpleAccountRealm);
        SecurityUtils.setSecurityManager(securityManager);

        // 3. 获取主体
        Subject subject = SecurityUtils.getSubject();
        // 4. 用户名和密码(这里也就是 用户输入的用户名密码 )生成token
        UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("feng", "123456");
        System.out.println(usernamePasswordToken.getUsername());
        System.out.println(usernamePasswordToken.getPassword());
        System.out.println(usernamePasswordToken.getPrincipal());
        System.out.println(usernamePasswordToken.getCredentials());
        System.out.println(usernamePasswordToken.getHost());
        System.out.println(usernamePasswordToken.toString());
        try {
            // 5. 进行登入(提交认证)
            subject.login(usernamePasswordToken);

        } catch (UnknownAccountException e) {
            //username wasn't in the system, show them an error message?
            System.out.println("账号不存在");
        } catch (IncorrectCredentialsException ice) {
            System.out.println("用户名密码不匹配");
            //password didn't match, try again?
        } catch (LockedAccountException lae) {
            //account for that username is locked - can't login.  Show them a message?
            System.out.println("账号已被禁用,请联系系统管理员");
        } catch (AuthenticationException ae) {
            //unexpected condition - error?
            System.out.println("用户认证失败");
        }

        System.out.println("用户认证的状态:isAuthenticated=" + subject.isAuthenticated());
        // 登出logout
        System.out.println("执行 logout()方法后");
        subject.logout();
        System.out.println("用户认证的状态:isAuthenticated=" + subject.isAuthenticated());
    }

}

3. 测试

  • 当用户输入用户名密码为“feng”、“123456” 程序输出
    在这里插入图片描述
  • 当用户输入的用户名密码为‘feng“、”1234567“(密码错误) 程序输出
    在这里插入图片描述
  • 当用户输入的用户名密码为"feng1"、“123456”(用户名错误) 程序输出
    在这里插入图片描述

四、 shiro 用户授权 (demo2)

上面的小 demo 是 shiro 实现认证的一个过程,做权限管理的时候,分为两块,一个认证,一个是授权,认证是判断用户是否账号密码正确,授权是判断用户登入以后有什么权限

1. 流程逻辑

与上面的用户认证一致,我就直接复制过来了,

  1. 构建安全管理器环境 DefaultWebSecurityManager
  2. 构建数据源 这里先使用 SimpleAccountRealm (后面可以做成读取动态读取数据库)
  3. 获取主体
  4. 用户名和密码生成token(这里也就是 用户输入的用户名密码 )
  5. 进行登入(提交认证)

2. 编写Ch02_authorization 测试类

package com.feng;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.UnauthorizedException;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.realm.SimpleAccountRealm;
import org.apache.shiro.subject.Subject;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
public class Ch02_authorization {
    /**
     * shiro 授权 初使用
     */
    @Test
    public void authorization() {
        // 1. 构建安全管理器环境
        DefaultSecurityManager securityManager = new DefaultSecurityManager();
        // 2. 构建数据源,创建一个SimpleAccountRealm 域
        SimpleAccountRealm simpleAccountRealm = new SimpleAccountRealm();
        //添加一个测试账号、和所拥有的角色(后面可以做成读取动态读取数据库)
        simpleAccountRealm.addAccount("feng", "123456", "admin", "test");
        // 设置Realm
        securityManager.setRealm(simpleAccountRealm);
        SecurityUtils.setSecurityManager(securityManager);

        // 3. 获取主体
        Subject subject = SecurityUtils.getSubject();
        // 4. 用户名和密码(用户输入的用户名密码)生成token
        UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("feng", "123456");
        try {
            // 5.进行登入(提交认证)
            subject.login(usernamePasswordToken);
            // 6. 检查是否有角色
            subject.checkRoles("admin", "test");
//            subject.checkRoles("admin", "user"); // 该用户没有权限访问
//            subject.checkRoles("user");  // 该用户没有权限访问
        } catch (UnknownAccountException e) {
            //username wasn't in the system, show them an error message?
            System.out.println("账号不存在");
        } catch (IncorrectCredentialsException ice) {
            System.out.println("用户名密码不匹配");
            //password didn't match, try again?
        } catch (LockedAccountException lae) {
            //account for that username is locked - can't login.  Show them a message?
            System.out.println("账号已被禁用,请联系系统管理员");
        } catch (AuthenticationException ae) {
            //unexpected condition - error?
            System.out.println("用户认证失败");
        } catch (UnauthorizedException e) {
            System.out.println("该用户没有权限访问");
        }

        System.out.println("---------->用户认证的状态:" + subject.isAuthenticated());
        //登出logout
        System.out.println("执行 logout()方法后");
        subject.logout();
        System.out.println("---------->用户认证的状态:" + subject.isAuthenticated());
    }
}

3. 修改地方

修改一处,添加一处。

//添加一个测试账号、和所拥有的角色(后面可以做成读取动态读取数据库)
simpleAccountRealm.addAccount("feng", "123456", "admin", "test");

// 6. 检查是否有角色
subject.checkRoles("admin", "test");
//subject.checkRoles("admin", "user"); // 该用户没有权限访问
//subject.checkRoles("user");  // 该用户没有权限访问

4. 测试

  • 当用户要 subject.checkRoles("admin","test"); 检测是否拥有 admin、test角色的时候 程序输出
    在这里插入图片描述
  • 当用户要 subject.checkRoles("user","test");检测是否拥有 test、user 角色的时候 程序输出
    在这里插入图片描述
  • checkRoles ()方法就是检测用户是否拥有传入的角色,即只要有一个不是用户所拥有的角色就会抛出异常。请看下列源码。
    在这里插入图片描述
    在这里插入图片描述

四、使用IniRealm进行认证授权

上面的两个demo中我们利用 SimpleAccountRealm 在程序中写死了用户安全数据,接下来我们使用 .ini将数据移到配置文件中

1. 概述

IniRealm是Shiro提供一种Realm实现。用户、角色、权限等信息集中在一个.ini文件那里。

2. 配置 .ini 文件

resources目录下创建一个 shiro.ini文件
从文件中可以看出:
用户admin 属于 admin 角色,拥有:所有的权限
用户test属于 test 角色,拥有:user:list,user:deleted,user:edit 三个权限

#账号信息
[users]
#账号=密码,角色
test=123456,test
admin=123456,admin


#角色信息
[roles]
test=user:list,user:deleted,user:edit,user:add
admin=*

在这里插入图片描述

3. 编写 Ch03_testIniRealm 测试类

流程与上不变,主要修改的是数据源的部分
在这里插入图片描述

package com.feng;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.UnauthorizedException;
import org.apache.shiro.realm.text.IniRealm;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
public class Ch03_testIniRealm {

    /**
     * 读取 .ini配置文件 作为 数据源Realm
     */
    @Test
    public void testIniRealm() {
        // 配置文件中用户权限信息,文件在类路径下
        IniRealm initRealm = new IniRealm("classpath:shiro.ini");

        // 1. 构建安全管理器环境
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        // 2. 设置Realm
        securityManager.setRealm(initRealm);
        SecurityUtils.setSecurityManager(securityManager);

        // 3. 获取主体
        Subject subject = SecurityUtils.getSubject();
        // 4. 用户名和密码的token
        UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("admin", "123456");
        try {
            // 5.主体提交认证请求
            subject.login(usernamePasswordToken);
            System.out.println("---------->用户认证的状态:isAuthenticated=" + subject.isAuthenticated());
            // 6. 检查是否有角色
            subject.checkRoles("admin"); // test用户没有权限访问
            // 检查是够有权限
            subject.checkPermissions("user:add","user:userList");
            System.out.println("---------->用户认证的状态:isAuthenticated=" + subject.isAuthenticated());

            System.out.println("执行 logout()方法后");
            subject.logout();
            System.out.println("---------->用户认证的状态:" + subject.isAuthenticated());
        } catch (UnknownAccountException e) {
            //username wasn't in the system, show them an error message?
            System.out.println("账号不存在");
        } catch (IncorrectCredentialsException ice) {
            System.out.println("用户名密码不匹配");
            //password didn't match, try again?
        } catch (LockedAccountException lae) {
            //account for that username is locked - can't login.  Show them a message?
            System.out.println("账号已被禁用,请联系系统管理员");
        } catch (AuthenticationException ae) {
            //unexpected condition - error?
            System.out.println("用户认证失败");
        } catch (UnauthorizedException e) {
            // 关于是够有权限的 异常
            System.out.println("该用户没有权限访问");
        }
    }
}

4. 测试

  • 用户 test进入到程序时候我们首先用 IniRealm 读取 shiro.ini配置获得 test 用户的一些安全信息。
    subject.checkRoles("test"):即是判断该用户是否拥有 test 角色。
    subject.checkPermissions("user:add","user:list"):即是检查用户是否拥有 user:list 的权限
    很明显test 这个用户都满足这些条件,最后程序输出
    在这里插入图片描述
  • 假如把 subject.checkRoles("test") 改成 subject.checkRoles("amin") 即验证用户 test 是否拥有 admin 角色呢?
    这个时候很明显会抛出异常,因为我们在 shiro.ini 配置里面只给 test 用户 配置了 test 的角色。
    在这里插入图片描述
  • 假如是用户 admin 登录进来呢?
    很明显不会抛异常,因为用户 admin 拥有了 admin 这个角色,而 admin 这角色设置了 ‘*’ 的通配符即拥有所有的权限所以不会抛出异常。
    程序输出
    在这里插入图片描述

五、使用 JdbcRealm 进行认证授权

上一个案例demo我们主要讲了把用户安全信息(相应的角色/权限)配置在 .ini 文件,使用 IniRealm 去读取 .ini 文件获得用户的安全信息。也是有局限性的。因为我们得事先把所有用户信息配置在.ini 文件,这样显然是行不通的,我们的系统用户都是动态的不固定的,它的一些用户信息权限信息都是变化的,所以固定在.ini 配置文件显然是行不通的。这些数据通常我们都是把它存入到DB 中,那shiro 有没有提供直接从DB读取用户安全信息的域呢 ? (Realm)

shiro 作为一个优秀的开源框架,显然是可以的。

下面就该 JdbcRealm 出场了。

1. 新加依赖:数据库驱动和数据源

        <!--数据库驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>

        <!--数据源-->
        <!--http://localhost:8083/druid/index.html 通过这个网址 对 SQL进行监控-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.10</version>
        </dependency>

2. 数据库表结构

下面我们创建一个名为shiro的数据库、分别创建三张表 users、user_roles、roles_permissions
在这里插入图片描述

创建数据库shiro

CREATE DATABASE IF NOT EXISTS shiro DEFAULT CHARSET utf8 COLLATE utf8_general_ci;

创建users`表

CREATE TABLE `users` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(25) DEFAULT NULL,
  `password` varchar(100) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

创建user_roles

CREATE TABLE `user_roles` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `role_name` varchar(25) DEFAULT NULL,
  `username` varchar(25) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

创建roles_permissions

CREATE TABLE `roles_permissions` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `permission` varchar(255) DEFAULT NULL,
  `role_name` varchar(25) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

往三个表中插入一些数据

INSERT INTO `shiro`.`users` (`id`, `username`, `password`) VALUES ('1', 'admin', '123456');
INSERT INTO `shiro`.`users` (`id`, `username`, `password`) VALUES ('2', 'test', '123456');

INSERT INTO `shiro`.`user_roles` (`id`, `role_name`, `username`) VALUES ('1', 'admin', 'admin');
INSERT INTO `shiro`.`user_roles` (`id`, `role_name`, `username`) VALUES ('2', 'test', 'test');

INSERT INTO `shiro`.`roles_permissions` (`id`, `permission`, `role_name`) VALUES ('1', 'user:deleted', 'test');
INSERT INTO `shiro`.`roles_permissions` (`id`, `permission`, `role_name`) VALUES ('2', 'user:list', 'test');
INSERT INTO `shiro`.`roles_permissions` (`id`, `permission`, `role_name`) VALUES ('3', '*', 'admin');
INSERT INTO `shiro`.`roles_permissions` (`id`, `permission`, `role_name`) VALUES ('4', 'user:edit', 'test');

3. 编写 Ch04_testJdbcRealm测试类

流程逻辑其实与上面几个案例一致。
主要修改的地儿 还是数据域这里,其余不变
在这里插入图片描述

package com.feng;

import com.alibaba.druid.pool.DruidDataSource;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.UnauthorizedException;
import org.apache.shiro.realm.jdbc.JdbcRealm;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
public class Ch04_testJdbcRealm {

    /**
     * 读取shiro创建好的 JdbcRealm ,sql语句也写好
     */
    @Test
    public void testJdbcRealm() {
        // 配置数据源
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setUrl("jdbc:mysql://192.168.131.168:3306/shiro");
        druidDataSource.setDriverClassName("com.mysql.jdbc.Driver");
        druidDataSource.setUsername("root");
        druidDataSource.setPassword("Dataadt123!");

        // 1. 构建安全管理器环境
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();

        // 2. 搭建数据源 Realm
        // 配置文件中的用户权限信息,文件在类路径下
        JdbcRealm jdbcRealm = new JdbcRealm();
        jdbcRealm.setDataSource(druidDataSource);
        // 使用 JdbcRealm 下面的值需要为 true 不然无法查询用户权限
        jdbcRealm.setPermissionsLookupEnabled(true);
        // 设置 Realm
        securityManager.setRealm(jdbcRealm);
        SecurityUtils.setSecurityManager(securityManager);

        // 3. 获取主体
        Subject subject = SecurityUtils.getSubject();
        // 4. 用户名和密码的token
        UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("test", "123456");
        try {
            // 5.主体提交认证
            subject.login(usernamePasswordToken);
            System.out.println("---------->用户认证的状态:isAuthenticated=" + subject.isAuthenticated());
            // 6. 检查是否有角色
            subject.checkRoles("test");
            System.out.println("---------->有 test 角色");
            // 7. 检查是够有权限
            subject.checkPermissions("user:update");
            System.out.println("---------->有 user:update 权限");

            System.out.println("---------->用户认证的状态:isAuthenticated=" + subject.isAuthenticated());
            System.out.println("执行 logout()方法后");
            subject.logout();
            System.out.println("---------->用户认证的状态:" + subject.isAuthenticated());
        } catch (UnknownAccountException e) {
            //username wasn't in the system, show them an error message?
            System.out.println("账号不存在");
        } catch (IncorrectCredentialsException ice) {
            System.out.println("用户名密码不匹配");
            //password didn't match, try again?
        } catch (LockedAccountException lae) {
            //account for that username is locked - can't login.  Show them a message?
            System.out.println("账号已被禁用,请联系系统管理员");
        } catch (AuthenticationException ae) {
            //unexpected condition - error?
            System.out.println("用户认证失败");
        } catch (UnauthorizedException e) {
            // 关于是够有权限的 异常
            System.out.println("该用户没有权限访问");
        }
    }
}

4. 测试

  • 当用户 admin 登录进来的时候 程序输出
    在这里插入图片描述
  • 当用户 test 登录进来的时候 程序输出
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("test", "123456");

在这里插入图片描述

5. 源码分析

看了这个例子,为什么我们没有写 sql 怎么查询用户信息和角色,权限信息的呢?我们来看JdbcRealm源码
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
从以上源码得知其实 JdbcRealm 已经帮我们写好查询语句了,所以我们就要在数据库创建与之对应的表结构,这样才能查出数据,但是有的同学可能就有疑问了,这里只能使用他默认的 sql,在实际的开发中,我们不可能就简单的使用 JdbcRealm 默认的 sql 语句,而是自己自定义的 sql 语句,更多时候我们的数据库以及数据表都是根据业务需要自己创建的。
而且自己创建的表中字段必须与上面源码中一致,才可以。

因此需要自定义写SQL,看如下案例。

六、自定义SQL认证授权

流程逻辑其实还是不变,都是一样的,改变的地儿还是数据域这里(Realm),看类即可,我不再测试了

1. 编写 Ch05_testNewJdbcRealm 类

package com.feng;

import com.alibaba.druid.pool.DruidDataSource;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.UnauthorizedException;
import org.apache.shiro.realm.jdbc.JdbcRealm;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
public class Ch05_testNewJdbcRealm {

    /**
     * 修改数据库的表,统一加前缀 sys_:: users=>sys_users,user_roles=>sys_user_roles, roles_permissions=>sys_roles_permissions
     * 进行查询自定义的表
     */
    @Test
    public void testNewJdbcRealm() {
        // 配置数据源
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setUrl("jdbc:mysql://192.168.131.168:3306/shiro");
        druidDataSource.setDriverClassName("com.mysql.jdbc.Driver");
        druidDataSource.setUsername("root");
        druidDataSource.setPassword("Dataadt123!");

        // 1. 构建安全管理器环境
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();

        // 2. 配置数据源
        // 配置文件中的用户权限信息,文件在类路径下
        JdbcRealm jdbcRealm = new JdbcRealm();
        jdbcRealm.setDataSource(druidDataSource);
        // 使用 JdbcRealm 下面的值需要为 true 不然无法查询用户权限
        jdbcRealm.setPermissionsLookupEnabled(true);

        // 使用自定义的 SQL 查询用户权限
        String sql = "select password from users where username =?";
        jdbcRealm.setAuthenticationQuery(sql);
        String roleSql = "select role_name from user_roles where username = ?";
        jdbcRealm.setUserRolesQuery(roleSql);
        String permissionSql = "select permission from roles_permissions where role_name = ?";
        jdbcRealm.setPermissionsQuery(permissionSql);
        // 设置 Realm
        securityManager.setRealm(jdbcRealm);
        SecurityUtils.setSecurityManager(securityManager);

        //获取主体
        Subject subject = SecurityUtils.getSubject();
        //用户名和密码的token
        UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("admin", "123456");
        try {
            // 2.主体提交认证
            subject.login(usernamePasswordToken);
            System.out.println("---------->用户认证的状态:isAuthenticated=" + subject.isAuthenticated());
            // 检查是否有角色
            subject.checkRoles("admin");
            System.out.println("---------->有 admin 角色");
            // 检查是够有权限
            subject.checkPermissions("user:list");
            System.out.println("---------->有 user:list 权限");

            System.out.println("---------->用户认证的状态:isAuthenticated=" + subject.isAuthenticated());
            System.out.println("执行 logout()方法后");
            subject.logout();
            System.out.println("---------->用户认证的状态:" + subject.isAuthenticated());
        } catch (UnknownAccountException e) {
            //username wasn't in the system, show them an error message?
            System.out.println("账号不存在");
        } catch (IncorrectCredentialsException ice) {
            System.out.println("用户名密码不匹配");
            //password didn't match, try again?
        } catch (LockedAccountException lae) {
            //account for that username is locked - can't login.  Show them a message?
            System.out.println("账号已被禁用,请联系系统管理员");
        } catch (AuthenticationException ae) {
            //unexpected condition - error?
            System.out.println("用户认证失败");
        } catch (UnauthorizedException e) {
            // 关于是够有权限的 异常
            System.out.println("该用户没有权限访问");
        }
    }
}

七、自定义 Realm 进行认证授权(important)

1. 概述

虽然 jdbcRealm 已经实现了从数据库中获取用户的验证信息,但是 jdbcRealm灵活性也是稍差一些的,如果要实现自己的一些特殊应用时将不能支持,这个时候可以通过自定义realm来实现身份的认证功能。

通常自定义Realm只需要继承AuthorizingRealm 重写 doGetAuthenticationInfo(用户认证)和doGetAuthorizationInfo(用户授权) 这两个方法即可。

2. 自定义 Realm

编写类 com.feng.shiro.CustomRealmCh06。这个也就是上面案例中的数据域部分。继承AuthorizingRealm类,实现doGetAuthenticationInfo()和doGetAuthorizationInfo()方法。

里面的数据是做的模拟数据,按说应该从数据库中获取,此时这里不是重点哈。

package com.feng.shiro;

import org.apache.shiro.SecurityUtils;
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.crypto.hash.Md5Hash;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class CustomRealmCh06 extends AuthorizingRealm {

    /**
     * 模拟数据库中的用户名和密码
     */
    private Map<String, String> userMap =new HashMap<>();

    /**
     * 使用代码块初始化数据
     */
    {
        userMap.put("admin", "123456");
        userMap.put("test","123456");
    }

    /**
     * 授权
     * @param principalCollection
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("---------->开始执行 CustomRealm.doGetAuthorizationInfo 方法啦");
        // SecurityUtils.getSubject().getPrincipal()用户名: 就是SimpleAuthenticationInfo(username,password,getName()); 第一个参数

        String name= (String) SecurityUtils.getSubject().getPrincipal();
        System.out.println("---------->(String)SecurityUtils.getSubject().getPrincipal():"+name);

        String username = (String) principalCollection.getPrimaryPrincipal();
        System.out.println("---------->(String) principalCollection.getPrimaryPrincipal():"+username);
        //从数据库或者缓存中获取角色数据
        List<String> roles = getRolesByUsername(username);

        //从数据库或者缓存中获取权限数据
        List<String> permissions = getPerminssionsByUsername(username);
        //创建AuthorizationInfo,并设置角色和权限信息
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        authorizationInfo.addStringPermissions(permissions);
        authorizationInfo.addRoles(roles);
        return authorizationInfo;
    }

    /**
     * 认证
     * 逻辑:获取用户输入的用户名。密码不用获取,已经在 shiro 中(密码是 HashedCredentialsMatcher 进行 md5  加密的)
     * 根据用户名获取数据库的密码,然后进行MD5加密。
     * 然后将 用户名、加密后密码、类名称  交给shiro去认证
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        /*
        * AuthenticationToken 是 UsernamePasswordToken 顶级父类,getPrincipal() 和 getCredentials() 是其接口的两个方法,
        * 被 UsernamePasswordToken实现重新,分别是 获取用户名和密码。
        * 这里也就是获取用户输入的用户名和密码
        * */
        System.out.println("---------->开始执行 CustomRealm.doGetAuthenticationInfo 方法啦");
        //获取登录用户名
        String username = (String) authenticationToken.getPrincipal();
        System.out.println("---------->根据authenticationToken获取到的用户名:"+username);
        // 下面这行 执行报错,不能获取用户输入的密码, 上面是获取用户名,可以获取
        // String pass = (String) authenticationToken.getCredentials();
        // System.out.println("---------->根据authenticationToken获取到的密码:"+pass);
        //通过用户名到数据库获取用户信息
        String password = getPasswordByUsername(username);
        System.out.println("---------->从数据库中获取的密码:"+password);
        if (password == null) {
            return null;
        }
        // 不加密
        // 传入用户名、密码、name 。流转到下面去认证
        SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(username, password, getName());
        return authenticationInfo;
    }

    /**
     * 模拟通过数据库获取权限数据
     * @Author:     小冯
     * @UpdateUser:
     * @Version:     0.0.1
     * @param username
     * @return       java.util.List<java.lang.String>
     * @throws
     */
    private List<String> getPerminssionsByUsername(String username) {
        List<String> permissions = new ArrayList<>();
        /**
         * 只有是 admin 用户才有 新增、删除、编辑权限
         * 用户只有 遍历 权限
         */
        if(username.equals("admin")){
            permissions.add("user:delete");
            permissions.add("user:add");
            permissions.add("user:edit");
        }
        permissions.add("user:list");
        return permissions;
    }

    /**
     * 模拟通过数据库获取用户角色信息
     * @Author:     小冯
     * @UpdateUser:
     * @Version:     0.0.1
     * @param username
     * @return       java.util.List<java.lang.String>
     * @throws
     */
    private List<String> getRolesByUsername(String username) {
        List<String> roles = new ArrayList<>();
        if(username.equals("admin")){
            roles.add("admin");
        }
        roles.add("test");
        return roles;
    }
    /**
     * 通过用户名查询密码,模拟数据库查询
     * @Author:     小冯
     * @UpdateUser:
     * @Version:     0.0.1
     * @param username
     * @return       java.lang.String
     * @throws
     */
    private String getPasswordByUsername(String username) {
        return userMap.get(username);
    }

}

3. 编写 Ch06_testCustomRealm 测试类

这里的代码逻辑部分其实也是不变的。只改变了数据域的地方。

package com.feng;

import com.feng.shiro.CustomRealmCh06;
import com.feng.shiro.CustomRealmCh07;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.UnauthorizedException;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
public class Ch06_testCustomRealm {

    /**
     * 自定义 数据源 CustomRealm 。
     */
    @Test
    public void testCustomRealm() {
        // 1. 构建安全管理器环境
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        // 2. 构建数据源
        CustomRealmCh06 customRealmCh06 = new CustomRealmCh06();
        securityManager.setRealm(customRealmCh06);
        SecurityUtils.setSecurityManager(securityManager);

        // 3. 获取主体
        Subject subject = SecurityUtils.getSubject();
        UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("admin", "123456");
        System.out.println(usernamePasswordToken.getUsername());
        System.out.println(usernamePasswordToken.getPassword());
        System.out.println(usernamePasswordToken.getPrincipal());
        System.out.println(usernamePasswordToken.getCredentials());
        try {
            // 2.主体提交认证
            subject.login(usernamePasswordToken);
            System.out.println("---------->用户认证的状态:isAuthenticated=" + subject.isAuthenticated());
            // 检查是否有角色
            subject.checkRoles("admin");
            System.out.println("---------->有 admin 角色");
            // 检查是够有权限
            subject.checkPermissions("user:delete", "user:add", "user:edit", "user:list");
            System.out.println("---------->有 user:delete, user:add, user:edit 权限");

            System.out.println("执行 logout()方法后");
            subject.logout();
            System.out.println("---------->用户认证的状态:" + subject.isAuthenticated());
        } catch (UnknownAccountException e) {
            //username wasn't in the system, show them an error message?
            System.out.println("账号不存在");
        } catch (IncorrectCredentialsException ice) {
            System.out.println("用户名密码不匹配");
            //password didn't match, try again?
        } catch (LockedAccountException lae) {
            //account for that username is locked - can't login.  Show them a message?
            System.out.println("账号已被禁用,请联系系统管理员");
        } catch (AuthenticationException ae) {
            //unexpected condition - error?
            System.out.println("用户认证失败");
        } catch (UnauthorizedException ue){
            System.out.println("用户没有权限");
        }
    }
}

4. 测试

  • 当用户 admin 登录进来的时候 程序输出
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("admin", "123456");

在这里插入图片描述
在这里插入图片描述

  • 当用户 test 登录进来的时候 程序输出
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("test", "123456");

在这里插入图片描述

  • 当用户 dev 登陆进来的时候 程序输出
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("dev", "123456");

在这里插入图片描述

通过上述验证过程我们可以发现,shiro 更多的是帮助我们完成验证过程。我们需要从数据库查询当前用户的角色、权限,把这些信息告诉 shiro 框架。当我们执行用户认证的时候首先调用 doGetAuthenticationInfo 进行用户认证,当我们要校验权限的时候 就会执行
doGetAuthorizationInfo 进行授权操作。

相关文章
|
2月前
|
Java 测试技术 开发者
必学!Spring Boot 单元测试、Mock 与 TestContainer 的高效使用技巧
【10月更文挑战第18天】 在现代软件开发中,单元测试是保证代码质量的重要手段。Spring Boot提供了强大的测试支持,使得编写和运行测试变得更加简单和高效。本文将深入探讨Spring Boot的单元测试、Mock技术以及TestContainer的高效使用技巧,帮助开发者提升测试效率和代码质量。
224 2
|
6天前
|
安全 Java 测试技术
springboot之SpringBoot单元测试
本文介绍了Spring和Spring Boot项目的单元测试方法,包括使用`@RunWith(SpringJUnit4ClassRunner.class)`、`@WebAppConfiguration`等注解配置测试环境,利用`MockMvc`进行HTTP请求模拟测试,以及如何结合Spring Security进行安全相关的单元测试。Spring Boot中则推荐使用`@SpringBootTest`注解简化测试配置。
|
14天前
|
Java 测试技术 API
详解Swagger:Spring Boot中的API文档生成与测试工具
详解Swagger:Spring Boot中的API文档生成与测试工具
29 4
|
15天前
|
Java 测试技术 数据库连接
使用Spring Boot编写测试用例:实践与最佳实践
使用Spring Boot编写测试用例:实践与最佳实践
38 0
|
1月前
|
JavaScript Java 项目管理
Java毕设学习 基于SpringBoot + Vue 的医院管理系统 持续给大家寻找Java毕设学习项目(附源码)
基于SpringBoot + Vue的医院管理系统,涵盖医院、患者、挂号、药物、检查、病床、排班管理和数据分析等功能。开发工具为IDEA和HBuilder X,环境需配置jdk8、Node.js14、MySQL8。文末提供源码下载链接。
|
2月前
|
机器学习/深度学习 监控 计算机视觉
目标检测实战(八): 使用YOLOv7完成对图像的目标检测任务(从数据准备到训练测试部署的完整流程)
本文介绍了如何使用YOLOv7进行目标检测,包括环境搭建、数据集准备、模型训练、验证、测试以及常见错误的解决方法。YOLOv7以其高效性能和准确率在目标检测领域受到关注,适用于自动驾驶、安防监控等场景。文中提供了源码和论文链接,以及详细的步骤说明,适合深度学习实践者参考。
470 0
目标检测实战(八): 使用YOLOv7完成对图像的目标检测任务(从数据准备到训练测试部署的完整流程)
|
2月前
|
机器学习/深度学习 并行计算 数据可视化
目标分类笔记(二): 利用PaddleClas的框架来完成多标签分类任务(从数据准备到训练测试部署的完整流程)
这篇文章介绍了如何使用PaddleClas框架完成多标签分类任务,包括数据准备、环境搭建、模型训练、预测、评估等完整流程。
126 0
目标分类笔记(二): 利用PaddleClas的框架来完成多标签分类任务(从数据准备到训练测试部署的完整流程)
|
1月前
|
JavaScript NoSQL Java
CC-ADMIN后台简介一个基于 Spring Boot 2.1.3 、SpringBootMybatis plus、JWT、Shiro、Redis、Vue quasar 的前后端分离的后台管理系统
CC-ADMIN后台简介一个基于 Spring Boot 2.1.3 、SpringBootMybatis plus、JWT、Shiro、Redis、Vue quasar 的前后端分离的后台管理系统
38 0
|
2月前
|
监控 测试技术 数据安全/隐私保护
新产品测试流程如何?
新产品测试流程如何?【10月更文挑战第10天】
110 0
|
27天前
|
JSON Java 测试技术
SpringCloud2023实战之接口服务测试工具SpringBootTest
SpringBootTest同时集成了JUnit Jupiter、AssertJ、Hamcrest测试辅助库,使得更容易编写但愿测试代码。
55 3