【爆肝推荐】手摸手带你做后台管理项目(第四章)整合redis添加shiro权限

本文介绍了如何在Spring Boot项目中整合Redis和Shiro实现动态权限管理,包括创建数据库表、配置授权器、开启注解权限校验、使用Redis缓存权限信息以及动态目录的实现。通过动态更新数据库数据,实现了角色和权限的动态绑定,以及目录菜单的动态加载。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前情提要

这一篇是关于动态权限和动态目录的,
shiro授权器在碰到权限的校验时候才会去触发,这个时候就可以从数据库中获取到用户关联的角色
角色绑定的权限,大概就如下图了
在这里插入图片描述

有兴趣可以了解一下RBAC,大概就是如下的一个关系
在这里插入图片描述

动态目录就更简单了,用户关联的角色,角色所拥有的目录,这个就是展示的目录了,修改数据库数据就可达到动态的目的。

正文开始

设计五个表,管理员表(也就是用户表,已存在)角色表目录表用户角色表角色目录表,如果没懂这些关联,可以看一下图片,图片最下方标记了关系,希望能看懂
在这里插入图片描述
创建数据库sys_menu
在这里插入图片描述
创建sys_role
在这里插入图片描述
创建sys_role_menu
在这里插入图片描述
创建sys_user_role
在这里插入图片描述

记得创建对应的controllerservicedao以及xmlentity

太多了,就不一一创建展示了,记得servicedao集成mybatis plus提供的类

修改上篇没动的授权器UserRealm
  @Autowired
    private SysMenuService menuService;


  /**
     * 授权
     * @param principalCollection
     * @return
     */
    @Override
   	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
   
        System.out.println("这里是授权");
        UserEntity userEntity = (UserEntity) principalCollection.getPrimaryPrincipal();
        Integer id = userEntity.getId();
        //设置id为空则拥有所有权限,sql中设置了id为空则查询所有权限
        List<SysMenuEntity> menuList = menuService.findByUserId(id);
        //转存set是为了去重,保证权限唯一
        Set<String> collect = menuList.stream().map(SysMenuEntity::getPerms).collect(Collectors.toSet());
        //所有权限
        Set<String> perms = new HashSet<>();
        collect.stream().forEach(y -> {
   
            //防止空的造成异常
            if(!StringUtils.isEmpty(y)){
   
                //存放无论是否有多个或者单个,直接变成数组,更加清晰
                /**
                 * 查询的权限中含有sys:user:info,sys:user:list
                 * 存入的依旧是set,防止权限重复,直接切割分隔的权限
                 */
                perms.addAll(Arrays.asList(y.split(",")));
            }

        });
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        //此处是放入权限中,不是role角色中
        simpleAuthorizationInfo.setStringPermissions(perms);
        return simpleAuthorizationInfo;
    }
SysMenuService
	//用户查询关联的目录
 List<SysMenuEntity> findByUserId(Integer userId);

SysMenuServiceImpl

 @Autowired
    private SysMenuDao menuDao;

    @Override
    public List<SysMenuEntity> findByUserId(Integer userId) {
   
        return menuDao.selectByUserId(userId);
    }

SysMenuDao

 List<SysMenuEntity> selectByUserId(@Param("userId") Integer userId);

SysMenuDao.xml

    <select id="selectByUserId" resultType="com.macro.entity.SysMenuEntity">
        select m.* from sys_user_role ur
        LEFT JOIN sys_role_menu rm on rm.role_id = ur.role_id
        LEFT JOIN sys_menu m on m.id = rm.menu_id
        where 1=1
        <if test="userId != null and userId != ''">
            and   ur.user_id = #{userId}
        </if>
        GROUP BY m.id
    </select>
开启注解,校验权限 ShiroConfig
 /**
     * @Title: authorizationAttributeSourceAdvisor
     * @Description:开启shiro提供的权限相关的注解
     * @param defaultWebSecurityManager
     * @return AuthorizationAttributeSourceAdvisor
     **/
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager defaultWebSecurityManager) {
   
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(defaultWebSecurityManager);
        return authorizationAttributeSourceAdvisor;
    }
sysUserController中加入权限校验

添加的权限Permissions,授权器添加的也是权限,并非角色
注解没注意看,结果加错了,一直找,没找到原因,头都大了,后来仔细研究了一下才看到写错了
@RequiresRoles:角色校验 (如 : user)
@RequiresPermissions:权限校验 (如 : sys:user:info)

    //只添加了@RequiresPermissions("sys:user:info")
    //添加的权限Permissions,授权器添加的也是权限,并非角色
    @RequiresPermissions("sys:user:info")
    @GetMapping("info/{id}")
    public Result info(@PathVariable("id") Integer id){
   
        UserEntity userEntity = userService.getById(id);
        return Result.success(userEntity);
    }

运行项目后准备修改一条数据,这个时候角色权限都是空的
在这里插入图片描述
发生了异常,Subject does not have permission [sys:user:info]
在这里插入图片描述
在这里插入图片描述

然后再sys_rolesys_user_rolesys_menusys_role_menu,中个添加一条数据

sys_role

在这里插入图片描述

sys_user_role

在这里插入图片描述

sys_menu

在这里插入图片描述

sys_role_menu

在这里插入图片描述
上面都关联起来了,然后退出,重新登陆,让他加载一下角色权限,试一下,没啥问题了
在这里插入图片描述

整合redis

pom.xml添加redis依赖

我这边单独引入spring-boot-starter-data-redis会发生异常,所以增加commons-pool2依赖
不信邪的可以试试

		 <!-- Redis -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
         <!--        填redis坑-->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>

#### yml引入redis

  redis:
    database: 0
    host: 127.0.0.1
    port: 6379
    password:
    timeout: 3000
    lettuce:
      pool:
        max-active: 8
        max-wait: -1
        max-idle: 8
        min-idle: 0

在这里插入图片描述

utils包下新建sessionredis

session包下新建RedisSessionDao

import org.apache.shiro.session.Session;
import org.apache.shiro.session.UnknownSessionException;
import org.apache.shiro.session.mgt.eis.AbstractSessionDAO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import java.io.Serializable;
import java.util.Collection;
import java.util.concurrent.TimeUnit;


public class RedisSessionDao  extends AbstractSessionDAO {
   

    // Session超时时间,单位为毫秒
    private long expireTime = 1200000;

    @Autowired
    private RedisTemplate redisTemplate;// Redis操作类,对这个使用不熟悉的,可以参考前面的博客

    public RedisSessionDao() {
   
        super();
    }

    public RedisSessionDao(long expireTime, RedisTemplate redisTemplate) {
   
        super();
        this.expireTime = expireTime;
        this.redisTemplate = redisTemplate;
    }

    @Override // 更新session
    public void update(Session session) throws UnknownSessionException {
   
        if (session == null || session.getId() == null) {
   
            return;
        }
        session.setTimeout(expireTime);
        redisTemplate.opsForValue().set(session.getId(), session, expireTime, TimeUnit.MILLISECONDS);
    }

    @Override // 删除session
    public void delete(Session session) {
   
        if (null == session) {
   
            return;
        }
        redisTemplate.opsForValue().getOperations().delete(session.getId());
    }

    @Override// 获取活跃的session,可以用来统计在线人数,如果要实现这个功能,可以在将session加入redis时指定一个session前缀,统计的时候则使用keys("session-prefix*")的方式来模糊查找redis中所有的session集合
    public Collection<Session> getActiveSessions() {
   
        return redisTemplate.keys("*");
    }

    @Override// 加入session
    protected Serializable doCreate(Session session) {
   

        Serializable sessionId = this.generateSessionId(session);
        this.assignSessionId(session, sessionId);
        redisTemplate.opsForValue().set(session.getId(), session, expireTime, TimeUnit.MILLISECONDS);
        return sessionId;
    }

    @Override// 读取session
    protected Session doReadSession(Serializable sessionId) {
   
        if (sessionId == null) {
   
            return null;
        }
        Session session = (Session) redisTemplate.opsForValue().get(sessionId);
        return session;
    }

    public long getExpireTime() {
   
        return expireTime;
    }

    public void setExpireTime(long expireTime) {
   
        this.expireTime = expireTime;
    }

    public RedisTemplate getRedisTemplate() {
   
        return redisTemplate;
    }

    public void setRedisTemplate(RedisTemplate redisTemplate) {
   
        this.redisTemplate = redisTemplate;
    }

}

#### 在utils包下新建ApplicationContextUtil工具类

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

@Component
public class ApplicationContextUtil implements ApplicationContextAware {
   

    public static ApplicationContext context;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
   
        System.out.println("初始化");
        context = applicationContext;
    }

    /**
     * 根据工厂中的类名获取类实例
     */
    public static Object getBean(String beanName){
   
        return context.getBean(beanName);
    }
}
redis包下新建RedisCacheManager类和RedisCache

RedisCacheManager

import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
import org.apache.shiro.cache.CacheManager;

public class RedisCacheManager implements CacheManager {
   
    @Override
    public <K, V> Cache<K, V> getCache(String cacheName) throws CacheException {
   
        System.out.println("缓存名称: "+cacheName);
        return new RedisCache<K,V>(cacheName);
    }
}

RedisCache
重写了shiro内部的缓存方式,采用了redis缓存

import com.macro.utils.ApplicationContextUtil;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
import org.springframework.data.redis.core.RedisTemplate;
import java.util.Collection;
import java.util.Set;


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("获取缓存:"+ k);
        return (V) getRedisTemplate().opsForHash().get(this.cacheName,k.toString());
    }

    @Override
    public V put(K k, V v) throws CacheException {
   
        System.out.println("设置缓存key: "+k+" value:"+v);
        getRedisTemplate().opsForHash().put(this.cacheName,k.toString(),v);
        return null;
    }

    @Override
    public V remove(K k) throws CacheException {
   
        return (V) getRedisTemplate().opsForHash().delete(this.cacheName,k.toString());
    }

    @Override
    public void clear() throws CacheException {
   
        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) ApplicationContextUtil.getBean("redisTemplate");
        return redisTemplate;
    }
}
修改 ShiroConfig
    //2.创建安全管理器
    @Bean
    public DefaultWebSecurityManager getDefaultWebSecurityManager(Realm realm){
   
        DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
        //给安全管理器设置
        defaultWebSecurityManager.setRealm(realm);
        //新增
        //配置自定义的session缓存
        defaultWebSecurityManager.setSessionManager(configWebSessionManager());
        //配置自定义缓存redis
        defaultWebSecurityManager.setCacheManager(redisCacheManager());
        return defaultWebSecurityManager;
    }

	//3.将自定义的Realm 设置为Bean ,注入到2中
    @Bean
    public Realm getRealm(){
   
        UserRealm realm = new UserRealm();
        // 设置密码匹配器
        HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
        // 设置加密方式
        credentialsMatcher.setHashAlgorithmName("MD5");
        // 设置散列次数
        credentialsMatcher.setHashIterations(1024);
        realm.setCredentialsMatcher(credentialsMatcher);

		//这一步开始就是新加的了
        real
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值