SpringBoot基础学习之整合Shiro框架(下篇)

前言

小伙伴们,大家好,我是狂奔の蜗牛rz,当然你们可以叫我蜗牛君,我是一个学习Java半年多时间的小菜鸟,同时还有一个伟大的梦想,那就是有朝一日,成为一个优秀的Java架构师。


这个SpringBoot基础学习系列用来记录我学习SpringBoot框架基础知识的全过程 (这个系列是参照B站狂神的SpringBoot最新教程来写的,由于是之前整理的,但当时没有发布出来,所以有些地方可能有错误,希望大家能够及时指正!)

之后我将会尽量以一天一更的速度更新这个系列,还没有学习SpringBoot的小伙伴可以参照我的博客学习一下;当然学习过的小伙伴,也可以顺便跟我一起复习一下基础。最后,希望能够和大家一同进步吧!加油吧!少年们!


由于篇幅较长,所以这里我将其分为了上下两篇博客上篇主要了解Shiro的功能,以及基本环境的搭建下篇主要学习Shiro整合Mybatis和Thymeleaf框架

特别提醒上篇博客链接SpringBoot基础学习之整合Shiro框架(上篇)

今天我们来到了SpringBoot基础学习的第九站:整合Shiro框架(下篇)。废话不多说,让我们开始今天的学习内容吧!

8.4 Shiro整合Mybatis

8.4.1 导入模块相关资源依赖

1.导入MySQL数据库驱动资源依赖
<!-- MySQL数据库驱动资源依赖 -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
    <version>5.1.46</version>
</dependency>
2.导入mybatis整合springboot资源依赖
<!-- Mybatis-SpringBoot的启动器 -->
<!-- https://mvnrepository.com/artifact/org.mybatis.spring.boot/mybatis-spring-boot-starter -->
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.1.3</version>
</dependency>
3.导入Druid数据源资源依赖
<!-- Druid数据源资源依赖 -->
<!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.12</version>
</dependency>
4.导入lombok的资源依赖
<!-- lombok资源依赖 -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.16.10</version>
</dependency>
5.所有的资源依赖
<dependencies>
    <!-- Shiro的三大核心:
         Subject: 用户 SecurityManager: 管理所有用户 Realm: 连接数据-->
    <!-- springboot整合shiro的资源依赖 -->
    <!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-spring -->
    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-spring</artifactId>
        <version>1.4.1</version>
    </dependency>
    <!-- thymeleaf的资源依赖 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
    <!-- spring-boot-web资源依赖 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- spring-boot-test资源依赖 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    <!-- log4j资源依赖 -->
    <!-- https://mvnrepository.com/artifact/log4j/log4j -->
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.17</version>
    </dependency>
    <!-- MySQL数据库驱动资源依赖 -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
        <version>5.1.46</version>
    </dependency>
    
    <!-- 第三方提供的启动器 -->
    <!-- Mybatis-SpringBoot的启动器 -->
    <!-- https://mvnrepository.com/artifact/org.mybatis.spring.boot/mybatis-spring-boot-starter -->
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>2.1.3</version>
    </dependency>
    <!-- Druid数据源资源依赖 -->
    <!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.1.12</version>
    </dependency>
    <!-- lombok资源依赖 -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.16.10</version>
    </dependency>
    
</dependencies>

8.4.2 搭建项目基本环境

1.项目模块基本结构和编写配置文件
1-1 项目模块基本结构

在这里插入图片描述

1-2 编写application.yml配置文件
# 设置服务器端口号
server:
  port: 8888

# 设置数据库驱动
spring:
  datasource:
    username: root
    password: 123456
    # 8.0以下版本的url连接格式
    url: jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf-8&useSSL=true
    # 8.0以下版本的数据库驱动
    driver-class-name: com.mysql.jdbc.Driver

    # 使用Druid的数据源
    type: com.alibaba.druid.pool.DruidDataSource
    # Spring Boot 默认是不注入这些属性值的,因此需要自己来配置
    # 设置Druid数据源的基本配置
    # 初始化时的物理连接个数:初始化发生在显式调用init方法或者第一次getConnection时
    initialSize: 5
    # 最小连接池数量
    minIdle: 5
    # 最大连接池数量
    maxActive: 20
    # 获取连接的最大等待时间,时间单位是毫秒
    maxWait: 60000
    # 运行和回收的间隔时间:即销毁线程检测连接的间隔时间
    timeBetweenEvictionRunsMillis: 300000
    # 最小回收时间:如果检测到当前连接的最后活跃时间和当前时间大于最小回收时间,则关闭当前连接
    minEvictableIdleTimeMillis: 300000
    # 判断连接到的查询语句是否有效
    validationQuery: Select 1 from dual
    # 检测空闲连接时间:建议设置为true,申请连接检测,如果空闲时间大于回收运行间隔时间,执行检测连接到的查询语句是否生效
    testWhiteIdle: true
    # 检测引入的查询语言的连接是否生效:此配置会降低性能,默认值为false
    testOnBorrow: false
    # 检测归还连接时执行生效查询语句是否有效:此配置会降低性能
    testOnReturn: false
    # 是否缓存预编译语句池:即使用PSCache,默认值为false,MySQL5.5以上版本有PSCache,建议开启
    poolPreparedStatements: true

    # 配置监控拦截的filters:监控统计-stat;日志记录-log4j;防御SQL注入:wall
    # 如果运行时报错-java.lang.ClassNotFoundException:org.apache,log4j.Priority
    # 则导入log4j依赖即可,Maven仓库地址:https://mvnrepository.com/artifact/log4j/log4j
    filters: stat,log4j,wall
    # 设置每个连接的最大池预编译语句大小
    maxPoolPreparedStatementPerConnectionSize: 20
    # 设置使用全局的数据源的监控统计拦截器stat
    useGlobalDataSourceStat: true
    # 设置连接属性:监控统计拦截器stat的mergeSql(合并SQL)属性为true,即此配置生效;慢SQL时间为0.5秒
    connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500

# 配置mybatis相关属性
mybatis:
  # 通过包设置别名
  type-aliases-package: com.kuang.pojo
  # 设置mapper映射文件的位置
  mapper-locations: classpath:mapper/*.xml
2.编写User实体类和UserMapper接口及映射文件
2-1 编写User实体类
package com.kuang.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;

@Data // 导入无参构造, set和get方法, 以及toString方法
@AllArgsConstructor // 导入有参构造
@NoArgsConstructor // 再次无参构造,防止无参构造被覆盖掉
// 实现Serializable接口,序列化User对象
public class User implements Serializable {
    
    private Integer id; // 编号
    private String name; // 用户名
    private String pwd; // 密码
    
}
2-2 编写UserMapper接口
package com.kuang.mapper;

import com.kuang.pojo.User;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;

@Repository // 将Dao层交由Spring的IOC容器统一管理
@Mapper // 注册为Mapper接口
public interface UserMapper {
    
    // 通过用户名获取指定用户信息
    public User getUserByName(String name);
    
}
2-3 编写UserMapper.xml映射文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.kuang.mapper.UserMapper">

    <!-- 通过用户名获取指定用户信息 -->
    <select id="getUserByName" resultType="com.kuang.pojo.User"  parameterType="string">
        Select id, name, pwd from mybatis.user where name = #{name}
    </select>

</mapper>
3.编写UserService接口和UserServiceImpl实现类
3-1 编写UserService接口
package com.kuang.service;

import com.kuang.pojo.User;

public interface UserService {
    
    // 通过用户名获取指定用户信息
    public User getUserByName(String name);
    
}
3-2 编写UserServiceImpl实现类
package com.kuang.service.impl;

import com.kuang.mapper.UserMapper;
import com.kuang.pojo.User;
import com.kuang.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

// 将Service层交由Spring的IOC容器统一管理
@Service
public class UserServiceImpl implements UserService {
    
    // 自动注入UserMapper接口
    @Autowired
    private UserMapper userMapper;

    // 根据用户名获取指定用户信息
    @Override
    public User getUserByName(String name) {
        return this.userMapper.getUserByName(name);
    }
    
}
4.修改UserRealm配置类和编写UserController控制类
4-1 修改UserRealm配置类
package com.kuang.config;

import com.kuang.pojo.User;
import com.kuang.service.UserService;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;
import sun.security.provider.MD5;

// 自定义UserRealm,继承AuthorizingRealm类
public class UserRealm extends AuthorizingRealm {

    @Autowired
    private UserService userService;

    /**
     * 1.授权
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("执行了=>授权doGetAuthorizationInfo");
        // 1.1 获取简单授权信息(SimpleAuthorizationInfo)对象
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        /**
         * 把之前的添加用户请求注释掉
         */
        // 1.2 在授权认证信息(authorizationInfo)中设置权限请求的值
//        authorizationInfo.addStringPermission("user:addUser");
        // 1.3 获取当前登录的这个用户(即subject对象)
        Subject subject = SecurityUtils.getSubject();
        // 1.4 获取当前用户的权限
        User currentUser = (User) subject.getPrincipal();
        // 1.5 设置当前用户的权限,存入到授权认证信息(authorizationInfo)中去
        authorizationInfo.addStringPermission(currentUser.getPerms());
        // 1.6 获取当前用户的权限
        String perms = currentUser.getPerms();
        // 1.7 判断当前用户的权限是否为空
        if(perms!=null) {
            // 1.7.1 设置当前用户的权限,存入到授权认证信息(authorizationInfo)中去
            authorizationInfo.addStringPermission(currentUser.getPerms());
            // 1.7.2 将授权后的认证信息进行返回
            return authorizationInfo;
        } else {
            // 1.7.3 如果用户的权限为空,返回null值,抛出异常
            return null;
       }
    }

    /**
     * 2.认证
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        
        System.out.println("执行了=>授权doGetAuthenticationInfo");
        
        /**
         * 2.2 连接数据库
         */
        // 2.2.1 获取用户认证信息:可以打断点做测试
        UsernamePasswordToken userToken = (UsernamePasswordToken) authenticationToken;
        // 2.2.2 通过用户名获取来获取用户信息
        User user = userService.getUserByName(userToken.getUsername());
        // 2.2.3 判断用户信息是否为空
        if(user==null) {
          // 如果用户名不存在,就抛出异常UnknownAccountException
          return null;
        }
        
        /** 
         * 2.3 密码认证
         * 让Shiro来做,可以进行密码加密(这里为了省事,就不做加密了)
         */
       return new SimpleAuthenticationInfo("",user.getPwd(),"");
    }
    
}
4-2 编写UserController接口
package com.kuang.controller;

import com.kuang.pojo.User;
import com.kuang.service.UserService;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;

@Controller
public class UserController {
    
    /** 
     * 跳转到首页
     * 使用@RequestMapping注解, 设置请求映射路径, 请求方式为get方式
     * 多个请求使用{}包围, 使用","进行分隔 
     */
    @RequestMapping({"/","/index"})
    public String toIndex(Model model) {
        // 设置视图模型信息
        model.addAttribute("msg", "Hello,Shiro!");
        // 返回视图逻辑名
        return "index";
    }

    /** 
     * 跳转到添加页面
     * 使用@RequestMapping注解, 设置请求映射路径, 请求方式为get方式
     */
    @RequestMapping("/user/addUser")
    public String toAddPage() {
        // 返回视图逻辑名
        return "user/addUser";
    }

    /** 
     * 跳转到修改页面
     * 使用@RequestMapping注解, 设置请求映射路径, 请求方式为get方式
     */
    @RequestMapping("/user/updateUser")
    public String toUpdatePage() {
        // 返回视图逻辑名
        return "user/updateUser";
    }

    
    /** 
     * 跳转到登录页面
     * 使用@RequestMapping注解, 设置请求映射路径, 请求方式为get方式
     */
    @RequestMapping("/toLogin")
    public String toLogin() {
        // 返回视图逻辑名
        return "login";
    }
    
    /**
     * 登录验证
     * 使用@PostMapping注解, 设置请求映射路径, 请求方式为post方式
     */
    @PostMapping("/login")
    public String login(String username, String password,Model model) {
        // 获取当前用户
        Subject subject = SecurityUtils.getSubject();
        // 封装用户的登录数据
        UsernamePasswordToken token = new UsernamePasswordToken(username,MD5Util.MD5EncodeUtf8(password));
        try{
            // 执行登录方法,如果没有异常就说明OK了
            subject.login(token);
            // 设置视图逻辑名为index主页
            return "index";
        // 捕获未知用户异常
        } catch(UnknownAccountException uae) {
            // 设置视图模型信息
            model.addAttribute("msg","用户名错误");
            // 设置视图逻辑名为login登录页
            return "login";
        // 捕获错误认证异常
        } catch (IncorrectCredentialsException ica) {
            // 设置视图模型信息
            model.addAttribute("msg","密码错误");
            // 设置视图逻辑名为login登录页
            return "login";
        }
    }

}
5.编写测试类和测试结果
5-1 编写ShiroSpringbootApplicationTests测试类
package com.kuang;

import com.kuang.service.impl.UserServiceImpl;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class ShiroSpringbootApplicationTests {

    @Autowired
    private UserServiceImpl userService;

    @Test
    void contextLoads() {
        // 打印通过用户名获取的用户信息
        System.out.println(userService.getUserByName("root"));
    }

}
5-2 测试结果

在这里插入图片描述

结果控制台中成功输出6号用户的账号密码信息!

8.4.3 完善项目环境

1.修改UserRealm和ShiroConfig配置类
1-1 修改UserRealm配置类
package com.kuang.config;

import com.kuang.pojo.User;
import com.kuang.service.UserService;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;
import sun.security.provider.MD5;

// 自定义UserRealm,继承AuthorizingRealm类
public class UserRealm extends AuthorizingRealm {

    // 使用@Autowired注解, 通过类型自动装配UserService接口
    @Autowired
    private UserService userService;

    /**
     * 1.授权
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        
        System.out.println("执行了=>授权doGetAuthorizationInfo");
        // 1.1 获取简单授权信息(SimpleAuthorizationInfo)对象
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        // 1.2 在授权认证信息(authorizationInfo)中设置权限请 求的值
//        authorizationInfo.addStringPermission("user:addUser");
        // 1.3 获取当前登录的这个用户(即subject对象)
        Subject subject = SecurityUtils.getSubject();
        // 1.4 获取当前用户的权限
        User currentUser = (User) subject.getPrincipal();
        // 1.5 设置当前用户的权限,存入到授权认证信息(authorizationInfo)中去
        authorizationInfo.addStringPermission(currentUser.getPerms());
        // 1.6 获取当前用户的权限
        String perms = currentUser.getPerms();
        // 1.7 判断当前用户的权限是否为空
        if(perms!=null) {
            // 1.7.1 设置当前用户的权限,存入到授权认证信息(authorizationInfo)中去
            authorizationInfo.addStringPermission(currentUser.getPerms());
            // 1.7.2 将授权后的认证信息进行返回
            return authorizationInfo;
        } else {
            // 1.7.3 如果用户的权限为空,返回null值,抛出异常
            return null;
       }
        
    }

    /**
     * 2.认证
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        
        System.out.println("执行了=>授权doGetAuthenticationInfo");
        /** 
         * 2.2 连接数据库
         */
        // 2.2.1 获取用户认证信息:可以打断点做测试
        UsernamePasswordToken userToken = (UsernamePasswordToken) authenticationToken;
        // 2.2.2 通过用户名获取来获取用户信息
        User user = userService.getUserByName(userToken.getUsername());
        // 2.2.3 判断用户信息是否为空
        if(user==null) {
          // 如果用户名不存在,就抛出异常UnknownAccountException
          return null;
        }

        /** 
         * 2.3 密码加密和认证
         */
        /**
         * 2.3.1 可以给密码做加密:
         * 使用MD5加密:e10adc3949ba59abbe56e057f20f883e
         * 使用MD5盐值加密:e10adc3949ba59abbe56e057f20f883e+username
         */
        // 2.3.2 在principal参数中,把user传进来;密码加密使用盐值加密
        return new SimpleAuthenticationInfo(user, MD5Util.MD5EncodeUtf8(user.getPwd()),"");
    }
    
}
1-2 修改ShiroConfig配置类
package com.kuang.config;

import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.LinkedHashMap;
import java.util.Map;

// 使用@Configuration注解, 让ShiroConfig成为配置类
@Configuration
public class ShiroConfig {
    
    /** 
     * 1.创建realm对象,需要自定义类
     */
    // 1.1 将userRealm作为组件, 注册到Spring的IOC容器中去
    @Bean
    public UserRealm userRealm() {
        // 1.2 返回值为创建一个UserRealm对象
        return new UserRealm();
    }

    /** 
     * 2.设置DefaultWebSecurityManager(默认Web安全管理器)
     */
    // 2.1 使用@Bean注解,将getDefaultWebSecurityManager方法作为作为组件, 注册到Spring的IOC容器中去
    @Bean(name="securityManager")
    // 2.2 使用@Qualifier注解,通过名字userRealm获取SpringdeIOC容器中的UserRealm对象
    public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm) {
        // 2.3 获取DefaultWebSecurityManager对象
        DefaultWebSecurityManager securityManager =  new DefaultWebSecurityManager();
        // 2.4 关联userRealm对象
        securityManager.setRealm(userRealm);
        // 2.5 返回securityManager对象
        return securityManager;
    }
    
    /** 
     * 3.设置ShiroFilterFactoryBean(Shiro过滤器工厂Bean)
     */
    // 使用@Bean注解,将getShiroFilterFactoryBean方法注册到Spring容器中去
    @Bean
    // 使用@Qualifier注解,通过名字securityManage获取Spring容器中的DefaultWebSecurityManager对象
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager securityManager) {
        // 3.1 获取Shiro的过滤工厂, 设置安全管理器
        // 3.1.1 获取ShiroFilterFactoryBean对象
        ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
        // 3.1.2 设置securityManager(安全管理器)对象
        factoryBean.setSecurityManager(securityManager);

        /** 
         * 3.2 添加shiro的内置过滤器
         * anon:无需认证就可以访问
         * authc:必须认证了才能访问
         * user:必须拥有记住我功能才能使用
         * perms:拥有对某个资源的权限才能访问
         * role:拥有某个角色权限才能访问
         */
        /** 
         * 3.2.1 拦截
         */
        // 获取LinkedHashMap对象
        Map<String, String> filterMap = new LinkedHashMap<>();

         /**
          * 3.2.2 授权
          * 使用Map集合设置权限对应的key-value值:key是相应的请求,value是权限值
          */
       // #1 "/user/addUser"是添加用户的请求,"perms[user:addUser]"表示只有拥有添加用户权限才能访问
         filterMap.put("/user/addUser", "perms[user:addUser]");
        // #2 "/user/updateUser"是修改用户的请求,"perms[user:updateUser]"表示只有拥有修改用户权限才能访问
        filterMap.put("/user/updateUser", "perms[user:updateUser]");
        // #3 "/user/*"所有的user下的请求, "authc"表示通过认证后才能访问
        filterMap.put("/user/*", "authc");

        // 3.2.3 设置FilterChainDefinitionMap(过滤链式定义Map)
        factoryBean.setFilterChainDefinitionMap(filterMap);

        /**
         * 3.3 设置登录和未授权的相关请求
         */
        // 3.3.1 设置登录的请求
        factoryBean.setLoginUrl("/toLogin");
        // 3.3.2 未授权页面的请求
        factoryBean.setUnauthorizedUrl("/unAuthorized");

        // 3.4 返回factoryBean对象
        return factoryBean;
    }
    
}
2.修改UserController控制类
package com.kuang.controller;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class UserController {

    /** 
     * 跳转到首页
     * 使用@RequestMapping注解, 设置请求映射路径, 请求方式为get方式
     * 多个请求使用{}包围, 使用","进行分隔 
     */
    @RequestMapping({"/","/index"})
    public String toIndex(Model model) {
        // 设置视图模型信息
        model.addAttribute("msg", "Hello,Shiro!");
        // 返回视图逻辑名
        return "index";
    }

    /** 
     * 跳转到添加页面
     * 使用@RequestMapping注解, 设置请求映射路径, 请求方式为get方式
     */
    @RequestMapping("/user/addUser")
    public String toAddPage() {
        // 返回视图逻辑名
        return "user/addUser";
    }

    /** 
     * 跳转到修改页面
     * 使用@RequestMapping注解, 设置请求映射路径, 请求方式为get方式
     */
    @RequestMapping("/user/updateUser")
    public String toUpdatePage() {
        // 返回视图逻辑名
        return "user/updateUser";
    }

    
    /** 
     * 跳转到登录页面
     * 使用@RequestMapping注解, 设置请求映射路径, 请求方式为get方式
     */
    @RequestMapping("/toLogin")
    public String toLogin() {
        // 返回视图逻辑名
        return "login";
    }
    
    /**
     * 登录验证
     * 使用@PostMapping注解, 设置请求映射路径, 请求方式为post方式
     */
    @PostMapping("/login")
    public String login(String username, String password,Model model) {
        // 获取当前用户
        Subject subject = SecurityUtils.getSubject();
        // 封装用户的登录数据
        UsernamePasswordToken token = new UsernamePasswordToken(username,MD5Util.MD5EncodeUtf8(password));
        try{
            // 执行登录方法,如果没有异常就说明OK了
            subject.login(token);
            // 设置视图逻辑名为index主页
            return "index";
        // 捕获未知用户异常
        } catch(UnknownAccountException uae) {
            // 设置视图模型信息
            model.addAttribute("msg","用户名错误");
            // 设置视图逻辑名为login登录页
            return "login";
        // 捕获错误认证异常
        } catch (IncorrectCredentialsException ica) {
            // 设置视图模型信息
            model.addAttribute("msg","密码错误");
            // 设置视图逻辑名为login登录页
            return "login";
        }
    }

    /** 
     * 未授权
     * 使用@RequestMapping注解, 设置请求映射路径, 请求方式为get方式
     */
    @RequestMapping("/unAuthorized")
    // 使用@ResponseBody注解, 将方法返回的字符串转化为JSON串
    @ResponseBody 
    public String unAuthorized() {
        return "未授权,无法访问此页面!";
    }

}
3. 修改User实体类和编写MD5工具类
3-1 修改User实体类
package com.kuang.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

@Data // 导入无参构造, set和get方法, 以及toString方法
@AllArgsConstructor // 导入有参构造
@NoArgsConstructor // 再次无参构造,防止无参构造被覆盖掉
// 实现Serializable接口,序列化User对象
public class User implements Serializable {
    
    private Integer id; //编号
    private String name; //用户名
    private String pwd; //密码
    private String perms; //权限
    
}

3-2 编写MD5Util工具类
package com.kuang.utils;

import java.security.MessageDigest;

public class MD5Util {

    private static String byteArrayToHexString(byte b[]) {
        StringBuffer resultSb = new StringBuffer();
        for (int i = 0; i < b.length; i++)
            resultSb.append(byteToHexString(b[i]));

        return resultSb.toString();
    }

    private static String byteToHexString(byte b) {
        int n = b;
        if (n < 0)
            n += 256;
        int d1 = n / 16;
        int d2 = n % 16;
        return hexDigits[d1] + hexDigits[d2];
    }

    /**
     * 返回大写MD5
     *
     * @param origin 初始值
     * @param charsetname 字符集名
     * @return String 盐值加密后的字符串
     */
    private static String MD5Encode(String origin, String charsetname) {
        String resultString = null;
        try {
            resultString = new String(origin);
            MessageDigest md = MessageDigest.getInstance("MD5");
            if (charsetname == null || "".equals(charsetname))
                resultString = byteArrayToHexString(md.digest(resultString.getBytes()));
            else
                resultString = byteArrayToHexString(md.digest(resultString.getBytes(charsetname)));
        } catch (Exception exception) {
        }
        return resultString.toUpperCase();
    }

    public static String MD5EncodeUtf8(String origin) {

        //盐值Salt加密
        //origin = origin + PropertiesUtil.getProperty("password.salt", "");
        return MD5Encode(origin, "utf-8");
    }


    private static final String hexDigits[] = {"0", "1", "2", "3", "4", "5",
            "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"};

}
4.修改数据库信息
4-1 修改User表
  • 添加varchar类型perms字段,用来设置用户的权限
4-2 给用户添加权限

在这里插入图片描述

4.权限验证测试
4-1 使用root账户登录
  • 登录root用户进行测试

在这里插入图片描述

  • 访问添加用户页面

在这里插入图片描述

  • 访问修改用户页面

在这里插入图片描述

4-2 使用admin账户登录
  • 登录admin用户进行测试

在这里插入图片描述

  • 访问新增用户页面

在这里插入图片描述

  • 访问修改用户页面

在这里插入图片描述

8.5 Shiro整合thymeleaf

8.5.1 导入资源依赖和修改部分代码

1.导入shiro整合thymeleaf资源依赖
<!-- shiro-thymeleaf整合包 -->
<!-- https://mvnrepository.com/artifact/com.github.theborakompanioni/thymeleaf-extras-shiro -->
<dependency>
    <groupId>com.github.theborakompanioni</groupId>
    <artifactId>thymeleaf-extras-shiro</artifactId>
    <version>2.0.0</version>
</dependency>
2.修改ShiroConfig配置类
package com.kuang.config;

import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.LinkedHashMap;
import java.util.Map;

// 使用@Configuration注解, 让ShiroConfig成为配置类
@Configuration
public class ShiroConfig {

    /** 
     * 1.创建realm对象,需要自定义类
     */
    // 1.1 将userRealm作为组件, 注册到Spring的IOC容器中去
    @Bean
    public UserRealm userRealm() {
        // 1.2 返回值为创建一个UserRealm对象
        return new UserRealm();
    }

    /** 
     * 2.设置DefaultWebSecurityManager(默认Web安全管理器)
     */
    // 2.1 使用@Bean注解,将getDefaultWebSecurityManager方法作为作为组件, 注册到Spring的IOC容器中去
    @Bean(name="securityManager")
    // 2.2 使用@Qualifier注解,通过名字userRealm获取SpringdeIOC容器中的UserRealm对象
    public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm) {
        // 2.3 获取DefaultWebSecurityManager对象
        DefaultWebSecurityManager securityManager =  new DefaultWebSecurityManager();
        // 2.4 关联userRealm对象
        securityManager.setRealm(userRealm);
        // 2.5 返回securityManager对象
        return securityManager;
    }
    
    /** 
     * 3.设置ShiroFilterFactoryBean(Shiro过滤器工厂Bean)
     */
    // 使用@Bean注解,将getShiroFilterFactoryBean方法注册到Spring容器中去
    @Bean
    // 使用@Qualifier注解,通过名字securityManage获取Spring容器中的DefaultWebSecurityManager对象
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager securityManager) {
        // 3.1 获取Shiro的过滤工厂, 设置安全管理器
        // 3.1.1 获取ShiroFilterFactoryBean对象
        ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
        // 3.1.2 设置securityManager(安全管理器)对象
        factoryBean.setSecurityManager(securityManager);

        /** 
         * 3.2 添加shiro的内置过滤器
         * anon:无需认证就可以访问
         * authc:必须认证了才能访问
         * user:必须拥有记住我功能才能使用
         * perms:拥有对某个资源的权限才能访问
         * role:拥有某个角色权限才能访问
         */
        /** 
         * 3.2.1 拦截
         */
        // 获取LinkedHashMap对象
        Map<String, String> filterMap = new LinkedHashMap<>();

         /**
          * 3.2.2 授权
          * 使用Map集合设置权限对应的key-value值:key是相应的请求,value是权限值
          */
       // #1 "/user/addUser"是添加用户的请求,"perms[user:addUser]"表示只有拥有添加用户权限才能访问
         filterMap.put("/user/addUser", "perms[user:addUser]");
        // #2 "/user/updateUser"是修改用户的请求,"perms[user:updateUser]"表示只有拥有修改用户权限才能访问
        filterMap.put("/user/updateUser", "perms[user:updateUser]");
        // #3 "/user/*"所有的user下的请求, "authc"表示通过认证后才能访问
        filterMap.put("/user/*", "authc");

        // 3.2.3 设置FilterChainDefinitionMap(过滤链式定义Map)
        factoryBean.setFilterChainDefinitionMap(filterMap);

        /**
         * 3.3 设置登录和未授权的相关请求
         */
        // 3.3.1 设置登录的请求
        factoryBean.setLoginUrl("/toLogin");
        // 3.3.2 未授权页面的请求
        factoryBean.setUnauthorizedUrl("/unAuthorized");

        // 3.4 返回factoryBean对象
        return factoryBean;
    }

    /**
     * 4.整合ShiroDialect(Shiro的方言): 用来整合shiro thymeleaf
     */
    // 4.1 将ShiroDialect注册到Spring容器中去
    @Bean
    public ShiroDialect getShiroDialect() {
        // 4.2 将ShiroDialect对象进行返回
        return new ShiroDialect();
    }

}
3.修改UserRealm配置类
package com.kuang.config;

import com.kuang.pojo.User;
import com.kuang.service.UserService;
import com.kuang.utils.MD5Util;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;

// 自定义UserRealm, 继承AuthorizingRealm类
public class UserRealm extends AuthorizingRealm {

    @Autowired
    private UserService userService;

    /**
     * 1.授权
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        
        System.out.println("执行了=>授权doGetAuthorizationInfo");
        
        // 1.1 获取简单授权信息(SimpleAuthorizationInfo)对象
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        // 1.3 获取当前登录的这个用户(即subject对象)
        Subject subject = SecurityUtils.getSubject();
        // 1.4 获取当前用户的权限
        User currentUser = (User) subject.getPrincipal();
        // 1.5 设置当前用户的权限,存入到授权认证信息(authorizationInfo)中去
        authorizationInfo.addStringPermission(currentUser.getPerms());
        // 1.6 获取当前用户的权限
        String perms = currentUser.getPerms();
        // 1.7 判断当前用户的权限是否为空
        if(perms!=null) {
            // 1.7.1 设置当前用户的权限,存入到授权认证信息(authorizationInfo)中去
            authorizationInfo.addStringPermission(currentUser.getPerms());
            // 1.7.2 将授权后的认证信息进行返回
            return authorizationInfo;
        } else {
            // 1.7.3 如果用户的权限为空,返回null值,抛出异常
            return null;
       }
    }

    /**
     * 2.认证
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        
        System.out.println("执行了=>授权doGetAuthenticationInfo");
        
        /** 
         * 2.2 连接数据库
         */
        // 2.2.1 获取用户认证信息:可以打断点做测试
        UsernamePasswordToken userToken = (UsernamePasswordToken) authenticationToken;
        // 2.2.2 通过用户名获取来获取用户信息
        User user = userService.getUserByName(userToken.getUsername());
        // 2.2.3 判断用户信息是否为空
        if(user==null) {
          // 如果用户名不存在,就抛出异常UnknownAccountException
          return null;
        }
        
        /**
         * 3.获取用户session信息
         */
        // 3.1 获取当前用户
        Subject currentUser = SecurityUtils.getSubject();
        // 3.2 获取当前用户的session信息
        Session userSession = currentUser.getSession();
        // 3.3 设置当前用户的session信息,将用户名封装到loginUser中去
        userSession.setAttribute("loginUser",user.getName());

        /**
         * 2.4 密码加密和认证
         */
        /**
         * 2.4.1 可以给密码做加密:
         * 使用MD5加密:e10adc3949ba59abbe56e057f20f883e
         * 使用MD5盐值加密:e10adc3949ba59abbe56e057f20f883e+username
         */
        // 2.4.2 在principal参数中,把user传进来;密码加密使用盐值加密
        return new SimpleAuthenticationInfo(user, user.getPwd(),"");
    }
    
}
4.修改UserController控制类
package com.kuang.controller;

import com.kuang.utils.MD5Util;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class UserController {

    /** 
     * 跳转到首页
     * 使用@RequestMapping注解, 设置请求映射路径, 请求方式为get方式
     * 多个请求使用{}包围, 使用","进行分隔 
     */
    @RequestMapping({"/","/index"})
    public String toIndex(Model model) {
        // 设置视图模型信息
        model.addAttribute("msg", "Hello,Shiro!");
        // 返回视图逻辑名
        return "index";
    }

    /** 
     * 跳转到添加页面
     * 使用@RequestMapping注解, 设置请求映射路径, 请求方式为get方式
     */
    @RequestMapping("/user/addUser")
    public String toAddPage() {
        // 返回视图逻辑名
        return "user/addUser";
    }

    /** 
     * 跳转到修改页面
     * 使用@RequestMapping注解, 设置请求映射路径, 请求方式为get方式
     */
    @RequestMapping("/user/updateUser")
    public String toUpdatePage() {
        // 返回视图逻辑名
        return "user/updateUser";
    }

    
    /** 
     * 跳转到登录页面
     * 使用@RequestMapping注解, 设置请求映射路径, 请求方式为get方式
     */
    @RequestMapping("/toLogin")
    public String toLogin() {
        // 返回视图逻辑名
        return "login";
    }
    
    /**
     * 登录验证
     * 使用@PostMapping注解, 设置请求映射路径, 请求方式为post方式
     */
    @PostMapping("/login")
    public String login(String username, String password,Model model) {
        // 获取当前用户
        Subject subject = SecurityUtils.getSubject();
        // 封装用户的登录数据
        UsernamePasswordToken token = new UsernamePasswordToken(username,MD5Util.MD5EncodeUtf8(password));
        try{
            // 执行登录方法,如果没有异常就说明OK了
            subject.login(token);
            // 设置视图逻辑名为index主页
            return "index";
        // 捕获未知用户异常
        } catch(UnknownAccountException uae) {
            // 设置视图模型信息
            model.addAttribute("msg","用户名错误");
            // 设置视图逻辑名为login登录页
            return "login";
        // 捕获错误认证异常
        } catch (IncorrectCredentialsException ica) {
            // 设置视图模型信息
            model.addAttribute("msg","密码错误");
            // 设置视图逻辑名为login登录页
            return "login";
        }
    }

    /** 
     * 未授权
     * 使用@RequestMapping注解, 设置请求映射路径, 请求方式为get方式
     */
    @RequestMapping("/unAuthorized")
    // 使用@ResponseBody注解, 将方法返回的字符串转化为JSON串
    @ResponseBody 
    public String unAuthorized() {
        return "未授权,无法访问此页面!";
    }

    /** 
     * 退出登录
     * 使用@RequestMapping注解, 设置请求映射路径, 请求方式为get方式
     */
    @RequestMapping("/logout")
    public String logout(){
        // 获取当前用户信息
        Subject currentUser = SecurityUtils.getSubject();
        // 注销当前登录用户
        currentUser.logout();
        // 返回到index主页
        return "index";
    }
    
}

8.5.2 修改页面和登录验证测试

1.修改login.html登录页
<!DOCTYPE html>
<!-- 引入thymeleaf的命名空间 -->
<html lang="en" xmlns:th=http://www.thymeleaf.org>
<head>
    <meta charset="UTF-8">
    <title>登录</title>
</head>
<body>
<h1>登录</h1>
<hr/>
<p th:text="${msg}" style="color: red"></p>
<!-- 登录验证表单 -->
<form th:action="@{/login}" method="post">
    <p>
        用户名:<input type="text" name="username"/>
    </p>
    <p>
        密码:<input type="password" name="password"/>
    </p>
    <p>
        <input type="checkbox"/>记住我
    </p>
    <input type="submit" value="登录" />
</form>

</body>
</html>
2.修改index.html主页
<!DOCTYPE html>
<!-- 引入thymeleaf和thymeleaf整合shiro的命名空间 -->
<html lang="en" xmlns:th=http://www.thymeleaf.org
      xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
<head>
    <meta charset="UTF-8">
    <title>首页</title>
</head>
<body>
<h1>首页</h1>
    
<!-- 1.登录账户- ->
<!-- 1.1 从session中判断值 -->
<!-- <div th:if="${session.loginUser==null}">
    <a th:href="@{/toLogin}">登录</a>
</div> -->

<!-- 1.2 判断是否被认证 -->
<!--
<div shiro:notAuthenticated="">
    <a th:href="@{/toLogin}">登录</a>
</div>-->

<!-- 1.3 判断是否为游客 -->
<!-- <div shiro:guest="true">
    <a th:href="@{/toLogin}">登录</a>
</div> -->

<!-- 1.4 判断session是否为空:使用三元运算符 -->
<div>
    <!--若当前用户为空,则显示登录;若不为空,则显示用户的session信息-->
    <a th:href="@{/toLogin}" th:text="${session.loginUser==null?'登录':session.loginUser}"></a>
</div>

<!-- 2.注销账户 -->
<!-- 2.1 判断用户是否不为空 -->
<div th:if="${session.loginUser!=null}">
    <!-- 2.2 若不为空,则允许其注销 -->
    <a th:href="@{/logout}">注销</a>
</div>

<!-- 使用th:text来显示信息 -->
<p th:text="${msg}"></p>

<hr/>

<!-- 3.设置添加用户和修改用户的入口 -->
<!-- 3.1 拥有"user:addUser"权限才能看到 -->
<div shiro:haspermission="'user:addUser'">
    <a th:href="@{/user/addUser}">添加用户</a>
</div>
<!-- 3.2 拥有"user:updateUser"权限才能看到 -->
<div shiro:haspermission="'user:updateUser'">
    <a th:href="@{/user/updateUser}">修改用户</a>
</div>

</body>
</html>
3.登录验证测试
3-1 使用root账户登录
  • 未登录

在这里插入图片描述

  • 登录root账户

在这里插入图片描述

  • 访问首页成功

在这里插入图片描述

  • 访问修改用户页面

在这里插入图片描述

  • 退出登录

在这里插入图片描述

3-2 使用admin账户登录
  • 登录root账户

在这里插入图片描述

  • 访问首页成功

在这里插入图片描述

  • 访问新增用户页面

在这里插入图片描述


好了,今天的有关 SpringBoot基础学习之整合Shiro框架(下篇) 的学习就到此结束啦,欢迎小伙伴们积极学习和讨论,喜欢的可以给蜗牛君点个关注,顺便来个一键三连,我们下期见,拜拜啦!


参考视频链接:https://www.bilibili.com/video/BV1PE411i7CV(【狂神说Java】SpringBoot最新教程IDEA版通俗易懂)

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

狂奔の蜗牛rz

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值