shiro项目爆肝
作者:不染心
时间:2021/8/2
项目地址: https://download.csdn.net/download/qq_38234785/20679811
一、概念介绍
(一)权限管理
1、权限管理
基本上涉及到用户参与的系统都要进行权限管理,权限管理属于系统安全的范畴,权限管理实现对用户访问系统的控制
,按照安全规则或者安全策略控制用户可以访问而且只能访问自己被授权的资源。
权限管理包括用户身份认证
和授权
两部分,简称认证授权
。对于需要访问控制的资源用户首先经过身份认证,认证通过后用户具有该资源的访问权限方可访问。
2、身份认证
身份认证
,就是判断一个用户是否为合法用户的处理过程。最常用的简单身份认证方式是系统通过核对用户输入的用户名和口令,看其是否与系统中存储的该用户的用户名和口令一致,来判断用户身份是否正确。对于采用指纹等系统,则出示指纹;对于硬件Key等刷卡系统,则需要刷卡。
3、授权
授权,即访问控制
,控制谁能访问哪些资源。主体进行身份认证后需要分配权限方可访问系统的资源,对于某些资源没有权限是无法访问的
(二)Shiro
1、Shiro概念
Apache Shiro™ is a powerful and easy-to-use Java security framework that performs authentication, authorization, cryptography, and session management. With Shiro’s easy-to-understand API, you can quickly and easily secure any application – from the smallest mobile applications to the largest web and enterprise applications.
Shiro 是一个功能强大且易于使用的Java安全框架,它执行身份验证、授权、加密和会话管理。使用Shiro易于理解的API,您可以快速轻松地保护任何应用程序—从最小的移动应用程序到最大的web和企业应用程序。
Shiro是apache旗下一个开源框架,它将软件系统的安全认证相关的功能抽取出来,实现用户身份认证,权限授权、加密、会话管理等功能,组成了一个通用的安全认证框架。
2、Shiro核心组件
🍀 Subject
Subject即主体
,外部应用与subject进行交互,subject记录了当前操作用户,将用户的概念理解为当前操作的主体,可能是一个通过浏览器请求的用户,也可能是一个运行的程序。 Subject在shiro中是一个接口,接口中定义了很多认证授相关的方法,外部程序通过subject进行认证授,而subject是通过SecurityManager安全管理器进行认证授权
🍀 SecurityManager
SecurityManager即安全管理器
,对全部的subject进行安全管理,它是shiro的核心,负责对所有的subject进行安全管理。通过SecurityManager可以完成subject的认证、授权等,实质上SecurityManager是通过Authenticator进行认证,通过Authorizer进行授权,通过SessionManager进行会话管理等。
SecurityManager是一个接口,继承了Authenticator, Authorizer, SessionManager这三个接口。
🍀 Authenticator
Authenticator即认证器
,对用户身份进行认证,Authenticator是一个接口,shiro提供ModularRealmAuthenticator实现类,通过ModularRealmAuthenticator基本上可以满足大多数需求,也可以自定义认证器。
🍀 Authorizer
Authorizer即授权器
,用户通过认证器认证通过,在访问功能时需要通过授权器判断用户是否有此功能的操作权限。
🍀 Realm
Realm即领域
,相当于datasource数据源,securityManager进行安全认证需要通过Realm获取用户权限数据,比如:如果用户身份数据在数据库那么realm就需要从数据库获取用户身份信息。
- 注意:不要把realm理解成只是从数据源取数据,在realm中还有认证授权校验的相关的代码。
🍀 SessionManager
sessionManager即会话管理
,shiro框架定义了一套会话管理,它不依赖web容器的session,所以shiro可以使用在非web应用上,也可以将分布式应用的会话集中在一点管理,此特性可使它实现单点登录。
🍀 SessionDAO
SessionDAO即会话dao
,是对session会话操作的一套接口,比如要将session存储到数据库,可以通过jdbc将会话存储到数据库。
🍀 CacheManager
CacheManager即缓存管理
,将用户权限数据存储在缓存,这样可以提高性能。
🍀 Cryptography
Cryptography即密码管理
,shiro提供了一套加密/解密的组件,方便开发。比如提供常用的散列、加/解密等功能。
3、Shiro认证
4、Shiro授权
二、基础项目构建
💥 实现功能:
- MySQL数据库用户信息、权限存储
- 注册、认证、授权
- 注册:哈希(密码+salt)存储;
- 认证:用户登陆,赋予角色;
- 授权:用户登陆,赋予权限列表;
- 设定权限访问API
- 依据授权列表,判断该用户是否有访问API的权限;
(一)数据库
也可直接下载项目,里面有sql文件
🍃 user
user_id | user_name | user_role | user_pass | salt | user_tel | update_user | update_time |
---|---|---|---|---|---|---|---|
18 | admintest | admin | 61ca717a947acb9ae594d41188a77176 | NF4&*7DT | 19987654376 | zhangsna | 7/22/2021 15:39:26 |
19 | usertest | user | d246c655fbb39fde25f39b9282c2cda0 | Olr@fnwY | 19987654376 | zhangsna | 7/22/2021 16:37:03 |
🍃 role
role_id | role_name | role_code | role_description |
---|---|---|---|
1 | 系统管理员 | admin | 系统管理员 |
10 | 普通用户 | user | 普通用户 |
🍃 permission
permission_id | permission_name | permission_code | permission_url |
---|---|---|---|
1 | 用户管理 | user | /user |
2 | 用户新增 | useradd | /user/add |
3 | 用户修改 | userupdate | /user/update |
4 | 用户删除 | userdel | /user/del |
5 | 权限管理 | permis | /permis |
6 | 权限查看 | permislist | /permis/list |
7 | 用户权限test | usertest | /user/test |
🍃 role_permission
role_id | permission_id |
---|---|
1 | 1 |
1 | 2 |
1 | 3 |
1 | 4 |
10 | 1 |
10 | 7 |
(二)项目结构
(三)代码编写
1、配置文件
🍃pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.dyl</groupId>
<artifactId>shiroCSDN</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>shiroCSDNpro</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- shiro -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.6.0</version>
</dependency>
<!--mysql connector 根据自己mysql版本号选择驱动-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.39</version>
</dependency>
<!--mysql connector 根据自己mysql版本号选择驱动-->
<!--参数校验依赖-->
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.1.7.Final</version>
</dependency>
<dependency>
<groupId>javax.el</groupId>
<artifactId>javax.el-api</artifactId>
<version>3.0.0</version>
</dependency>
<!--参数校验依赖-->
<!--ehcache缓存-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.5.3</version>
</dependency>
<!--ehcache缓存-->
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
🍃resources > application.yml
spring:
datasource:
name: shirocsdn
url: jdbc:mysql://localhost:3306/shirocsdn #url
username: root #用户名
password: 123456 #密码
driver-class-name: com.mysql.jdbc.Driver #数据库链接驱动
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.example.domainsysadmin.mapper
2、公共模块
🍃com.dyl.shirocsdn.utils.ApplicationContextUtils.java
:获取容器中的bean
package com.dyl.shirocsdn.utils;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
@Component
public class ApplicationContextUtils implements ApplicationContextAware {
private static ApplicationContext context;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.context = applicationContext;
}
/**
* 根据bean的名字获取工厂中指定的bean/对象
*/
public static Object getBean(String beanName){
return context.getBean(beanName);
}
}
🍃com.dyl.shirocsdn.utils.PatternContents.java
:参数校验正则匹配和异常信息
package com.dyl.shirocsdn.utils;
/**
* 公共参数校验
*/
public class PatternContents {
// 手机号
public static final String TEL_TYPE = "^1([358][0-9]|4[579]|66|7[0135678]|9[89])[0-9]{8}$";
public static final String TEL_DES = "电话号码不合法";
// email
public static final String EMAIL_TYPE = "^\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*$";
public static final String EMAIL_DES = "Email不合法";
// 身份证号码
public static final String IDCARD_TYPE = "^\\d{15}|\\d{18}$";
public static final String IDCARD_DES = "身份证不合法";
// 中文名字
public static final String CHINESE_NAME_TPYE = "^[\\u4e00-\\u9fa5]{2,}$";
public static final String CHINESE_NAME_DES = "长度大于2的中文名字";
/**
* ========= user ===============
*/
// 账户名称
public static final String SYSUSER_NAME_TYPE = "^.{8,}$";
public static final String SYSUSER_NAME_DES = "用户名称长度应大于8";
// 账户密码
public static final String USER_PASS_TYPE = "^(\\w){6,20}$";
public static final String USER_PASS_DES = "用户密码长度应大于6";
/**
* ========= role ===============
*/
public static final String ROLE_CODE = "^[A-Za-z]{2,}$";
public static final String ROLE_DESCRIPTION = "^.{2,}$";
}
🍃com.dyl.shirocsdn.utils.SaltUtils.java
:生成用户哈希用户密码的随即盐
package com.dyl.shirocsdn.utils;
import java.util.Random;
public class SaltUtils {
public static String getSalt(int n){
char[] chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()".toCharArray();
StringBuilder stringBuilder = new StringBuilder();
for(int i = 0; i < n; i++){
char aChar = chars[new Random().nextInt(chars.length)];
stringBuilder.append(aChar);
}
return stringBuilder.toString();
}
public static void main(String[] args) {
String salt = getSalt(6);
System.out.println(salt);
}
}
3、实体类
🍃com.dyl.shirocsdn.entity.User.java
package com.dyl.shirocsdn.entity;
import com.dyl.shirocsdn.utils.PatternContents;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import javax.validation.constraints.Pattern;
@Setter
@Getter
@AllArgsConstructor
@NoArgsConstructor
public class User {
private int user_id;
@Pattern(regexp = PatternContents.SYSUSER_NAME_TYPE, message = PatternContents.SYSUSER_NAME_DES)
private String user_name;
private String user_role;
@Pattern(regexp = PatternContents.USER_PASS_TYPE, message = PatternContents.USER_PASS_DES)
private String user_pass;
private String salt;
@Pattern(regexp = PatternContents.TEL_TYPE, message = PatternContents.TEL_DES)
private String user_tel;
private String update_user;
private String update_time;
public User(String user_name, String user_role, String user_pass, String user_tel){
this.user_name = user_name;
this.user_role = user_role;
this.user_pass = user_pass;
this.user_tel = user_tel;
this.update_user = "system";
}
}
🍃com.dyl.shirocsdn.entity.ReturnEntity.java
:返回实体
package com.dyl.shirocsdn.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
public class ReturnEntity {
private int code;
private String msg;
private String data;
}
4、Shiro层
🍃com.dyl.shirocsdn.shiro.realms.CustomerRealm.java
:自定义realm,其中功能包括:认证、授权
package com.dyl.shirocsdn.shiro.realms;
import com.dyl.shirocsdn.entity.User;
import com.dyl.shirocsdn.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import javax.annotation.Resource;
import java.util.List;
/**
* @author dyl
*/
@Slf4j
public class CustomerRealm extends AuthorizingRealm {
@Resource
private UserService userService;
/**
* 授权
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
// 能进入这里说明用户已经通过验证了
log.info("====授权开始===");
String userName = (String)principalCollection.getPrimaryPrincipal();
User sysUser = userService.selectUserByName(userName);
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
String user_role = sysUser.getUser_role();
simpleAuthorizationInfo.addRole(user_role);
List<String> permisList = userService.selectPermisByRole(user_role);
log.info( userName+ "的权限有:" + permisList);
for(String permission: permisList){
simpleAuthorizationInfo.addStringPermission(permission);
}
return simpleAuthorizationInfo;
}
/**
* 认证
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
// 获取用户输入的账户
String user_name = (String) authenticationToken.getPrincipal();
// 通过username从数据库中查找 UserInfo 对象
// 实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法
User user = userService.selectUserByName(user_name);
if(user != null){
return new SimpleAuthenticationInfo(user.getUser_name(), user.getUser_pass(), ByteSource.Util.bytes(user.getSalt()), this.getName());
}
return null;
}
}
🍃com.dyl.shirocsdn.shiro.config.ShiroConfig.java
:shiro配置文件
package com.dyl.shirocsdn.config;
import com.dyl.shirocsdn.shiro.realms.CustomerRealm;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.cache.ehcache.EhCacheManager;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.session.InvalidSessionException;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.mgt.SessionKey;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import java.util.HashMap;
import java.util.Map;
/**
* 用来整合shiro框架相关的配置类
* @Ling https://zhuanlan.zhihu.com/p/354205960
* @author dyl
*/
@Configuration
public class ShiroConfig {
/**
* 创建shiroFilter,负责拦截所有请求
* @param defaultWebSecurityManager
* @return
*/
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
//给filter设置安全管理器
shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);
//配置系统受限资源
//配置系统的公共资源
Map<String, String> map = new HashMap<>();
//请求这个资源需要认证和授权
map.put("/api/user/login", "anon");
map.put("/api/user/register", "anon");
map.put("/**", "authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
//默认认证界面路径(可不写)
shiroFilterFactoryBean.setLoginUrl("/login.html");
return shiroFilterFactoryBean;
}
/**
* 创建安全管理器
* @param realm
* @return
*/
@Bean
public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("getRealm") Realm realm){
DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
//给安全管理器设置
defaultWebSecurityManager.setRealm(realm);
return defaultWebSecurityManager;
}
/**
* 创建自定义Realm
* @return
*/
@Bean
public Realm getRealm(){
CustomerRealm customerRealm = new CustomerRealm();
//修改凭证校验匹配器
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
//设置加密算法为md5
hashedCredentialsMatcher.setHashAlgorithmName("MD5");
//设置散列次数
hashedCredentialsMatcher.setHashIterations(1024);
customerRealm.setCredentialsMatcher(hashedCredentialsMatcher);
//开启cache缓存管理, 将自定义的RedisCacheManager注入进来
// customerRealm.setCacheManager(new RedisCacheManager());
// 使用ehcache做本地缓存
customerRealm.setCacheManager(new EhCacheManager());
//开启全局缓存
customerRealm.setCachingEnabled(true);
//开启认证缓存
customerRealm.setAuthenticationCachingEnabled(true);
//开启授权缓存
customerRealm.setAuthorizationCachingEnabled(true);
// 认证name
customerRealm.setAuthenticationCacheName("authenticationCache");
// 授权name
customerRealm.setAuthorizationCacheName("authorizationCache");
return customerRealm;
}
//注入权限管理
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
@Bean
@ConditionalOnMissingBean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator defaultAAP = new DefaultAdvisorAutoProxyCreator();
defaultAAP.setProxyTargetClass(true);
return defaultAAP;
}
}
5、Mapper层
🍃resources.mapper.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.dyl.shirocsdn.mapper.UserMapper">
<!--查询-->
<select id="selectAllUser" resultType="com.dyl.shirocsdn.entity.User" parameterType="com.dyl.shirocsdn.entity.User">
select * from user
</select>
<!--增-->
<insert id="addUser" parameterType="com.dyl.shirocsdn.entity.User">
insert into user(user_name, user_role, user_pass, salt, user_tel, update_user, update_time) VALUE(#{user_name}, #{user_role}, #{user_pass}, #{salt}, #{user_tel}, #{update_user}, #{update_time})
</insert>
<!--查询-->
<select id="selectUserByName" resultType="com.dyl.shirocsdn.entity.User" parameterType="String">
select * from user where user_name = #{user_name}
</select>
<select id="selectPermisByRole" resultType="String" parameterType="String">
SELECT permission_code FROM `permission` as t1
INNER JOIN role_permission as t2 on t1.permission_id = t2.permission_id
INNER JOIN role as t3 on t3.role_id = t2.role_id
where role_code = #{role};
</select>
</mapper>
🍃com.dyl.shirocsdn.shiro.mapper.UserMapper.java
package com.dyl.shirocsdn.mapper;
import com.dyl.shirocsdn.entity.User;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
/**
* @author Administrator
*/
@Mapper
public interface UserMapper {
List<User> selectAllUser();
int addUser(User user);
User selectUserByName(String user_name);
List<String> selectPermisByRole(String role);
}
6、Service层
🍃com.dyl.shirocsdn.shiro.service.UserService.java
package com.dyl.shirocsdn.service;
import com.dyl.shirocsdn.entity.ReturnEntity;
import com.dyl.shirocsdn.entity.User;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import java.util.List;
/**
* @author dyl
*/
public interface UserService {
List<User> selectAllUser();
ReturnEntity addUser(User user);
ReturnEntity loginUser(String user_name, String user_pass);
ReturnEntity userLoginOut();
User selectUserByName(String userName);
List<String> selectPermisByRole(String role);
}
🍃com.dyl.shirocsdn.shiro.service.impl.UserServiceImpl.java
package com.dyl.shirocsdn.service.impl;
import com.dyl.shirocsdn.entity.ReturnEntity;
import com.dyl.shirocsdn.entity.User;
import com.dyl.shirocsdn.mapper.UserMapper;
import com.dyl.shirocsdn.service.UserService;
import com.dyl.shirocsdn.utils.SaltUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.crypto.hash.Md5Hash;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
/**
* @author dyl
* @description: 用户服务
* @date 2021/7/30 0030 下午 16:31
*/
@Slf4j
@Service
public class UserServiceImpl implements UserService {
@Autowired
UserMapper userMapper;
@Override
public List<User> selectAllUser() {
return userMapper.selectAllUser();
}
/**
* 添加用户
* @param user
* @return
*/
@Override
@Transactional(rollbackFor = Exception.class)
public ReturnEntity addUser(User user) {
int addCount;
log.info("添加用户为:" + user);
String salt = SaltUtils.getSalt(8);
user.setSalt(salt);
Md5Hash md5Hash = new Md5Hash(user.getUser_pass(), salt, 1024);
user.setUser_pass(md5Hash.toHex());
addCount = userMapper.addUser(user);
return addCount > 0
? new ReturnEntity(200, "创建用户成功", "")
: new ReturnEntity(500, "创建用户失败", "");
}
/**
* 登陆
* @param user_name
* @param user_pass
* @return
*/
@Override
@Transactional(rollbackFor = Exception.class)
public ReturnEntity loginUser(String user_name, String user_pass) {
Subject subject = SecurityUtils.getSubject();
subject.login(new UsernamePasswordToken(user_name, user_pass));
if(subject.isAuthenticated()) {
log.info("{} 认证成功", user_name);
// subject.checkPermission("userfjkdal");
// subject.checkRole("admin");
}
return subject.isAuthenticated()
? new ReturnEntity(200, "OK", "登陆授权成功")
: new ReturnEntity(500, "ERROR", "登陆授权成功");
}
@Override
public ReturnEntity userLoginOut() {
Subject subject = SecurityUtils.getSubject();
subject.logout();
return new ReturnEntity(200, "OK", "登出成功");
}
@Override
public User selectUserByName(String user_name) {
return userMapper.selectUserByName(user_name);
}
@Override
public List<String> selectPermisByRole(String role) {
return userMapper.selectPermisByRole(role);
}
}
7、Controller层
🍃com.dyl.shirocsdn.shiro.controller.SourceController.java
package com.dyl.shirocsdn.controller;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.apache.shiro.subject.Subject;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author dyl
* @description:
* @date 2021/7/30 0030 下午 21:59
*/
@Slf4j
@RestController
@RequestMapping("/api/res")
public class SourceController {
@RequiresPermissions("user")
@GetMapping("/sourceA")
public String sourceA(){
return "sourceA";
}
@RequiresPermissions("useradd")
@GetMapping("/sourceB")
public String sourceB(){
return "sourceB";
}
}
🍃com.dyl.shirocsdn.shiro.controller.UserController.java
package com.dyl.shirocsdn.controller;
import com.dyl.shirocsdn.entity.ReturnEntity;
import com.dyl.shirocsdn.entity.User;
import com.dyl.shirocsdn.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.sql.Timestamp;
import java.util.List;
/**
* @author dyl
* @description:
* @date 2021/7/30 0030 下午 21:59
*/
@RestController
@RequestMapping("/api/user")
public class UserController {
@Autowired
UserService userService;
@GetMapping("/userlist")
public List<User> userList(){
return userService.selectAllUser();
}
@PostMapping("/register")
public ReturnEntity addUser(@Validated User user) throws Exception {
user.setUpdate_time(new Timestamp(System.currentTimeMillis()) + "");
return userService.addUser(user);
}
@PostMapping("/login")
public ReturnEntity loginUser(String user_name, String user_pass) throws Exception {
return userService.loginUser(user_name, user_pass);
}
@GetMapping("/loginout")
public ReturnEntity loginOut(){
return userService.userLoginOut();
}
}
(四)测试
1、注册测试
💥 该测试目的:测试参数校验、注册接口
测试一:
POST: http://localhost:8080/api/user/register?user_name=usertest2&user_role=user&user_pass=123456&user_tel=1217059
user_name=usertest2
user_role=user
user_pass=123456
user_tel=1217059
result
:说明参数校验模块是起作用的
Field error in object 'user' on field 'user_tel': rejected value [1217059]; codes [Pattern.user.user_tel,Pattern.user_tel,Pattern.java.lang.String,Pattern]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [user.user_tel,user_tel]; arguments []; default message [user_tel],[Ljavax.validation.constraints.Pattern$Flag;@27a34635,^1([358][0-9]|4[579]|66|7[0135678]|9[89])[0-9]{8}$]; default message [电话号码不合法]]
测试二:
POST: http://localhost:8080/api/user/register?user_name=usertest2&user_role=user&user_pass=123456&user_tel=1217059
user_name=usertest2
user_role=user
user_pass=123456
user_tel=17754017055
result
{
"code": 200,
"msg": "创建用户成功",
"data": ""
}
2、登陆认证测试
💥 该测试目的:测试认证接口正确与否
测试一:
POST: http://localhost:8080/api/user/login?user_name=usertest&user_pass=123456
user_name=usertest
user_pass=123456
result
{
"code": 200,
"msg": "OK",
"data": "登陆授权成功"
}
3、授权测试
💥 该测试目的:测试授权模块能够从数据库中拿到用户权限,并查看根据权限访问接口功能正确与否
两个资源测试接口:
@RequestMapping("/api/res")
public class SourceController {
@RequiresPermissions("user")
@GetMapping("/sourceA")
public String sourceA(){
return "sourceA";
}
@RequiresPermissions("useradd")
@GetMapping("/sourceB")
public String sourceB(){
return "sourceB";
}
}
**测试一:**在2步骤登陆之后,再访问sourceA接口测试授权
GET: http://localhost:8080/api/res/sourceA
result
sourceA
**测试二:**访问sourceB接口测试授权,
GET: http://localhost:8080/api/res/sourceB
result
{
"timestamp": "2021-08-01T02:21:07.040+00:00",
"status": 500,
"error": "Internal Server Error",
"path": "/api/res/sourceB"
}
分析:
可以看出/sourceB
没有权限useradd
,控制台也会给出无权限错误。
console
2021-08-01 10:21:03.649 INFO 7600 --- [nio-8080-exec-4] c.d.s.shiro.realms.CustomerRealm : usertest的权限有:[user, usertest]
org.apache.shiro.authz.AuthorizationException: Not authorized to invoke method: public java.lang.String com.dyl.shirocsdn.controller.SourceController.sourceB()
4、缓存测试
💥 该测试目的:查看缓存是否生效
测试一:连续点击三次登陆,查看控制台输出情况
POST: http://localhost:8080/api/user/login?user_name=usertest&user_pass=123456
user_name=usertest
user_pass=123456
console
2021-08-01 10:29:46.468 INFO 2364 --- [nio-8080-exec-2] o.a.shiro.cache.ehcache.EhCacheManager : Cache with name 'authenticationCache' does not yet exist. Creating now.
2021-08-01 10:29:46.469 INFO 2364 --- [nio-8080-exec-2] o.a.shiro.cache.ehcache.EhCacheManager : Added EhCache named [authenticationCache]
2021-08-01 10:29:46.501 INFO 2364 --- [nio-8080-exec-2] com.zaxxer.hikari.HikariDataSource : shirocsdn - Starting...
2021-08-01 10:29:46.748 INFO 2364 --- [nio-8080-exec-2] com.zaxxer.hikari.HikariDataSource : shirocsdn - Start completed.
2021-08-01 10:29:46.761 DEBUG 2364 --- [nio-8080-exec-2] c.d.s.m.UserMapper.selectUserByName : ==> Preparing: select * from user where user_name = ?
2021-08-01 10:29:46.783 DEBUG 2364 --- [nio-8080-exec-2] c.d.s.m.UserMapper.selectUserByName : ==> Parameters: usertest(String)
2021-08-01 10:29:46.802 DEBUG 2364 --- [nio-8080-exec-2] c.d.s.m.UserMapper.selectUserByName : <== Total: 1
2021-08-01 10:29:46.834 INFO 2364 --- [nio-8080-exec-2] c.d.s.service.impl.UserServiceImpl : usertest 认证成功
2021-08-01 10:29:47.908 INFO 2364 --- [nio-8080-exec-3] c.d.s.service.impl.UserServiceImpl : usertest 认证成功
2021-08-01 10:29:50.663 INFO 2364 --- [nio-8080-exec-4] c.d.s.service.impl.UserServiceImpl : usertest 认证成功
- nio-8080-exec-2是第一次认证授权
- 缓存中查询结果:Cache with name ‘authenticationCache’ does not yet exist. Creating now.
- 缓存中没有,查询数据库并向缓存中添加:Added EhCache named [authenticationCache]
- nio-8080-exec-3是第二次认证
- nio-8080-exec-4是第三次认证
可以发现没有从数据库中取数据,直接缓存中有,认证成功
5、登出测试
💥 该测试目的:测试
测试一:
GET: http://localhost:8080/api/user/loginout
result
{
"code": 200,
"msg": "OK",
"data": "登出成功"
}
三、项目优化
💥 1、集成swagger端口调试;
💥 2、集成全局统一异常处理;
💥 3、使用Reis作为Cahcer、Session缓存实现
(一)Swagger端口调试
🍃pom.xml
:添加依赖
<!-- 优化:swagger2-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.6.1</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.6.1</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
<!-- 优化:swagger2-->
🍃com.dyl.shirocsdn.config.Swagger2.java
:配置swagger
package com.dyl.shirocsdn.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
/**
* @Author: dyl
* @Data: 2021/7/19
* @Description: 配置swagger2
*/
@Configuration
@EnableSwagger2
public class Swagger2 extends WebMvcConfigurationSupport {
@Bean
public Docket docket(){
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("com.dyl.shirocsdn.controller"))
.paths(PathSelectors.any())
.build();
}
public ApiInfo apiInfo(){
return new ApiInfoBuilder()
.title("用户权限系统API调试")
.description("用restful风格写接口")
.termsOfServiceUrl("")
.version("1.0")
.build();
}
}
访问:http://localhost:8080/swagger-ui.html
💥问题:swagger ui访问可能是404,这是因为资源访问被Shiro拦截器拦截,尽心如下设置。
🍃com.dyl.shirocsdn.interceptor.SourceAccessInterceptor.java
:资源访问拦截器
package com.dyl.shirocsdn.interceptor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @Des: url资源拦截器
* @author Administrator
*/
@Slf4j
public class SourceAccessInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
log.info("{} 进入拦截器了", request.getRequestURI());
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
log.info("{} 走出拦截器", request.getRequestURI());
}
}
🍃com.dyl.shirocsdn.config.InterceptorTrainConfigurer.java
:swagger资源访问放行
package com.dyl.shirocsdn.config;
import com.dyl.shirocsdn.interceptor.SourceAccessInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
/**
* @author Administrator
*/
@Configuration
public class InterceptorTrainConfigurer extends WebMvcConfigurationSupport {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new SourceAccessInterceptor())
.excludePathPatterns("/swagger-resources/**", "/webjars/**", "/v2/**", "/swagger-ui.html/**", "/api/test/hello")
.addPathPatterns("/**");
}
/**
* 解决swagger-ui.html 404无法访问的问题
*/
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
// 解决静态资源无法访问
registry.addResourceHandler("/**")
.addResourceLocations("classpath:/static/");
// 解决swagger无法访问
registry.addResourceHandler("/swagger-ui.html")
.addResourceLocations("classpath:/META-INF/resources/");
// 解决swagger的js文件无法访问
registry.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/");
}
}
访问:http://localhost:8080/swagger-ui.html
(二)全局统一异常处理
💥 目的:在使用Spring MVC进行开发时,总是要对系统异常和用户的异常行为进行处理,以提供给用户友好的提示,也可以提高系统的安全性。
🍃com.dyl.shirocsdn.entity.CodeMsgException.java
package com.dyl.shirocsdn.entity;
import java.io.Serializable;
/**
* @Author: dyl
* @Data: 2021/7/17
* @Description: 异常返回信息
*
*/
public class CodeMsgException implements Serializable {
private int code;
private String msg;
//通用异常
public static CodeMsgException SUCCESS = new CodeMsgException(0, "success");
public static CodeMsgException SERVER_ERROR = new CodeMsgException(500100, "服务端异常");
//注意 %s ,格式化字符串
public static CodeMsgException SERVER_BIND_ERROR = new CodeMsgException(500101, "服务端绑定异常:%s");
//登录模块 5002XX
public static CodeMsgException MSG_NAME_IS_EMPTY = new CodeMsgException(500200, "用户名不能为空!");
public static CodeMsgException MSG_PASSWORD_IS_EMPTY = new CodeMsgException(500201, "密码不能为空!");
public static CodeMsgException MSG_MOBILE_ERROR = new CodeMsgException(500202, "手机号格式不正确!");
public static CodeMsgException MSG_MOBILE_IS_EMPTY = new CodeMsgException(500203, "手机号不能为空!");
public static CodeMsgException MSG_MOBILE_NOT_EXIST = new CodeMsgException(500204, "手机号不存在!");
public static CodeMsgException MSG_PASSWORD_ERROR = new CodeMsgException(500205, "密码错误!");
//权限模块 5003XX
public static CodeMsgException MSG_PERMISSION_NEED_ADMIN = new CodeMsgException(500301, "需要管理员权限!");
private CodeMsgException(int code, String msg) {
this.code = code;
this.msg = msg;
}
public CodeMsgException fillArgs(Object ... args){
int code=this.code;
String message=String.format(msg,args);
return new CodeMsgException(code,message);
}
public int getCode() {
return code;
}
public String getMsg() {
return msg;
}
}
🍃com.dyl.shirocsdn.entity.GlobalException.java
package com.dyl.shirocsdn.entity;
import java.io.Serializable;
/**
* @Author: dyl
* @Data: 2021/7/17
* @Description: 全局异常类
*
*/
public class GlobalException extends RuntimeException implements Serializable {
private CodeMsgException codeMsgException;
public GlobalException(CodeMsgException codeMsgException){
super(codeMsgException.toString());
this.codeMsgException = codeMsgException;
}
public CodeMsgException getCodeMsg() {
return codeMsgException;
}
}
🍃com.dyl.shirocsdn.interceptor.GlobalExceptionHandler.java
:根据异常的种类返回给用户对应的错误编码
package com.dyl.shirocsdn.interceptor;
import com.dyl.shirocsdn.entity.CodeMsgException;
import com.dyl.shirocsdn.entity.GlobalException;
import org.springframework.validation.BindException;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
/**
* @Author: dyl
* @Data: 2021/7/17
* @Description: 异常拦截器,处理系统中出现的异常,或者是自定义异常
*
*/
@ControllerAdvice
@ResponseBody
public class GlobalExceptionHandler {
@ExceptionHandler(value = Exception.class)
public CodeMsgException exceptionHandler(HttpServletRequest request, Exception e) {
// 本地自定义异常处理
if(e instanceof GlobalException){
return ((GlobalException) e).getCodeMsg();
}
//绑定异常是需要明确提示给用户的
else if (e instanceof BindException) {
BindException exception = (BindException) e;
List<ObjectError> errors = exception.getAllErrors();
//获取自错误信息
String msg = errors.get(0).getDefaultMessage();
//将具体错误信息设置到CodeMsg中返回
return CodeMsgException.SERVER_BIND_ERROR.fillArgs(msg);
}
// 系统异常处理
return CodeMsgException.SERVER_BIND_ERROR.fillArgs(e.getCause() + e.getMessage());
}
}
(三)Reis作为Cahcer、Session缓存实现
🍃com.dyl.shirocsdn.shiro.session.ShiroSessionDAO.java
:配置session
package com.dyl.shirocsdn.shiro.session;
import com.dyl.shirocsdn.utils.ApplicationContextUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.UnknownSessionException;
import org.apache.shiro.session.mgt.eis.AbstractSessionDAO;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.stereotype.Repository;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.io.Serializable;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
/**
* @author dyl
* @data: 2021/7/29
*/
@Slf4j
@Repository
public class ShiroSessionDAO extends AbstractSessionDAO {
private static final String SESSION_REDIS_KEY_PREFIX = "session:";
@Override
protected Serializable doCreate(Session session) {
Serializable sessionId = generateSessionId(session);
assignSessionId(session, sessionId);
getRedisTemplate().boundValueOps(SESSION_REDIS_KEY_PREFIX + session.getId().toString()).set(session);
return sessionId;
}
@Override
public void update(Session session) throws UnknownSessionException {
getRedisTemplate().boundValueOps(SESSION_REDIS_KEY_PREFIX + session.getId().toString()).set(session);
}
@Override
public void delete(Session session) {
getRedisTemplate().delete(SESSION_REDIS_KEY_PREFIX + session.getId().toString());
HttpServletRequest request = getRequest();
// 一定要进行空值判断,因为SessionValidationScheduler的线程也会调用这个方法,而在那个线程中是不存在Request对象的
if (request != null) {
request.removeAttribute(session.getId().toString());
}
}
@Override
protected Session doReadSession(Serializable sessionId) {
HttpServletRequest request = getRequest();
if (request != null) {
Session sessionObj = (Session) request.getAttribute(sessionId.toString());
if (sessionObj != null) {
return sessionObj;
}
}
Session session = (Session) getRedisTemplate().boundValueOps(SESSION_REDIS_KEY_PREFIX + sessionId).get();
if (session != null && request != null) {
request.setAttribute(sessionId.toString(), session);
}
return session;
}
@Override
public Collection<Session> getActiveSessions() {
Set<String> keys = getRedisTemplate().keys(SESSION_REDIS_KEY_PREFIX + "*");
if (keys != null && !keys.isEmpty()) {
List<Object> sessions = getRedisTemplate().opsForValue().multiGet(keys);
if (sessions != null) {
return sessions.stream().map(o -> (Session) o).collect(Collectors.toList());
}
}
return Collections.emptySet();
}
private HttpServletRequest getRequest() {
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
return requestAttributes != null ? requestAttributes.getRequest() : null;
}
/**
* 序列化
* @return
*/
public RedisTemplate getRedisTemplate(){
RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtils.getBean("redisTemplate");
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(new StringRedisSerializer());
return redisTemplate;
}
}
🍃com.dyl.shirocsdn.shiro.cache.RedisCache.java
:配置cache
package com.dyl.shirocsdn.shiro.cache;
import com.dyl.shirocsdn.salt.NewSimpleAuthenticationInfo;
import com.dyl.shirocsdn.utils.ApplicationContextUtils;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.util.Collection;
import java.util.Set;
/**
* @author Administrator
* @data: 2021/7/29
*/
@Slf4j
/**
* 自定义redis缓存的实现
*/
public class RedisCache<k, v> implements Cache<k, v> {
private String cacheName;
public RedisCache(){}
/**
* 有参构造器,获取定义的cacheName
* @param cacheName
*/
public RedisCache(String cacheName){
this.cacheName = cacheName;
}
@Override
@JsonIgnoreProperties(ignoreUnknown = true)
public v get(k k) throws CacheException {
log.info("=========redis cache get=============");
Object v1 = getRedisTemplate().opsForHash().get(this.cacheName, k.toString());
log.info("get key : " + k);
return (v) v1;
}
/**
* 存储key-value,存储的方式是,用cacheName做为hash数据结构中的key
* @param k
* @param v
* @return
* @throws CacheException
*/
@Override
public v put(k k, v v) throws CacheException {
log.info("put key : " + k);
log.info("put value : " + v.getClass().getName());
if(v.getClass().getName() == "org.apache.shiro.authc.SimpleAuthenticationInfo"){
NewSimpleAuthenticationInfo newSimpleAuthenticationInfo = new NewSimpleAuthenticationInfo(((SimpleAuthenticationInfo) v).getCredentials(), ((SimpleAuthenticationInfo) v).getCredentialsSalt());
getRedisTemplate().opsForHash().put(this.cacheName, k.toString(), newSimpleAuthenticationInfo);
}else{
getRedisTemplate().opsForHash().put(this.cacheName, k.toString(), v);
}
return null;
}
@Override
public v remove(k k) throws CacheException {
log.info("=========redis cache remove=============");
return (v) getRedisTemplate().opsForHash().delete(this.cacheName, k.toString());
}
@Override
public void clear() throws CacheException {
getRedisTemplate().opsForHash().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);
}
/**
* 从容器中获取RedisTemplate,序列化操作
* @return
*/
public RedisTemplate getRedisTemplate(){
RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtils.getBean("redisTemplate");
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
// 这是关键点,对象序列化为json
redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
return redisTemplate;
}
}
🍃com.dyl.shirocsdn.shiro.cache.RedisCacheManager.java
:配置cache
package com.dyl.shirocsdn.shiro.cache;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheExjavaception;
import org.apache.shiro.cache.CacheManager;
/**
* @author dyl
*/
@Slf4j
public class RedisCacheManager implements CacheManager {
/**
* 参数:认证或者是授权缓存的统一名称
* cacheName 也是作为redis Hash存储结构的key
*/
@Override
public <K, V> Cache<K, V> getCache(String cacheName) throws CacheException {
log.info("缓存的名字是: " + cacheName);
return (Cache<K, V>) new RedisCache<K, V>(cacheName);
}
}
🍃com.dyl.shirocsdn.shiro.salt.NewSimpleAuthenticationInfo.java
:解决序列化错误问题
package com.dyl.shirocsdn.salt;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.util.ByteSource;
/**
* @author dyl
* @description:
* @date 2021/8/1 0001 下午 20:09
*/
public class NewSimpleAuthenticationInfo extends SimpleAuthenticationInfo {
public NewSimpleAuthenticationInfo(Object hashedCredentials, ByteSource credentialsSalt){
this.credentials = hashedCredentials;
this.credentialsSalt = credentialsSalt;
}
public NewSimpleAuthenticationInfo(){
}
}
🍃com.dyl.shirocsdn.shiro.config.ShiroConfig.java
:配置shiroConfig
package com.dyl.shirocsdn.config;
import com.dyl.shirocsdn.shiro.cache.RedisCacheManager;
import com.dyl.shirocsdn.shiro.realms.CustomerRealm;
import com.dyl.shirocsdn.shiro.session.ShiroSessionDAO;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.session.InvalidSessionException;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.mgt.SessionKey;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import java.util.HashMap;
import java.util.Map;
/**
* 用来整合shiro框架相关的配置类
* @Ling https://zhuanlan.zhihu.com/p/354205960
* @author dyl
*/
@Configuration
public class ShiroConfig {
/**
* 创建shiroFilter,负责拦截所有请求
* @param defaultWebSecurityManager
* @return
*/
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
//给filter设置安全管理器
shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);
//配置系统受限资源
//配置系统的公共资源
Map<String, String> map = new HashMap<>();
//请求这个资源需要认证和授权
map.put("/api/user/login", "anon");
map.put("/api/user/register", "anon");
map.put("/api/*", "authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
//默认认证界面路径(可不写)
shiroFilterFactoryBean.setLoginUrl("/login.html");
return shiroFilterFactoryBean;
}
/**
* 自定义sessionManager
*
* @return SessionManager
*/
@Bean
public SessionManager sessionManager() {
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager() {
@Override // 重写touch()方法,降低Session更新的频率
public void touch(SessionKey key) throws InvalidSessionException {
Session session = doGetSession(key);
if (session != null) {
long oldTime = session.getLastAccessTime().getTime();
session.touch(); // 更新访问时间
long newTime = session.getLastAccessTime().getTime();
if (newTime - oldTime > 300000) { // 如果两次访问的时间间隔大于5分钟,主动持久化Session
onChange(session);
}
}
}
};
// 绑定SessionDAO
sessionManager.setSessionDAO(new ShiroSessionDAO());
SimpleCookie sessionIdCookie = new SimpleCookie("sessionId");
sessionIdCookie.setPath("/");
// 设置 Cookie 的过期时间,秒为单位
sessionIdCookie.setMaxAge(8 * 60 * 60);
// 配置Cookie模版
sessionManager.setSessionIdCookie(sessionIdCookie);
sessionManager.setSessionIdUrlRewritingEnabled(false);
// session的失效时长,单位毫秒; 60 * 60 * 1000
sessionManager.setGlobalSessionTimeout( 60 * 60 * 1000);
// 是否开启会话验证器,默认是开启的
sessionManager.setSessionValidationSchedulerEnabled(true);
// 设置调度时间间隔,单位毫秒,默认就是1小时
sessionManager.setSessionValidationInterval(2 * 60 * 60 * 1000);
// 删除失效的session
sessionManager.setDeleteInvalidSessions(true);
return sessionManager;
}
/**
* 创建安全管理器
* @param realm
* @return
*/
@Bean
public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("getRealm") Realm realm){
DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
//给安全管理器设置
defaultWebSecurityManager.setRealm(realm);
// 加入session管理器
defaultWebSecurityManager.setSessionManager(sessionManager());
return defaultWebSecurityManager;
}
/**
* 创建自定义Realm
* @return
*/
@Bean
public Realm getRealm(){
CustomerRealm customerRealm = new CustomerRealm();
//修改凭证校验匹配器
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
//设置加密算法为md5
hashedCredentialsMatcher.setHashAlgorithmName("MD5");
//设置散列次数
hashedCredentialsMatcher.setHashIterations(1024);
customerRealm.setCredentialsMatcher(hashedCredentialsMatcher);
//开启cache缓存管理, 将自定义的RedisCacheManager注入进来
customerRealm.setCacheManager(new RedisCacheManager());
// 使用ehcache做本地缓存
// customerRealm.setCacheManager(new EhCacheManager());
//开启全局缓存
customerRealm.setCachingEnabled(true);
//开启认证缓存
customerRealm.setAuthenticationCachingEnabled(true);
//开启授权缓存
customerRealm.setAuthorizationCachingEnabled(true);
// 认证name
customerRealm.setAuthenticationCacheName("authenticationCache");
// 授权name
customerRealm.setAuthorizationCacheName("authorizationCache");
return customerRealm;
}
//注入权限管理
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
@Bean
@ConditionalOnMissingBean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator defaultAAP = new DefaultAdvisorAutoProxyCreator();
defaultAAP.setProxyTargetClass(true);
return defaultAAP;
}
}
(四)测试
第一次使用usertest账户去登陆,控制台输出如下:
- 首先,去缓存中查找
get key usertest
不存在; - 然后,去数据库中拿数据
- 最后,将数据存储到缓存中
2021-08-02 15:36:31.806 INFO 17968 --- [nio-8080-exec-7] c.d.s.i.SourceAccessInterceptor : /api/user/login 进入拦截器了
2021-08-02 15:36:31.811 INFO 17968 --- [nio-8080-exec-7] c.d.s.shiro.cache.RedisCacheManager : 缓存的名字是: authenticationCache
2021-08-02 15:36:31.811 INFO 17968 --- [nio-8080-exec-7] c.dyl.shirocsdn.shiro.cache.RedisCache : =========redis cache get=============
2021-08-02 15:36:34.366 INFO 17968 --- [nio-8080-exec-7] c.dyl.shirocsdn.shiro.cache.RedisCache : get key : usertest
2021-08-02 15:36:34.405 INFO 17968 --- [nio-8080-exec-7] com.zaxxer.hikari.HikariDataSource : shirocsdn - Starting...
2021-08-02 15:36:34.756 INFO 17968 --- [nio-8080-exec-7] com.zaxxer.hikari.HikariDataSource : shirocsdn - Start completed.
2021-08-02 15:36:34.770 DEBUG 17968 --- [nio-8080-exec-7] c.d.s.m.UserMapper.selectUserByName : ==> Preparing: select * from user where user_name = ?
2021-08-02 15:36:34.793 DEBUG 17968 --- [nio-8080-exec-7] c.d.s.m.UserMapper.selectUserByName : ==> Parameters: usertest(String)
2021-08-02 15:36:34.825 DEBUG 17968 --- [nio-8080-exec-7] c.d.s.m.UserMapper.selectUserByName : <== Total: 1
2021-08-02 15:36:34.837 INFO 17968 --- [nio-8080-exec-7] c.dyl.shirocsdn.shiro.cache.RedisCache : put key : usertest
2021-08-02 15:36:34.837 INFO 17968 --- [nio-8080-exec-7] c.dyl.shirocsdn.shiro.cache.RedisCache : put value : org.apache.shiro.authc.SimpleAuthenticationInfo
2021-08-02 15:36:35.015 INFO 17968 --- [nio-8080-exec-7] c.d.s.service.impl.UserServiceImpl : usertest 认证成功
2021-08-02 15:36:35.029 INFO 17968 --- [nio-8080-exec-7] c.d.s.i.SourceAccessInterceptor : /api/user/login 走出拦截器
查看第一次登陆后,Redis缓存中存的认证和session序列化后的对象:
127.0.0.1:6379> keys *
1) "session:49bd5e7b-6e25-4b80-bd93-06a35441cc47"
2) "authenticationCache"
同一个账号第二次、第三次登陆
- 第二次、第三次usertest账号登陆
- 从Redis缓存中直接拿到了数据,就不去查询数据库了,缓解了数据库的压力
2021-08-02 15:37:59.320 INFO 17968 --- [io-8080-exec-10] c.d.s.i.SourceAccessInterceptor : /api/user/login 进入拦截器了
2021-08-02 15:37:59.320 INFO 17968 --- [io-8080-exec-10] c.dyl.shirocsdn.shiro.cache.RedisCache : =========redis cache get=============
2021-08-02 15:37:59.617 INFO 17968 --- [io-8080-exec-10] c.dyl.shirocsdn.shiro.cache.RedisCache : get key : usertest
2021-08-02 15:37:59.626 INFO 17968 --- [io-8080-exec-10] c.d.s.i.SourceAccessInterceptor : /api/user/login 走出拦截器
2021-08-02 15:38:05.966 INFO 17968 --- [nio-8080-exec-1] c.d.s.i.SourceAccessInterceptor : /api/user/login 进入拦截器了
2021-08-02 15:38:05.966 INFO 17968 --- [nio-8080-exec-1] c.dyl.shirocsdn.shiro.cache.RedisCache : =========redis cache get=============
2021-08-02 15:38:05.972 INFO 17968 --- [nio-8080-exec-1] c.dyl.shirocsdn.shiro.cache.RedisCache : get key : usertest
2021-08-02 15:38:05.975 INFO 17968 --- [nio-8080-exec-1] c.d.s.i.SourceAccessInterceptor : /api/user/login 走出拦截器
授权
- 上述步骤只完成了认证与会话管理,接下来进行授权
- 访问:/api/res/sourceA
- 首先,会去缓存中找数据
get key : usertest
,缓存中不存在 - 然后,去数据库中拿数据,并存储到缓存中,将权限赋给用户
usertest的权限有:[user, usertest]
2021-08-02 15:40:33.614 INFO 6068 --- [nio-8080-exec-4] c.d.s.i.SourceAccessInterceptor : /api/res/sourceA 进入拦截器了
2021-08-02 15:40:33.622 INFO 6068 --- [nio-8080-exec-4] c.dyl.shirocsdn.shiro.cache.RedisCache : =========redis cache get=============
2021-08-02 15:40:33.624 INFO 6068 --- [nio-8080-exec-4] c.dyl.shirocsdn.shiro.cache.RedisCache : get key : usertest
2021-08-02 15:40:33.624 INFO 6068 --- [nio-8080-exec-4] c.d.s.shiro.realms.CustomerRealm : ====授权开始===
2021-08-02 15:40:33.626 DEBUG 6068 --- [nio-8080-exec-4] c.d.s.m.UserMapper.selectUserByName : ==> Preparing: select * from user where user_name = ?
2021-08-02 15:40:33.627 DEBUG 6068 --- [nio-8080-exec-4] c.d.s.m.UserMapper.selectUserByName : ==> Parameters: usertest(String)
2021-08-02 15:40:33.628 DEBUG 6068 --- [nio-8080-exec-4] c.d.s.m.UserMapper.selectUserByName : <== Total: 1
2021-08-02 15:40:33.631 DEBUG 6068 --- [nio-8080-exec-4] c.d.s.m.UserMapper.selectPermisByRole : ==> Preparing: SELECT permission_code FROM `permission` as t1 INNER JOIN role_permission as t2 on t1.permission_id = t2.permission_id INNER JOIN role as t3 on t3.role_id = t2.role_id where role_code = ?;
2021-08-02 15:40:33.632 DEBUG 6068 --- [nio-8080-exec-4] c.d.s.m.UserMapper.selectPermisByRole : ==> Parameters: user(String)
2021-08-02 15:40:33.677 DEBUG 6068 --- [nio-8080-exec-4] c.d.s.m.UserMapper.selectPermisByRole : <== Total: 2
2021-08-02 15:40:33.678 INFO 6068 --- [nio-8080-exec-4] c.d.s.shiro.realms.CustomerRealm : usertest的权限有:[user, usertest]
2021-08-02 15:40:33.678 INFO 6068 --- [nio-8080-exec-4] c.dyl.shirocsdn.shiro.cache.RedisCache : put key : usertest
2021-08-02 15:40:33.678 INFO 6068 --- [nio-8080-exec-4] c.dyl.shirocsdn.shiro.cache.RedisCache : put value : org.apache.shiro.authz.SimpleAuthorizationInfo
2021-08-02 15:40:33.694 INFO 6068 --- [nio-8080-exec-4] c.d.s.i.SourceAccessInterceptor : /api/res/sourceA 走出拦截器
- 访问:/api/res/sourceB
- 首先,会去缓存中找数据
get key : usertest
,缓存中直接命中 - 将权限赋给用户
usertest的权限有:[user, usertest]
sourceB
要求权限是useradd
,没有权限,返回如下,异常经过全局异常处理之后返回给前端{ "code": 500101, "msg": "服务端绑定异常:org.apache.shiro.authz.AuthorizationException: Not authorized to invoke method: public java.lang.String com.dyl.shirocsdn.controller.SourceController.sourceB()Subject does not have permission [useradd]" }
2021-08-02 15:40:43.801 INFO 6068 --- [nio-8080-exec-5] c.d.s.i.SourceAccessInterceptor : /api/res/sourceB 进入拦截器了
2021-08-02 15:40:43.801 INFO 6068 --- [nio-8080-exec-5] c.dyl.shirocsdn.shiro.cache.RedisCache : =========redis cache get=============
2021-08-02 15:40:43.809 INFO 6068 --- [nio-8080-exec-5] c.dyl.shirocsdn.shiro.cache.RedisCache : get key : usertest
2021-08-02 15:40:43.816 INFO 6068 --- [nio-8080-exec-5] c.d.s.i.SourceAccessInterceptor : /api/res/sourceB 走出拦截器
Redis缓存中存储内容
HASH : authenticationCache
键名:usertest
键值:
{
"@class": "com.dyl.shirocsdn.salt.NewSimpleAuthenticationInfo",
"principals": null,
"credentials": "d246c655fbb39fde25f39b9282c2cda0",
"credentialsSalt": {
"@class": "com.dyl.shirocsdn.salt.MyByteSource",
"bytes": "T2xyQGZud1k=",
"empty": false
}
}
HASH : com.dyl.shirocsdn.shiro.realms.CustomerRealm.authorizationCache
键名:usertest
键值:
{
"@class": "org.apache.shiro.authz.SimpleAuthorizationInfo",
"roles": [
"java.util.HashSet",
[
"user"
]
],
"stringPermissions": [
"java.util.HashSet",
[
"usertest",
"user"
]
],
"objectPermissions": null
}
HASH : session:28188226-cc51-451a-9080-5ee92764c435
键名:usertest
键值:
\xAC\xED\x00\x05sr\x00*org.apache.shiro.session.mgt.SimpleSession\x9D\x1C\xA1\xB8\xD5\x8Cbn\x03\x00\x00xpw\x02\x00\xDBt\x00$28188226-cc51-451a-9080-5ee92764c435sr\x00\x0Ejava.util.Datehj\x81\x01KYt\x19\x03\x00\x00xpw\x08\x00\x00\x01{\x05\xCD\xF8\xq\x00~\x00\x04w\x19\x00\x00\x00\x00\x006\xEE\x80\x00\x0F0:0:0:0:0:0:0:1sr\x00\x11java.util.HashMap\x05\x07\xDA\xC1\xC3\x16`\xD1\x03\x00\x02F\x00\x0AloadFactorI\x00\x09thresholdxp?@\x00\x00\x00\x00\x00\x0Cw\x08\x00\x00\x00\x10\x00\x00\x00\x02t\x00Porg.apache.shiro.subject.support.DefaultSubjectContext_AUTHENTICATED_SESSION_KEYsr\x00\x11java.lang.Boolean\xCD r\x80\xD5\x9C\xFA\xEE\x02\x00\x01Z\x00\x05valuexp\x01t\x00Morg.apache.shiro.subject.support.DefaultSubjectContext_PRINCIPALS_SESSION_KEYsr\x002org.apache.shiro.subject.SimplePrincipalCollection\xA8\x7FX%\xC6\xA3\x08J\x03\x00\x01L\x00\x0FrealmPrincipalst\x00\x0FLjava/util/Map;xpsr\x00\x17java.util.LinkedHashMap4\xC0N\\x10l\xC0\xFB\x02\x00\x01Z\x00\x0BaccessOrderxq\x00~\x00\x05?@\x00\x00\x00\x00\x00\x0Cw\x08\x00\x00\x00\x10\x00\x00\x00\x01t\x00.com.dyl.shirocsdn.shiro.realms.CustomerRealm_0sr\x00\x17java.util.LinkedHashSet\xD8l\xD7Z\x95\xDD*\x1E\x02\x00\x00xr\x00\x11java.util.HashSet\xBAD\x85\x95\x96\xB8\xB74\x03\x00\x00xpw\x0C\x00\x00\x00\x10?@\x00\x00\x00\x00\x00\x01t\x00\x08usertestxx\x00w\x01\x01q\x00~\x00\x0Fxxx
四、Bug秃头调试
(一)Redis序列化错误
💥问题:认证redis缓存的时候,第一次登陆没问题,第二次登陆SimpleAuthenticationInfo
反序列化过程中无法识别部分属性。
**错误提示:**啥意思呢?就是它不认识其中的属性empty
。
org.springframework.data.redis.serializer.SerializationException: Could not read JSON: Unrecognized field "empty" (class org.apache.shiro.subject.SimplePrincipalCollection), not marked as ignorable (one known property: "realmNames"])
......
1、第一次点击登陆是没有问题的,并查看redis中的数据
# 因为我采用的序列化方式是:redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
# Redis中的数据存储格式为json格式
键名:admintest
键值:
{
"@class": "org.apache.shiro.authc.SimpleAuthenticationInfo",
"principals": {
"@class": "org.apache.shiro.subject.SimplePrincipalCollection",
"empty": false,
"primaryPrincipal": "admintest",
"realmNames": [
"java.util.LinkedHashMap$LinkedKeySet",
[
"com.example.domainsysadmin.shiro.realms.CustomerRealm_0"
]
]
},
"credentials": "61ca717a947acb9ae594d41188a77176",
"credentialsSalt": {
"@class": "com.example.domainsysadmin.salt.MyByteSource",
"bytes": "TkY0Jio3RFQ=",
"empty": false
}
}
2、删除没用的属性,测试
键名:admintest
键值:
{
"@class": "org.apache.shiro.authc.SimpleAuthenticationInfo",
"credentials": "61ca717a947acb9ae594d41188a77176",
"credentialsSalt": {
"@class": "com.example.domainsysadmin.salt.MyByteSource",
"bytes": "TkY0Jio3RFQ="
}
}
# 使用redis客户端,把数据改成这样,第二次点击登陆尽然就没问题了。MMP
3、问题定位:
基本上可以肯定问题是:SimplePrincipalCollection
反序列化问题,直接看看源码构造函数如下:
private Map<String, Set> realmPrincipals;
private transient String cachedToString;
public SimplePrincipalCollection() {
}
public SimplePrincipalCollection(Object principal, String realmName) {
if (principal instanceof Collection) {
addAll((Collection) principal, realmName);
} else {
add(principal, realmName);
}
}
public SimplePrincipalCollection(Collection principals, String realmName) {
addAll(principals, realmName);
}
public SimplePrincipalCollection(PrincipalCollection principals) {
addAll(principals);
}
这问题就来了:
"principals": {
"@class": "org.apache.shiro.subject.SimplePrincipalCollection",
"empty": false,
"primaryPrincipal": "admintest",
"realmNames": [
"java.util.LinkedHashMap$LinkedKeySet",
[
"com.example.domainsysadmin.shiro.realms.CustomerRealm_0"
]
]
}
# principals 这个 SimplePrincipalCollection 对象,怎么可能用上面的构造方法初始化,不可能正确?
# "empty" "primaryPrincipal" "realmNames" 这三个全是黑头户口。
# 解决:黑头户都不要
4、问题解决:
新建一个NewSimpleAuthenticationInfo
来继承SimpleAuthenticationInfo
,加一个构造函数:
package com.dyl.shirocsdn.salt;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.util.ByteSource;
/**
* @author dyl
* @description:
* @date 2021/8/1 0001 下午 20:09
*/
public class NewSimpleAuthenticationInfo extends SimpleAuthenticationInfo {
// 其他的参数不要
public NewSimpleAuthenticationInfo(Object hashedCredentials, ByteSource credentialsSalt){
this.credentials = hashedCredentials;
this.credentialsSalt = credentialsSalt;
}
public NewSimpleAuthenticationInfo(){
}
}
新建一个盐类MyByteSource
package com.dyl.shirocsdn.salt;
import org.apache.shiro.codec.Base64;
import org.apache.shiro.codec.CodecSupport;
import org.apache.shiro.codec.Hex;
import org.apache.shiro.util.ByteSource;
import java.io.File;
import java.io.InputStream;
import java.io.Serializable;
import java.util.Arrays;
/**
* @author dyl
* @description:
* @date 2021/7/31 0031 下午 19:59
*/
public class MyByteSource implements ByteSource,Serializable {
private byte[] bytes;
private String cachedHex;
private String cachedBase64;
private String empty; // 加一个无用的属性
//加入无参数构造方法实现序列化和反序列化
public MyByteSource(){
}
public void setBytes(byte[] bytes) {
this.bytes = bytes;
}
public MyByteSource(byte[] bytes) {
this.bytes = bytes;
}
public MyByteSource(char[] chars) {
this.bytes = CodecSupport.toBytes(chars);
}
public MyByteSource(String string) {
this.bytes = CodecSupport.toBytes(string);
}
public MyByteSource(ByteSource source) {
this.bytes = source.getBytes();
}
public MyByteSource(File file) {
this.bytes = (new BytesHelper()).getBytes(file);
}
public MyByteSource(InputStream stream) {
this.bytes = (new BytesHelper()).getBytes(stream);
}
public static boolean isCompatible(Object o) {
return o instanceof byte[] || o instanceof char[] || o instanceof String || o instanceof ByteSource || o instanceof File || o instanceof InputStream;
}
@Override
public byte[] getBytes() {
return this.bytes;
}
@Override
public boolean isEmpty() {
return this.bytes == null || this.bytes.length == 0;
}
@Override
public String toHex() {
if (this.cachedHex == null) {
this.cachedHex = Hex.encodeToString(this.getBytes());
}
return this.cachedHex;
}
@Override
public String toBase64() {
if (this.cachedBase64 == null) {
this.cachedBase64 = Base64.encodeToString(this.getBytes());
}
return this.cachedBase64;
}
@Override
public String toString() {
return this.toBase64();
}
@Override
public int hashCode() {
return this.bytes != null && this.bytes.length != 0 ? Arrays.hashCode(this.bytes) : 0;
}
@Override
public boolean equals(Object o) {
if (o == this) {
return true;
} else if (o instanceof ByteSource) {
ByteSource bs = (ByteSource)o;
return Arrays.equals(this.getBytes(), bs.getBytes());
} else {
return false;
}
}
private static final class BytesHelper extends CodecSupport {
private BytesHelper() {
}
public byte[] getBytes(File file) {
return this.toBytes(file);
}
public byte[] getBytes(InputStream stream) {
return this.toBytes(stream);
}
}
}
5、测试
连续登陆两次
2021-08-01 23:16:11.432 INFO 11796 --- [nio-8080-exec-1] c.d.s.shiro.cache.RedisCacheManager : 缓存的名字是: authenticationCache
2021-08-01 23:16:11.432 INFO 11796 --- [nio-8080-exec-1] c.dyl.shirocsdn.shiro.cache.RedisCache : =========redis cache get=============
2021-08-01 23:16:13.565 INFO 11796 --- [nio-8080-exec-1] c.dyl.shirocsdn.shiro.cache.RedisCache : get key : usertest
2021-08-01 23:16:13.593 INFO 11796 --- [nio-8080-exec-1] com.zaxxer.hikari.HikariDataSource : shirocsdn - Starting...
2021-08-01 23:16:13.818 INFO 11796 --- [nio-8080-exec-1] com.zaxxer.hikari.HikariDataSource : shirocsdn - Start completed.
2021-08-01 23:16:13.829 DEBUG 11796 --- [nio-8080-exec-1] c.d.s.m.UserMapper.selectUserByName : ==> Preparing: select * from user where user_name = ?
2021-08-01 23:16:13.851 DEBUG 11796 --- [nio-8080-exec-1] c.d.s.m.UserMapper.selectUserByName : ==> Parameters: usertest(String)
2021-08-01 23:16:13.926 DEBUG 11796 --- [nio-8080-exec-1] c.d.s.m.UserMapper.selectUserByName : <== Total: 1
2021-08-01 23:16:13.942 INFO 11796 --- [nio-8080-exec-1] c.dyl.shirocsdn.shiro.cache.RedisCache : put key : usertest
2021-08-01 23:16:13.942 INFO 11796 --- [nio-8080-exec-1] c.dyl.shirocsdn.shiro.cache.RedisCache : put value : org.apache.shiro.authc.SimpleAuthenticationInfo
2021-08-01 23:16:14.104 INFO 11796 --- [nio-8080-exec-1] c.d.s.service.impl.UserServiceImpl : usertest 认证成功
2021-08-01 23:16:16.878 INFO 11796 --- [nio-8080-exec-3] c.dyl.shirocsdn.shiro.cache.RedisCache : =========redis cache get=============
2021-08-01 23:16:16.953 INFO 11796 --- [nio-8080-exec-3] c.dyl.shirocsdn.shiro.cache.RedisCache : get key : usertest
2021-08-01 23:16:16.954 INFO 11796 --- [nio-8080-exec-3] c.d.s.service.impl.UserServiceImpl : usertest 认证成功
nio-8080-exec-3:第二次登陆直接从Redis中直接拿到了认证的SimpleAuthenticationInfo
对象
看一下Redis中的数据
HASH: authenticationCache
键名:admintest
键值:
{
"@class": "com.dyl.shirocsdn.salt.NewSimpleAuthenticationInfo",
"principals": null,
"credentials": "d246c655fbb39fde25f39b9282c2cda0",
"credentialsSalt": {
"@class": "com.dyl.shirocsdn.salt.MyByteSource",
"bytes": "T2xyQGZud1k=",
"empty": false
}
}
HASH: com.dyl.shirocsdn.shiro.realms.CustomerRealm.authorizationCache
键名:admintest
键值:
{
"@class": "org.apache.shiro.authz.SimpleAuthorizationInfo",
"roles": [
"java.util.HashSet",
[
"user"
]
],
"stringPermissions": [
"java.util.HashSet",
[
"usertest",
"user"
]
],
"objectPermissions": null
}