Java研学-Shiro安全框架(六)

八 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);
        }
    }
}
  • 12
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

泰勒疯狂展开

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值