前言:
小伙伴们,大家好,我是狂奔の蜗牛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版通俗易懂)