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

本文涉及的产品
RDS MySQL DuckDB 分析主实例,集群系列 4核8GB
RDS MySQL DuckDB 分析主实例,基础系列 4核8GB
RDS AI 助手,专业版
简介: 这篇文章是关于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 进行授权操作。

相关文章
|
8月前
|
安全 Java 数据库
第16课:Spring Boot中集成 Shiro
第16课:Spring Boot中集成 Shiro
944 0
|
9月前
|
人工智能 Java 测试技术
Spring Boot 集成 JUnit 单元测试
本文介绍了在Spring Boot中使用JUnit 5进行单元测试的常用方法与技巧,包括添加依赖、编写测试类、使用@SpringBootTest参数、自动装配测试模块(如JSON、MVC、WebFlux、JDBC等),以及@MockBean和@SpyBean的应用。内容实用,适合Java开发者参考学习。
976 0
|
5月前
|
Java 测试技术 数据库连接
【SpringBoot(四)】还不懂文件上传?JUnit使用?本文带你了解SpringBoot的文件上传、异常处理、组件注入等知识!并且带你领悟JUnit单元测试的使用!
Spring专栏第四章,本文带你上手 SpringBoot 的文件上传、异常处理、组件注入等功能 并且为你演示Junit5的基础上手体验
1034 3
|
12月前
|
安全 Java Apache
微服务——SpringBoot使用归纳——Spring Boot中集成 Shiro——Shiro 身份和权限认证
本文介绍了 Apache Shiro 的身份认证与权限认证机制。在身份认证部分,分析了 Shiro 的认证流程,包括应用程序调用 `Subject.login(token)` 方法、SecurityManager 接管认证以及通过 Realm 进行具体的安全验证。权限认证部分阐述了权限(permission)、角色(role)和用户(user)三者的关系,其中用户可拥有多个角色,角色则对应不同的权限组合,例如普通用户仅能查看或添加信息,而管理员可执行所有操作。
580 0
|
12月前
|
安全 Java 数据安全/隐私保护
微服务——SpringBoot使用归纳——Spring Boot中集成 Shiro——Shiro 三大核心组件
本课程介绍如何在Spring Boot中集成Shiro框架,主要讲解Shiro的认证与授权功能。Shiro是一个简单易用的Java安全框架,用于认证、授权、加密和会话管理等。其核心组件包括Subject(认证主体)、SecurityManager(安全管理员)和Realm(域)。Subject负责身份认证,包含Principals(身份)和Credentials(凭证);SecurityManager是架构核心,协调内部组件运作;Realm则是连接Shiro与应用数据的桥梁,用于访问用户账户及权限信息。通过学习,您将掌握Shiro的基本原理及其在项目中的应用。
429 0
|
12月前
|
数据采集 算法 测试技术
【硬件测试】基于FPGA的1024QAM基带通信系统开发与硬件片内测试,包含信道模块,误码统计模块,可设置SNR
本文介绍了基于FPGA的1024QAM基带通信系统的硬件测试版本,包含testbench、高斯信道模块和误码率统计模块。系统新增ila在线数据采集和vio在线SNR设置模块,支持不同SNR条件下的性能测试。1024QAM调制将10比特映射到复平面上的1024个星座点之一,实现高效数据传输。硬件测试结果表明,在SNR=32dB和40dB时,系统表现出良好的性能。Verilog核心程序展示了各模块的连接与功能实现。
326 7
|
6月前
|
人工智能 边缘计算 搜索推荐
AI产品测试学习路径全解析:从业务场景到代码实践
本文深入解析AI测试的核心技能与学习路径,涵盖业务理解、模型指标计算与性能测试三大阶段,助力掌握分类、推荐系统、计算机视觉等多场景测试方法,提升AI产品质量保障能力。
|
9月前
|
人工智能 Java 测试技术
SpringBoot 测试实践:单元测试与集成测试
在 Spring Boot 测试中,@MockBean 用于创建完全模拟的 Bean,替代真实对象行为;而 @SpyBean 则用于部分模拟,保留未指定方法的真实实现。两者结合 Mockito 可灵活控制依赖行为,提升测试覆盖率。合理使用 @ContextConfiguration 和避免滥用 @SpringBootTest 可优化测试上下文加载速度,提高测试效率。
440 5
|
8月前
|
Java 测试技术 Spring
简单学Spring Boot | 博客项目的测试
本内容介绍了基于Spring Boot的博客项目测试实践,重点在于通过测试驱动开发(TDD)优化服务层代码,提升代码质量和功能可靠性。案例详细展示了如何为PostService类编写测试用例、运行测试并根据反馈优化功能代码,包括两次优化过程。通过TDD流程,确保每项功能经过严格验证,增强代码可维护性与系统稳定性。
321 0
|
9月前
|
缓存 安全 Java
Shiro简介及SpringBoot集成Shiro(狂神说视频简易版)
Shiro简介及SpringBoot集成Shiro(狂神说视频简易版)
752 7