前言:
小伙伴们,大家好,我是狂奔の蜗牛rz,当然你们可以叫我蜗牛君,我是一个学习Java半年多时间的小菜鸟,同时还有一个伟大的梦想,那就是有朝一日,成为一个优秀的Java架构师。
这个SpringBoot基础学习系列用来记录我学习SpringBoot框架基础知识的全过程 (这个系列是参照B站狂神的SpringBoot最新教程来写的,由于是之前整理的,但当时没有发布出来,所以有些地方可能有错误,希望大家能够及时指正!)
之后我将会尽量以一天一更的速度更新这个系列,还没有学习SpringBoot的小伙伴可以参照我的博客学习一下;当然学习过的小伙伴,也可以顺便跟我一起复习一下基础。最后,希望能够和大家一同进步吧!加油吧!少年们!
由于篇幅较长,所以这里我将其分为了上下两篇博客,上篇主要了解Shiro的功能,以及基本环境的搭建;下篇主要学习Shiro整合Mybatis和Thymeleaf框架。
今天我们来到了SpringBoot基础学习的第九站:整合Shiro框架(上篇), ,废话不多说,让我们开始今天的学习内容吧,
8.1 Shiro基础知识
8.1.1 什么是Shiro?
- Apache Shiro是一个安全 (权限) 框架
- Shiro可以非常容易的开发出足够好的应用,其不仅可以用于JavaSE环境,也可以用在JavaEE环境
- Shiro可以完成认证、授权、加密、会话管理、Web集成、缓存等
官方网址:http://shiro.apache.org/
8.1.2 Shiro有哪些功能?
-
Authentication:身份认证,登录,验证用户是不是拥有相应的身份
-
Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限,即判断用户能否进行什么操作
-
Session Management:会话管理,即用户登录后就是第一次会话,在没有退出之前,它的所有信息都会在会话中,会话可以是普通的JavaSE环境,也可以是Web环境
-
Cryptograpy:加密,保护数据的安全性,如密码加密存储到三个月会议纪要中,而不是明文存储
特别提醒:会议纪要是会议工作的一项重要的环节。 它有两个目的:一是向上级汇报会议情况,以获得上级及时的指导 ;二是向下传达,以便工作贯彻落实 。
-
Web Support:Web支持,可以非常容易的集成到Web环境
-
Caching:缓存,比如用户登录后,其用户信息,拥有角色、权限不必每次去查,这样可以提高效率
-
Concurrency:Shiro支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动的传播过去
-
Testing:提供测试支持
-
Run As:允许一个用户假装为另一个用户 (如果他们允许) 的身份进行访问
-
Remember Me:记住我,这个是非常常见的功能,即一次登录后,下次再来就不用登录了
8.1.3 Shiro架构 (外部)
- Subjcet:
应用代码直接交互的对象是Subject,也就是说Shiro的对外API核心就是Subjcet,Subjcet代表了当前的用户;
这个用户不一定是一个具体的人,与当前应用的交互的任何东西都是Subject,如网络爬虫,机器人等;
与Subject的所有交互都会委托给SecurityManager,Subject其实是一个门面,SubjcetManager才是实际的执行者
- SecurityManager:
安全管理器,即所有与安全相关的操作都会与SecurityManager交互,并且它管理着所有的Subject,它相当于SpringMVC的DispatcherServlet (前端控制器) 的角色
- Realm:
Shiro从Realm获取安全数据 (如用户、角色、权限),就是说SecurityMananger 要验证用户身份,那么它需要从Realm获取相应的用户进行比较,来确定用户的身份是否合法;
也需要从Realm得到用户相应的角色、权限,进行验证用户的操作是否能够进行,把Realm看成DataSource
8.2 搭建hello-shiro模块基本环境
8.2.1 创建父工程项目
1.创建Maven项目
- 创建一个Maven项目,无需勾选任何选项
2.设置项目基本信息
- 设置 GroupId ( 即组名,表示主项目标识 ) 为 com.kuang,ArtifactId ( 即工程名,表示子项目 (模块) 标识 ) 为 springboot-07-shiro
3.选择项目存放位置
- 选择一个要存储的指定位置即可
4.创建父项目成功
- 如下图所示,创建父工程项目成功
5.删除多余文件
- 将父项目中的src源文件删除
8.2.2 搭建子模块基本环境
1.创建hello-shiro子模块
- 在父项目中创建子模块
- 创建普通Maven项目,不用勾选其他
- 编写子模块的基本信息,设置GroupId (组名) 和 ArtifactId (工程名)
- 选择子模块存放位置
2.导入资源依赖
- 在子模块的pom.xml文件中导入对应的资源依赖
<dependencies>
<!-- shiro的核心资源依赖 -->
<!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-core -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.4.1</version>
</dependency>
<!-- slf4j日志资源依赖 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>1.7.21</version>
</dependency>
<!-- slf4j门面资源依赖 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.21</version>
</dependency>
<!-- log4j资源依赖 -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
</dependencies>
- 查看右侧Maven中导入的资源依赖
3.项目基本结构
- 在resources目录下创建log4j.properties文件 (用来设置有关日志输出的相关属性)和shiro.ini文件 (用来设置用户、角色及权限信息)
4.编写log4j.properties配置文件
- 设置有关log4j日志输出的相关属性
# log4j的根日志类型
log4j.rootLogger=INFO, stdout
# log4j的日志输出
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m %n
# apache的全局资源
log4j.logger.org.apache=WARN
# Spring框架的日志
log4j.logger.org.springframework=WARN
# 默认的Shiro日志
log4j.logger.org.apache.shiro=INFO
# 不启用冗长日志
log4j.logger.org.apache.shiro.util.ThreadContext=WARN
log4j.logger.org.apache.shiro.cache.ehcache.EhCache=WARN
5.编写shiro.ini测试文件
- 在shiro.ini配置文件中设置用户、角色及权限信息
特别提醒:ini文件用来进行shiro测试,如果ini文件没有高亮显示,创建完该文件IDEA会提示你安装相应插件
# 设置用户密码及对应角色
[users]
# 用户名为'root',密码为'secret',角色为'admin'
root = secret, admin
# 用户名'guest',密码为'guest',角色为'guest'
guest = guest, guest
# 用户'presidentskroob',密码为'123456',角色为'president'
# presidentskroob:总统斯卡普
presidentskroob = 12345, president
# 用户'darkhelmet',密码为'ludicrousspeed',角色为'darklord'和'schwartz'
# darkhelmet:黑暗头盔,ludicrousspeed:惊死人的飞速,schwartz:施瓦兹
darkhelmet = ludicrousspeed, darklord, schwartz
# 用户'lonestarr',密码为'vespa',角色为'goodguy'和'schwartz'
# lonestarr是独孤的斯塔尔
lonestarr = vespa, goodguy, schwartz
# 设置角色及相关权限
[roles]
# 角色'admin'拥有所有的权限,使用通配符'*'表示
admin = *
# 角色'schwartz'拥有lightsaber(类型)下的所有权限(使用通配符*表示)
# schwartz是指绝地武士,lightsaber是光剑
schwartz = lightsaber:*
# 角色'goodguy'只拥有'winnebago'(类型)下的'drive'(行为)
# 下的'eagle5'(即牌照,这里指具体的实例(用户)id)的权限
# goodguy:好人,winnebago:温尼贝格,drive:驾驶,eagle5老鹰5
goodguy = winnebago:drive:eagle5
6.编写Quickstart类
- 设置shiro.ini文件中存在的用户
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 简单的快速入门应用程序,展示了如何去使用shiro的API
*
* @since 0.9 RC2
*/
public class Quickstart {
// 使用日志门面
private static final transient Logger log = LoggerFactory.getLogger(Quickstart.class);
public static void main(String[] args) {
/**
* 一个简单的方式去创建Shiro安全管理器(SecurityManager),
* 通过配置范围(realms),用户(users),角色(roles)和权限(permissions),去使用简单INI配置
* 我们将使用一个工厂(factory)可以摄取一个.ini文件并且返回一个安全管理器实例
*/
/**
* 1.加载.ini配置文件,获取securityManager安全管理器(这三步是固定的)
*/
// 1.1 通过工厂模式来加载根路径下的.ini文件(但是IniSecurityManagerFactory已经过时了)
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
// 1.2 通过工厂的单例模式来获取一个单例的安全管理器(SecurityManager)对象
SecurityManager securityManager = factory.getInstance();
/**
* 大多数应用不会这么做,而是依赖它们webapps下的容器配置或者web.xml
*/
// 1.3 设置安全管理器为上面创建的单例securityManager对象
SecurityUtils.setSecurityManager(securityManager);
/**
* 2.目前还没有设置一个简单的Shiro环境,下面是具体步骤
*/
// 2.1 获取当前的用户对象Subject
Subject currentUser = SecurityUtils.getSubject();
/**
* 2.2 存取用户的session信息
*/
// 2.2.1 通过当前用户获取session
Session session = currentUser.getSession();
// 2.2.2 在session中存值
session.setAttribute("someKey", "aValue");
// 2.2.3 获取session中存入的值
String value = (String) session.getAttribute("someKey");
// 2.2.4 判断其是否等于session中之前存入的值
if (value.equals("aValue")) {
// 打印value的值
log.info("Subject=>session[" + value + "]");
}
// 2.3 判断当前用户是否被认证
if (!currentUser.isAuthenticated()) {
// 2.3.1 如果通过认证就拿到一个Token令牌,用户名为孤独的斯塔尔(lonestarr)
UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");
// 2.3.2 开启记住我
token.setRememberMe(true);
try {
// 2.3.3 执行登录操作
/**
* 用户操作实际放在Realm中,而登录操作在Subject中
*/
currentUser.login(token);
// 2.3.4 捕获未知账户异常(UnknownAccountException)
} catch (UnknownAccountException uae) {
// 打印token中获取的认证
log.info("There is no user with username of " + token.getPrincipal());
// 2.3.5 捕获错误凭证异常
} catch (IncorrectCredentialsException ice) {
// 打印token中获取的认证
log.info("Password for account " + token.getPrincipal() + " was incorrect!");
// 2.3.6 捕获用户被锁定异常
} catch (LockedAccountException lae) {
// 打印token中获取的认证
log.info("The account for username " + token.getPrincipal() + " is locked. " +
"Please contact your administrator to unlock it.");
}
// 2.3.7 捕获其他的异常 (根据具体的应用需求定制)
catch (AuthenticationException ae) {
// 异常的条件和错误
}
}
// 打印token中当前用户的认证
log.info("User [" + currentUser.getPrincipal() + "] logged in successfully.");
/**
* 2.4 测试角色
*/
// 2.4.1 判断当前用户lonestarr(孤独的斯塔尔)的角色是否为原力(schwartz)
if (currentUser.hasRole("schwartz")) {
// 2.4.2 认证后登录成功,输出日志"愿原力与你同在"
log.info("May the Schwartz be with you!");
} else {
// 2.4.3 认证失败,输出日志"你好,凡人"
log.info("Hello, mere mortal.");
}
/**
* 2.5 测试用户对应权限
*/
// 2.5.1 粗粒度权限
// 判断当前用户(lonestarr)是否拥有lightsaber:wield(光剑:挥舞)的权限
if (currentUser.isPermitted("lightsaber:wield")) {
// #1 认证成功,日志输出"你使用了光剑环,明智的使用它"
log.info("You may use a lightsaber ring. Use it wisely.");
} else {
// #3 认证失败,日志输出"抱歉,原力的主人才拥有光剑环"
log.info("Sorry, lightsaber rings are for schwartz masters only.");
}
/**
* 2.5.2 细粒度权限
*/
// #1 判断当前用户(lonestarr)是否拥有winnebago:drive:eagle5(温尼贝格-类型:驾驶-行为:老鹰5-牌照)权限
if (currentUser.isPermitted("winnebago:drive:eagle5")) {
// #2 认证成功,日志输出"你拥有驾驶类型为温尼贝格,牌照(id)为eagle5的权限,钥匙在这,祝你愉快!"
log.info("You are permitted to 'drive' the winnebago with license plate (id) 'eagle5'. " +
"Here are the keys - have fun!");
} else {
// #3 认证失败,日志输出"抱歉,你不被允许驾驶'eagle5'温尼贝格
log.info("Sorry, you aren't allowed to drive the 'eagle5' winnebago!");
}
// 2.6 注销,所有角色都可以执行该操作
currentUser.logout();
// 退出系统
System.exit(0);
}
}
- 设置shiro.ini文件中不存在的用户
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 简单的快速入门应用程序,展示了如何去使用shiro的API
*
* @since 0.9 RC2
*/
public class Quickstart {
//使用日志门面
private static final transient Logger log = LoggerFactory.getLogger(Quickstart.class);
public static void main(String[] args) {
/**
* 一个简单的方式去创建Shiro安全管理器(SecurityManager),
* 通过配置范围(realms),用户(users),角色(roles)和权限(permissions),去使用简单INI配置
* 我们将使用一个工厂(factory)可以摄取一个.ini文件并且返回一个安全管理器实例
*/
/**
* 1.加载.ini配置文件,获取securityManager安全管理器(这三步是固定的)
*/
// 1.1 通过工厂模式来加载根路径下的.ini文件(但是IniSecurityManagerFactory已经过时了)
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
// 1.2 通过工厂的单例模式来获取一个单例的安全管理器(SecurityManager)对象
SecurityManager securityManager = factory.getInstance();
/**
* 大多数应用不会这么做,而是依赖它们webapps下的容器配置或者web.xml
*/
// 1.3 设置安全管理器为上面创建的单例securityManager对象
SecurityUtils.setSecurityManager(securityManager);
/**
* 2.目前还没有设置一个简单的Shiro环境,下面是具体步骤
*/
// 2.1 获取当前的用户对象Subject
Subject currentUser = SecurityUtils.getSubject();
/**
* 2.2 存取用户的session信息
*/
// 2.2.1 通过当前用户获取session
Session session = currentUser.getSession();
// 2.2.2 在session中存值
session.setAttribute("someKey", "aValue");
// 2.2.3 获取session中存入的值
String value = (String) session.getAttribute("someKey");
// 2.2.4 判断其是否等于session中之前存入的值
if (value.equals("aValue")) {
// 打印value的值
log.info("Subject=>session[" + value + "]");
}
/**
* 2.3 判断当前用户是否被认证
*/
if (!currentUser.isAuthenticated()) {
// 2.3.1 设置一个不存在的用户
// UsernamePasswordToken token = new UsernamePasswordToken("lonestarr22", "vespa");
// 2.3.2 开启记住我
token.setRememberMe(true);
// ...(由于后面的代码跟上面的代码相同,所以这里就省略掉了)...
/**
* 2.6 注销,所有角色都可以执行该操作
*/
currentUser.logout();
// 退出系统
System.exit(0);
}
}
7.查看结果
- 查看设置存在的用户的控制台输出
结果:该用户登录成功,拥有相应的权限!
- 查看设置不存在的用户的控制台输出
结果:该用户不存在,没有相应的权限!
8.3 SpringBoot整合Shiro环境搭建
8.3.1搭建基本环境
1.创建shiro-springboot子模块
2.创建Spring initializr项目
3.设置子模块基本信息
4.选择子模块的资源依赖
- 这里勾选Spring Web和Thymeleaf模板引擎即可
5.选择子模块的存放位置
6.子模块创建成功
7.删除子项目中多余文件
8.3.2 搭建环境后简单测试
1.模块基本结构和编写配置文件
1-1 项目模块基本结构
1-2 编写application.properties配置文件
# 修改默认服务器端口号
server.port=8888
2.导入相关资源依赖
2-1 导入shiro整合springboot资源依赖
<!-- 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>
2-2 所有的资源依赖
<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>
</dependencies>
3.编写MyController控制器类
package com.kuang.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
// 使用@Controller注解, 将UserController注册为控制器, 交由Spring的IOC容器统一管理
@Controller
public class UserController {
/**
* 跳转到首页
* 使用@RequestMapping注解, 设置请求映射路径, 请求方式为get方式
* 多个请求使用{}包围, 使用","进行分隔
*/
@RequestMapping({"/","/index"})
public String toIndex(Model model) {
// 设置视图模型信息
model.addAttribute("msg", "Hello,Shiro!");
// 返回视图逻辑名
return "index";
}
}
4.编写index.html和页面访问测试
4-1 编写index.html主页面
<!DOCTYPE html>
<!-- 注意: 这里需要引入thymeleaf的命名空间, 否则无法生效 -->
<html lang="en" xmlns:th=http://www.thymeleaf.org>
<head>
<meta charset="UTF-8">
<title>首页</title>
</head>
<body>
<h1>首页</h1>
<!--使用th:text来显示信息-->
<p th:text="${msg}"></p>
</body>
</html>
4-2 页面访问测试
结果:访问首页成功!
8.3.3 完善模块环境
1.模块基本结构
2.编写ShiroConfig和UserRealm配置类
2-1 编写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.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
// 使用@Configuration注解, 让ShiroConfig成为配置类, 交由Spring的IOC容器统一管理
@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容器中去
@Bean(name="securityManager")
// 2.2 使用@Qualifier注解,通过名字(userRealm)获取Spring的IOC容器中的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的IOC容器中去
@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/*"所有的user下的请求, "authc"表示通过认证后才能访问
filterMap.put("/user/*", "authc");
// 3.2.3 设置FilterChainDefinitionMap(过滤链式定义Map)
factoryBean.setFilterChainDefinitionMap(filterMap);
// 3.4 返回factoryBean对象
return factoryBean;
}
}
2-2 编写UserRealm数据连接类
package com.kuang.config;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
// 自定义UserRealm,继承AuthorizingRealm类
public class UserRealm extends AuthorizingRealm {
/**
* 授权
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("执行了=>授权doGetAuthorizationInfo");
return null;
}
/**
* 认证
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("执行了=>授权doGetAuthenticationInfo");
return null;
}
}
3.修改主页面和编写增加修改页面
3-1 修改index.html页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>新增用户</title>
</head>
<body>
</body>
</html>
3-2 编写addUser.html添加页
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>新增用户</title>
</head>
<body>
<h1>新增用户</h1>
</body>
</html>
3-3 编写updateUser.html修改页
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>修改用户</title>
</head>
<body>
<h1>修改用户</h1>
</body>
</html>
4.编写UserController控制器类
package com.kuang.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
// 使用@Controller注解, 将UserController注册为控制器, 交由Spring的IOC容器统一管理
@Controller
@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";
}
}
5.页面访问测试
5-1 访问index.html主页
结果:访问首页成功!
5-2 访问addUser.html新增页
结果:访问失败,404找不到资源!
5-3 访问updateUser.html修改页
结果:访问失败,404找不到资源!
4-4 错误分析
具体分析:
可以发现,请求路径上出现了login.jsp,这个login.jsp应该是Shiro默认登录JSP页面;因为我们要进行增改操作时,需要先登录进行身份验证后,才能执行用户对应角色的操作;
但由于我们并没有登录的jsp页面,所以才出现了访问失败问题;因此我们需要自定义登录页,编写相关的控制请求,以及在Shiro的核心配置类中编写登录跳转的相关请求
8.3.4 自定义登录页和页面测试访问
1.修改子模块结构和编写login.html登录页
1-2 在templates下创建login.html登录页
1-2 编写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>
<input type="submit" value="登录" />
</form>
</body>
</html>
2.修改UserController控制类和ShiroConfig以及UserRealm配置类
2-1 修改UserController控制类
package com.kuang.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.PostMapping;
@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,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";
}
}
}
2-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成为配置类, 交由Spring的IOC容器统一管理
@Configuration
public class ShiroConfig {
/**
* 1.创建realm对象,需要自定义类
*/
// 1.1 将userRealm注册到Spring容器中去
@Bean
public UserRealm userRealm() {
// 1.2 返回值为创建一个UserRealm对象
return new UserRealm();
}
/**
* 2.设置DefaultWebSecurityManager(默认Web安全管理器)
*/
//2.1 使用@Bean注解,将getDefaultWebSecurityManager方法注册到Spring容器中去
@Bean(name="securityManager")
//2.2 使用@Qualifier注解,通过名字userRealm获取Spring容器中的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]");
// #3 "/user/updateUser"是修改用户的请求,"perms[user:updateUser]"表示只有拥有修改用户权限才能访问
filterMap.put("/user/updateUser", "perms[user:updateUser]");
// #2 "/user/*"所有的user下的请求, "authc"表示通过认证后才能访问
filterMap.put("/user/*", "authc");
// 3.2.3 设置FilterChainDefinitionMap(过滤链式定义Map)
factoryBean.setFilterChainDefinitionMap(filterMap);
// 3.3 设置登录和未授权的相关请求
// 3.3.1 设置登录的请求
factoryBean.setLoginUrl("/toLogin");
// 3.4 返回factoryBean对象
return factoryBean;
}
}
2-3 修改UserRealm配置类
package com.kuang.config;
import com.kuang.pojo.User;
import com.kuang.service.UserService;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
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");
return null;
}
/**
* 2.认证
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("执行了=>授权doGetAuthenticationInfo");
/**
* 2.1 没有连接数据库
*/
// 2.1.1 设置用户名和密码
// 需要从数据库中取这里没有连接数据库,所以我们使用伪造数据
String username = "root";
String password = "123456";
// 2.1.2 获取用户认证信息
UsernamePasswordToken userToken = (UsernamePasswordToken) authenticationToken;
// 2.1.3 判断用户名是否与数据库中用户名相同
if (!userToken.getUsername().equals(username)) {
// 如果不同,就抛出异常UnknownAccountException
return null;
}
// 2.1.4 密码认证,Shiro来做
return new SimpleAuthenticationInfo("",password,"");
}
}
3.页面访问测试
3-1 访问addUser.html新增页
结果:访问新增页面后跳转到登录页!
3-2 访问updateUser修改页
结果:访问修改页面后跳转到登录页!
3-3 用户登录测试
- 输入正确的用户名和密码
- 用户登录验证成功
- 输入错误的用户名和密码
- 用户登录验证失败
好了,今天的有关 SpringBoot基础学习之整合Shiro框架(上篇) 的学习就到此结束啦,欢迎小伙伴们积极学习和讨论,喜欢的可以给蜗牛君点个关注,顺便来个一键三连,我们下期见,拜拜啦!
参考视频链接:https://www.bilibili.com/video/BV1PE411i7CV(【狂神说Java】SpringBoot最新教程IDEA版通俗易懂)