八 SpringBoot集成Shiro使用Ehcache缓存
1 缓存流程图
2 集成EhCache
每当应用程序进行鉴权的时候,都会调用Realm中的doGetAuthorizationInfo来获取用户的角色信息/权限信息,这个方法是需要访问数据库的. 而用户的角色信息/权限信息基本上是不变的, 所以目前我们的程序是每次鉴权都需要访问数据库,而且返回的数据都是一样的.
因此可以集成EhCache,将角色信息/权限信息都缓存起来,只有用户第一次鉴权的时候才会查询数据库,后续的鉴权都直接从缓存中获取.
页面每有一个需要判断权限显示或操作的选项,就需要进行一次判断查询数据库,因此会出现访问一个页面判断几十次的情况,此时应将数据存入缓存,这样同一条数据只查询一次数据库,之后访问缓存即可
3 缓存淘汰策略
策略 | 说明 |
LRU | 默认,最近最少使用,距离现在最久没有使用的元素将被清出缓存 |
FIFO | 先进先出, 如果一个数据最先进入缓存中,则应该最早淘汰掉 |
LFU | 较少使用,意思是一直以来最少被使用的,缓存的元素有一个hit 属性(命中率),hit 值最小的将会被清出缓存 |
4 导入依赖
<!-- Shiro使用EhCache缓存框架(默认集成) -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>${shiro.version}</version>
</dependency>
5 在resource
目录下新建ehcache/ehcache-shiro.xml
文件
<?xml version="1.0" encoding="UTF-8"?>
<ehcache>
<defaultCache
maxElementsInMemory="1000"
eternal="false"
timeToIdleSeconds="600"
timeToLiveSeconds="600"
memoryStoreEvictionPolicy="LRU">
</defaultCache>
</ehcache>
6 配置属性说明
参数 | 说明 |
maxElementsInMemory | 缓存对象最大个数 |
eternal | 对象是否永久有效,一但设置了,timeout 将不起作用。(此后被淘汰的数据将持久化到磁盘中) |
timeToIdleSeconds | 对象空闲时间,指对象在多长时间没有被访问就会失效(单位:秒)。仅当 eternal=false 对象不是永久有效时使用,可选属性,默认值是 0,也就是可闲置时间无穷大。 |
timeToLiveSeconds | 对象存活时间,指对象从创建到失效所需要的时间(单位:秒)。仅当 eternal=false 对象不是永久有效时使用,默认是 0,也就是对象存活时间无穷大。 |
memoryStoreEvictionPolicy | 当达到 maxElementsInMemory 限制时,Ehcache 将会根据指定的策略去清理内存。 |
7 ShiroConfig中添加缓存管理器
生成 EhCacheManager 的 Bean,设置给Realm(Realm本身在安全管理器中)即可
@Configuration
public class ShiroConfig {
// 略
// 缓存管理器
@Bean
public EhCacheManager ehCacheManager(){
EhCacheManager ehCacheManager=new EhCacheManager();
// 读取配置文件中的缓存规则
ehCacheManager.setCacheManagerConfigFile("classpath:ehcache/ehcache-shiro.xml");
return ehCacheManager;
}
// 配置数据源
@Bean
public EmployeeRealm employeeRealm(EhCacheManager ehCacheManager){
EmployeeRealm realm=new EmployeeRealm();
//设置缓存管理器
realm.setCacheManager(ehCacheManager);
return realm;
}
}
九 SpringBoot集成Shiro完成加盐加密
1 数据库表加盐
此时password中存放密文密码
CREATE TABLE `employee` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`username` varchar(255) DEFAULT NULL,
`name` varchar(255) DEFAULT NULL,
`password` varchar(255) DEFAULT NULL,
`email` varchar(255) DEFAULT NULL,
`age` int(11) DEFAULT NULL,
`admin` bit(1) DEFAULT NULL,
`dept_id` bigint(20) DEFAULT NULL,
`salt` varchar(20) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8;
2 员工实体类加盐
@Data
public class Employee {
private Long id;
private String username;
private String name;
private String password;
private String email;
private Integer age;
private boolean admin;
private Department dept;
// 添加盐字段
private String salt;
}
3 员工Mapper映射加盐
<?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="cn.tj.mapper.EmployeeMapper" >
<resultMap id="BaseResultMap" type="cn.tj.domain.Employee" >
<id column="id" property="id" />
<result column="username" property="username" />
<result column="name" property="name" />
<result column="password" property="password" />
<result column="email" property="email" />
<result column="age" property="age" />
<result column="admin" property="admin" />
<result column="salt" property="salt" />
<association columnPrefix="d_" property="dept" javaType="department">
<result column="id" property="id" />
<result column="name" property="name" />
<result column="sn" property="sn" />
</association>
</resultMap>
<delete id="deleteByPrimaryKey" >
delete from employee
where id = #{id}
</delete>
<delete id="deleteRelation">
delete from employee_role where employee_id = #{employeeId}
</delete>
<insert id="insert" useGeneratedKeys="true" keyProperty="id" >
insert into employee (username, name, password, email, age, admin, dept_id,salt
)
values (#{username}, #{name}, #{password}, #{email}, #{age}, #{admin}, #{dept.id},#{salt}
)
</insert>
<insert id="insertRelationBatch">
insert into employee_role(employee_id, role_id) values
<foreach collection="roleIds" separator="," item="roleId">
(#{employeeId},#{roleId})
</foreach>
</insert>
<update id="updateByPrimaryKey" >
update employee
set
name = #{name},
email = #{email},
age = #{age},
admin = #{admin},
dept_id = #{dept.id}
where id = #{id}
</update>
<select id="selectByPrimaryKey" resultMap="BaseResultMap" >
select e.id, e.username, e.name, e.password, e.email, e.age, e.admin,d.id d_id,d.name d_name,d.sn d_sn,e.salt
from employee e left join department d on e.dept_id = d.id
where e.id = #{id}
</select>
<select id="selectAll" resultMap="BaseResultMap" >
select id, username, name, password, email, age, admin, dept_id,salt
from employee
</select>
<sql id="where_sql">
<where>
<if test="keyword != null and keyword != ''">
and (e.name like concat('%',#{keyword},'%') or e.email like concat('%',#{keyword},'%'))
</if>
<if test="deptId != null">
and e.dept_id = #{deptId}
</if>
</where>
</sql>
<select id="selectForList" resultMap="BaseResultMap">
select e.id, e.username, e.name, e.password, e.email, e.age, e.admin,d.id d_id,d.name d_name,d.sn d_sn,e.salt
from employee e left join department d on e.dept_id = d.id
<include refid="where_sql"/>
</select>
<select id="checkUsername" resultType="java.lang.Integer">
select count(*) from employee where username=#{username}
</select>
<select id="getByUsernameAndPassword" resultMap="BaseResultMap">
select e.id, e.username, e.name, e.password, e.email, e.age, e.admin,d.id d_id,d.name d_name,d.sn d_sn,e.salt
from employee e left join department d on e.dept_id = d.id
where username=#{username} and password=#{password}
</select>
<select id="getByUsername" resultMap="BaseResultMap">
select e.id, e.username, e.name, e.password, e.email, e.age, e.admin,d.id d_id,d.name d_name,d.sn d_sn,e.salt
from employee e left join department d on e.dept_id = d.id
where username=#{username}
</select>
</mapper>
4 EmployeeRealm 认证加盐
public class EmployeeRealm extends AuthorizingRealm {
@Autowired
private IEmployeeService employeeService;
@Autowired
private IPermissionService permissionService;
@Autowired
private IRoleService roleService;
//授权方法
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
Employee currentEmployee= (Employee) principalCollection.getPrimaryPrincipal();
SimpleAuthorizationInfo info=new SimpleAuthorizationInfo();
if(currentEmployee.isAdmin()){
List<Role> roles=roleService.listAll();
for(Role role:roles){
info.addRole(role.getSn());
}
info.addStringPermission("*:*");
}else{
List<Role> roleList=roleService.queryByEmployeeId(currentEmployee.getId());
for(Role role:roleList){
info.addRole(role.getSn());
}
//查询该用户的权限集合
List<String> permissionList=permissionService.queryByEmployeeId(currentEmployee.getId());
info.addStringPermissions(permissionList);
}
return info;
}
//认证方法
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
// 根据token获取用户名
String username = (String) authenticationToken.getPrincipal();
// 根据用户名查询用户
Employee currentEmployee=employeeService.getByUsername(username);
// 根据查询结果返回对应数据
if(currentEmployee==null){
return null;
}
// 将盐返回
return new SimpleAuthenticationInfo(currentEmployee,currentEmployee.getPassword()
, ByteSource.Util.bytes(currentEmployee.getSalt()),getName());
}
}
5 设置凭证匹配器 – ShiroConfig
@Configuration
public class ShiroConfig {
// 略
// 设置凭证匹配器
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher(){
// 创建对象设置规则为MD5
HashedCredentialsMatcher matcher=new HashedCredentialsMatcher("MD5");
// 设置加密次数为3次
matcher.setHashIterations(3);
return matcher;
}
// 将凭证匹配器设置给数据源
//数据源
@Bean
public EmployeeRealm employeeRealm(EhCacheManager ehCacheManager,HashedCredentialsMatcher hashedCredentialsMatcher){
EmployeeRealm realm=new EmployeeRealm();
//设置缓存管理器
realm.setCacheManager(ehCacheManager);
//设置凭证匹配器(加密)
realm.setCredentialsMatcher(hashedCredentialsMatcher);
return realm;
}
}
6 修改添加操作
此时应对明文密码进行加密后存储
@Service
public class EmployeeServiceImpl implements IEmployeeService {
@Autowired
private EmployeeMapper employeeMapper;
// 略
@Override
@Transactional
public void save(Employee employee, Long[] roleIds) {
//对明文密码进行加密
String salt= UUID.randomUUID().toString().substring(0,4);
// 明文密码,盐,加密次数
Md5Hash hash=new Md5Hash(employee.getPassword(),salt,3);
// 设置密文密码
employee.setPassword(hash.toString());
// 设置盐
employee.setSalt(salt);
//新增员工的数据
employeeMapper.insert(employee);
//维护中间表的关系
if(!employee.isAdmin()&&roleIds!=null&&roleIds.length>0){
employeeMapper.insertRelationBatch(employee.getId(),roleIds);
}
}
}