1. 整合思路
2. 加入jsp相关配置方便测试
2.1 加入依赖:
<!--引入JSP解析依赖-->
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
</dependency>
<dependency>
<groupId>jstl</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
2.2 application.properties 文件
server.servlet.context-path=/shiro
spring.application.name=shiro
spring.mvc.view.prefix=/
spring.mvc.view.suffix=.jsp
2.3 添加启动项配置
2.4 jsp页面代码:
index.jsp
<%@page contentType="text/html;utf-8" pageEncoding="utf-8" isELIgnored="false" %>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<%-- 受限资源--%>
<h1>系统主页</h1>
<%--由于A标签只能发送get请求,如果需要post请求需要使用函数或者其他方式,此处直接get提交,注意项目中尽量post --%>
<a href="${pageContext.request.contextPath}/user/logout">退出登录</a>
<ul>
<li><a href="#">用户管理</a></li>
<li><a href="#">商品管理</a></li>
<li><a href="#">订单管理</a></li>
<li><a href="#">物流管理</a></li>
</ul>
</body>
</html>
login.jsp
<%@page contentType="text/html;utf-8" pageEncoding="utf-8" isELIgnored="false" %>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<h1>登录界面</h1>
<form action="${pageContext.request.contextPath}/user/login" method="post">
用户名:<input type="text" name="username" > <br/>
密码 : <input type="text" name="password"> <br>
<input type="submit" value="登录">
</form>
</body>
</html>
2.5 目录结构:
2.5 查看效果
3. 关联springboot配置简单使用
3.1 引入依赖
<!--引入shiro整合Springboot依赖-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-starter</artifactId>
<version>1.5.3</version>
</dependency>
3.2 配置类编写:
@Configuration
public class ShiroConfig {
//1.创建shiroFilter //负责拦截所有请求
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
//给filter设置安全管理器
shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);
//配置系统受限资源
//配置系统公共资源
Map<String,String> map = new HashMap<String,String>();
map.put("/index.jsp","authc");//authc 请求这个资源需要认证和授权
//默认认证界面路径---当认证不通过时跳转
shiroFilterFactoryBean.setLoginUrl("/login.jsp");
shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
return shiroFilterFactoryBean;
}
//2.创建安全管理器
@Bean
public DefaultWebSecurityManager getDefaultWebSecurityManager(Realm realm){
DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
//给安全管理器设置
defaultWebSecurityManager.setRealm(realm);
return defaultWebSecurityManager;
}
//3.创建自定义realm
// 此处realm中先不加内容直接返回null
@Bean
public Realm getRealm(){
CustomRealm1 customerRealm = new CustomRealm1();
return customerRealm;
}
}
3.3 启动错误解决:
3.3.1 报错1:
Parameter 0 of method getDefaultWebSecurityManager in com.yhx.toali.shiroStudy.withSpringboot.ShiroConfig required a single bean, but 2 were found:
- getRealm: defined by method 'getRealm' in class path resource [com/yhx/toali/shiroStudy/withSpringboot/ShiroConfig.class]
- iniClasspathRealm: defined by method 'iniClasspathRealm' in class path resource [org/apache/shiro/spring/boot/autoconfigure/ShiroAutoConfiguration.class]
这是因为之前在测试的时候编写了shiro.ini,进入到ShiroAutoConfiguration这个类查看,可以看到根据shiro.ini生成了一个realm:
@Bean
@ConditionalOnResource(
resources = {"classpath:shiro.ini"}
)
protected Realm iniClasspathRealm() {
return this.iniRealmFromLocation("classpath:shiro.ini");
}
3.3.2 报错2:
Description:
Method filterShiroFilterRegistrationBean in org.apache.shiro.spring.config.web.autoconfigure.ShiroWebFilterConfiguration required a bean named 'shiroFilterFactoryBean' that could not be found.
Action:
Consider defining a bean named 'shiroFilterFactoryBean' in your configuration.
设置bean的名称:@Bean("shiroFilterFactoryBean")
3.4 启动项目查看index.jsp
可以看到访问index.jsp自动跳到了login.jsp
4. 常见过滤器
shiro提供和多个默认的过滤器,我们可以用这些过滤器来配置控制指定url的权限:
其中主要使用的是anon和authc
5. 认证的登陆和退出实现
5.1 controller方法编写:
@Controller
@RequestMapping("user")
public class UserController {
/**
* 退出登录
*/
@PostMapping("logout")
public String logout() {
Subject subject = SecurityUtils.getSubject();
subject.logout();//退出用户
return "redirect:/login.jsp";
}
/**
* 用来处理身份认证
* @param username
* @param password
* @return
*/
@PostMapping("login")
public String login(String username, String password) {
try {
//获取主体对象
Subject subject = SecurityUtils.getSubject();
// 在认证过程中使用subject.login进行认证
subject.login(new UsernamePasswordToken(username, password));
return "redirect:/index.jsp";
} catch (UnknownAccountException e) {
e.printStackTrace();
System.out.println("用户名错误!");
} catch (IncorrectCredentialsException e) {
e.printStackTrace();
System.out.println("密码错误!");
} catch (Exception e) {
e.printStackTrace();
System.out.println(e.getMessage());
}
return "redirect:/login.jsp";
}
}
5.2 jsp页面事件触发
上面jsp页面代码已有,此处略
5.3 修改自定义realm实现登陆验证
//自定义realm
public class CustomRealm1 extends AuthorizingRealm {
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
return null;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("=============");
//从传过来的token获取到的用户名
String principal = (String) token.getPrincipal();
System.out.println("用户名"+principal);
//假设是从数据库获得的 用户名,密码
String password_db="123";
String username_db="zhangsan";
if (username_db.equals(principal)){
// SimpleAuthenticationInfo simpleAuthenticationInfo =
return new SimpleAuthenticationInfo(principal,password_db, this.getName());
}
return null;
}
}
5.4 修改shiroConfig类中的getShiroFilterFactoryBean()方法进行资源限制:
@Bean("shiroFilterFactoryBean")
public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
//给filter设置安全管理器
shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);
//配置系统受限资源
//配置系统公共资源
Map<String,String> map = new HashMap<String,String>();
map.put("/user/login","anon");//anon 设置为公共资源
map.put("/user/register","anon");//anon 设置为公共资源
map.put("/register.jsp","anon");//anon 设置为公共资源
map.put("/user/getImage","anon");
map.put("/**","authc");//authc 请求这个资源需要认证和授权
//默认认证界面路径---当认证不通过时跳转
shiroFilterFactoryBean.setLoginUrl("/login.jsp");
shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
return shiroFilterFactoryBean;
}
5.5 测试
登录正常,登出正常,未登录和登出后不能访问index.jsp
6. MD5、Salt的认证实现
6.1 springboot整合数据库及mybatis
略
6.2 数据库表结构及相关对象类添加:
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for t_user
-- ----------------------------
DROP TABLE IF EXISTS `t_user`;
CREATE TABLE `t_user` (
`id` int(6) NOT NULL AUTO_INCREMENT,
`username` varchar(40) DEFAULT NULL,
`password` varchar(40) DEFAULT NULL,
`salt` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
SET FOREIGN_KEY_CHECKS = 1;
user的entity:
@Data
@TableName("t_user")
public class User {
@TableId(value = "id", type= IdType.AUTO)
private String id;
@TableField("username")
private String username;
@TableField("password")
private String password;
@TableField("salt")
private String salt;
}
service、mapper、xml略
6.3 创建salt工具类
public class SaltUtils {
/**
* 生成salt的静态方法
* @param n
* @return
*/
public static String getSalt(int n){
char[] chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01234567890!@#$%^&*()".toCharArray();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < n; i++) {
char aChar = chars[new Random().nextInt(chars.length)];
sb.append(aChar);
}
return sb.toString();
}
}
6.4 注册相关代码:
6.4.1 添加注册页面:
<body>
<h1>注册界面</h1>
<form action="${pageContext.request.contextPath}/user/register" method="post">
用户名:<input type="text" name="username" > <br/>
密码 : <input type="text" name="password"> <br>
<input type="submit" value="注册">
</form>
</body>
6.4.2 在shiroconfig中添加权限通过
map.put("/user/register","anon");//anon 设置为公共资源
map.put("/register.jsp","anon");//anon 设置为公共资源
6.4.3 编写java代码:
@RequestMapping("register")
public String register(User user) {
try {
//处理业务调用dao
//1.生成随机盐
String salt = SaltUtils.getSalt(8);
//2.将随机盐保存到数据
user.setSalt(salt);
//3.明文密码进行md5 + salt + hash散列
Md5Hash md5Hash = new Md5Hash(user.getPassword(),salt,1024);
user.setPassword(md5Hash.toHex());
userService.save(user);
return "redirect:/login.jsp";
}catch (Exception e){
e.printStackTrace();
return "redirect:/register.jsp";
}
}
6.4.4 结果测试:
6.5 登陆相关代码:
6.5.1 添加自定义realm
//自定义realm
public class CustomRealm2 extends AuthorizingRealm {
@Autowired
private IUserService userService;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
return null;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//根据身份信息//从传过来的token获取到的用户名
String principal = (String) token.getPrincipal();
//根据身份信息查询
User user = userService.getOne(new LambdaQueryWrapper<User>().eq(User::getUsername, principal));
System.out.println("User:"+user);
//用户不为空
if(!ObjectUtils.isEmpty(user)){
//返回数据库信息
// 这里进行数据库中密码和登陆输入密码加盐对比
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(),
ByteSource.Util.bytes(user.getSalt()), this.getName());
return simpleAuthenticationInfo;
}
return null;
}
}
6.5.2 修改ShiroConfig中realm
使用凭证匹配器以及hash散列
以及在 getShiroFilterFactoryBean 中添加公共资源
@Bean
public Realm getRealm(){
CustomRealm2 customerRealm = new CustomRealm2();
//设置hashed凭证匹配器
HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
//设置md5加密
credentialsMatcher.setHashAlgorithmName("md5");
//设置散列次数
credentialsMatcher.setHashIterations(1024);
customerRealm.setCredentialsMatcher(credentialsMatcher);
return customerRealm;
}
7. 授权实现
7.1 非数据库方式实现:
7.1.1 页面资源授权
在页面的标签上添加权限控制,其中:
shiro:hasAnyRoles
:是当用户有以下权限时,才能访问此资源shiro:hasPermission :
是当用户有此资源访问权限时,才能访问此资源
<%@page contentType="text/html;utf-8" pageEncoding="utf-8" isELIgnored="false" %>
<%@taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<%-- 受限资源--%>
<h1>系统主页</h1>
<a href="${pageContext.request.contextPath}/user/logout">退出登录</a>
<ul>
<shiro:hasAnyRoles name="user_manager,admin,addinfo_manager">
<li><a href="">用户管理</a>
<ul>
<shiro:hasPermission name="user:add:*">
<li><a href="">添加</a></li>
</shiro:hasPermission>
<shiro:hasPermission name="user:delete:*">
<li><a href="">删除</a></li>
</shiro:hasPermission>
<shiro:hasPermission name="user:update:*">
<li><a href="">修改</a></li>
</shiro:hasPermission>
<shiro:hasPermission name="user:find:*">
<li><a href="">查询</a></li>
</shiro:hasPermission>
</ul>
</li>
</shiro:hasAnyRoles>
<shiro:hasAnyRoles name="order_manager,admin,addinfo_manager">
<li><a href="">订单管理</a></li>
<ul>
<shiro:hasPermission name="order:add:*">
<li><a href="">添加</a></li>
</shiro:hasPermission>
<shiro:hasPermission name="order:delete:*">
<li><a href="">删除</a></li>
</shiro:hasPermission>
<shiro:hasPermission name="order:update:*">
<li><a href="">修改</a></li>
</shiro:hasPermission>
<shiro:hasPermission name="order:find:*">
<li><a href="">查询</a></li>
</shiro:hasPermission>
</ul>
</shiro:hasAnyRoles>
<shiro:hasRole name="admin">
<li><a href="">商品管理</a></li>
<li><a href="">物流管理</a></li>
</shiro:hasRole>
<shiro:hasRole name="user">
<li><a href="">仅普通用户可见</a></li>
<li><a href="">公共资源</a></li>
</shiro:hasRole>
</ul>
</body>
</html>
7.1.2 代码方式授权
修改保存接口,只有admin的角色才能进行访问:
@RequestMapping("save")
public String save(){
System.out.println("进入方法");
//基于角色
//获取主体对象
Subject subject = SecurityUtils.getSubject();
//代码方式
if (subject.hasRole("admin")) {
System.out.println("保存订单!");
}else{
System.out.println("无权访问!");
}
//基于权限字符串
//....
return "redirect:/index.jsp";
}
7.1.3 方法调用授权
- @RequiresRoles 用来基于角色进行授权
- @RequiresPermissions 用来基于权限进行授权
@Controller
@RequestMapping("order")
public class OrderController {
@RequiresRoles(value={"admin","user"})//用来判断角色 同时具有 admin user
@RequiresPermissions("user:update:01") //用来判断权限字符串
@RequestMapping("save")
public String save(){
System.out.println("进入方法");
return "redirect:/index.jsp";
}
}
7.1.4 修改CustomRealm2的权限认证方法:
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
//获取身份信息
String primaryPrincipal = (String) principals.getPrimaryPrincipal();
System.out.println("调用授权验证: "+primaryPrincipal);
//根据主身份信息获取角色 和 权限信息
if ("xiaochen".equals(primaryPrincipal)) {
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
authorizationInfo.addRole("admin");
authorizationInfo.addRole("admin");
authorizationInfo.addStringPermission("user:find:*");
authorizationInfo.addStringPermission("user:update:*");
authorizationInfo.addStringPermission("order:update:*");
return authorizationInfo;
}
return null;
}
7.1.5 测试
7.2 数据库方式实现:
7.2.1 数据库添加
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for t_perms
-- ----------------------------
DROP TABLE IF EXISTS `t_perms`;
CREATE TABLE `t_pers` (
`id` int(6) NOT NULL AUTO_INCREMENT,
`name` varchar(80) DEFAULT NULL,
`url` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- ----------------------------
-- Table structure for t_role
-- ----------------------------
DROP TABLE IF EXISTS `t_role`;
CREATE TABLE `t_role` (
`id` int(6) NOT NULL AUTO_INCREMENT,
`name` varchar(60) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- ----------------------------
-- Table structure for t_role_perms
-- ----------------------------
DROP TABLE IF EXISTS `t_role_perms`;
CREATE TABLE `t_role_perms` (
`id` int(6) NOT NULL,
`roleid` int(6) DEFAULT NULL,
`permsid` int(6) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- ----------------------------
-- Table structure for t_user
-- ----------------------------
DROP TABLE IF EXISTS `t_user`;
CREATE TABLE `t_user` (
`id` int(6) NOT NULL AUTO_INCREMENT,
`username` varchar(40) DEFAULT NULL,
`password` varchar(40) DEFAULT NULL,
`salt` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Table structure for t_user_role
-- ----------------------------
DROP TABLE IF EXISTS `t_user_role`;
CREATE TABLE `t_user_role` (
`id` int(6) NOT NULL,
`userid` int(6) DEFAULT NULL,
`roleid` int(6) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
SET FOREIGN_KEY_CHECKS = 1;
7.2.2 相关实体类及查询语句添加
略
7.2.3 复制CustomRealm2为CustomRealm3,修改其中授权方法:
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
//获取身份信息
String primaryPrincipal = (String) principals.getPrimaryPrincipal();
System.out.println("调用授权验证: "+primaryPrincipal);
//根据主身份信息获取角色 和 权限信息
User user = userService.findRolesByUserName(primaryPrincipal);
System.out.println("user:"+user);
//授权角色信息
if(!CollectionUtils.isEmpty(user.getRoles())){
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
user.getRoles().forEach(role->{
simpleAuthorizationInfo.addRole(role.getName()); //添加角色信息
//权限信息
List<Perm> perms = userService.findPermsByRoleId(role.getId());
System.out.println("perms:"+perms);
if(!CollectionUtils.isEmpty(perms) && perms.get(0)!=null ){
perms.forEach(perm->{
simpleAuthorizationInfo.addStringPermission(perm.getName());
});
}
});
return simpleAuthorizationInfo;
}
return null;
}
7.2.4 数据库数据添加
- 用户 admin 具有 admin的角色,具有 对于 user,order的所有权限
- 用户 zhangsan 具有 user的角色,没有权限,只能访问公共资源
- 用户 usermanager 具有 user_manager的角色,具有 对于 user的所有权限
- 用户 ordermanager 具有 order_manager的角色,具有 对于 order的所有权限
- 用户 addinfomanager 具有 addinfo_manager的角色,具有 对于 user,order 的添加权限
7.2.5 测试:
8. shiro结合缓存
作用:减轻数据库压力
示意图:
8.1 使用shiro中默认EhCache实现缓存
8.1.1 添加依赖
<!--引入shiro和ehcache-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.5.3</version>
</dependency>
8.1.2 代码修改
@Bean
public Realm getRealm(){
CustomRealm2 customerRealm = new CustomRealm2();
//设置hashed凭证匹配器
HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
//设置md5加密
credentialsMatcher.setHashAlgorithmName("md5");
//设置散列次数
credentialsMatcher.setHashIterations(1024);
customerRealm.setCredentialsMatcher(credentialsMatcher);
//开启缓存管理器
customerRealm.setCacheManager(new EhCacheManager());
customerRealm.setCachingEnabled(true); // 开启全局缓存
customerRealm.setAuthorizationCachingEnabled(true); // 授权缓存
customerRealm.setAuthenticationCachingEnabled(true);// 认证缓存
return customerRealm;
}
8.1.3 测试
登陆后刷新页面发现没有再走数据库查询代码
8.1.3 优缺点
- 优点:
- 无需自己编写代码,shiro和ehcache集成,登录后自动缓存登陆信息
- 缺点:
- 应用内缓存,一但服务宕机,缓存也随机消失
- 不适用于分布式项目
8.2 使用redis作为缓存
8.2.1 redis配置
略
8.2.2 创建redis的缓存管理器
通过查看ehcache的源码,可以看到其实实现了CacheManager接口,所以这里我们自定义redis的缓存管理器实现CacheManager接口
//自定义shiro缓存管理器
public class RedisCacheManager implements CacheManager {
//参数1:认证或者是授权缓存的统一名称
@Override
public <K, V> Cache<K, V> getCache(String cacheName) throws CacheException {
System.out.println(cacheName);
return new RedisCache<K,V>(cacheName);
}
}
从上述返回值来看,如果要取缓存,还得用到shiro的Cache<K, V>
的实现,所以我们还得自定义一个类来实现Cache<K, V>
接口
8.2.3 自定义redis缓存实现
此实现类中的方法即shiro自动缓存访问的方法。
//自定义redis缓存的实现
public class RedisCache<k,v> implements Cache<k,v> {
private String cacheName;
public RedisCache() {
}
public RedisCache(String cacheName) {
this.cacheName = cacheName;
}
@Override
public v get(k k) throws CacheException {
System.out.println("get key:"+k);
return (v) getRedisTemplate().opsForHash().get(this.cacheName,k.toString());
}
@Override
public v put(k k, v v) throws CacheException {
System.out.println("put key: "+k);
System.out.println("put value:"+v);
getRedisTemplate().opsForHash().put(this.cacheName,k.toString(),v.toString());
return null;
}
@Override
public v remove(k k) throws CacheException {
System.out.println("=============remove=============");
return (v) getRedisTemplate().opsForHash().delete(this.cacheName,k.toString());
}
@Override
public void clear() throws CacheException {
System.out.println("=============clear==============");
getRedisTemplate().delete(this.cacheName);
}
@Override
public int size() {
return getRedisTemplate().opsForHash().size(this.cacheName).intValue();
}
@Override
public Set<k> keys() {
return getRedisTemplate().opsForHash().keys(this.cacheName);
}
@Override
public Collection<v> values() {
return getRedisTemplate().opsForHash().values(this.cacheName);
}
private RedisTemplate getRedisTemplate(){
RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtils.getBean("redisTemplate");
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
return redisTemplate;
}
}
8.2.4 启动登陆测试:
9. 加入验证码验证
略