百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

Springboot Shiro Redis配置高性能缓存?

wxin55 2024-11-01 14:30 10 浏览 0 评论

导读前文章

SpringBoot Shiro 实现登录/记住我的功能

SpringBoot Shiro基于Url权限拦截系统

Sprngboot Shiro增查按钮显示控制、注解权限拦截

本文导读

通过以上三篇文章,我们已经实现springboot shiro登录,权限系统,那么你们在使用这些功能的时候,发现每次调用一条权限的时候,shiro会去查询数据库,如果一个页面有大批量权限要控制的时候,那么就需要大量重复去调用数据库查询,用户量很大的情况,这个性能会非常差,因此我们如何提高这个性能呢?这个时候我们就引入了shiro 整合redis实现缓存,只有第一次访问的时候权限的时候,加载一次,然后放入缓存中,之后的操作都是从缓存里面获取数据即可。

导入依赖包

<dependencies>

	  <dependency>
	  	<groupId>org.springframework.boot</groupId>
	  	<artifactId>spring-boot-starter-web</artifactId>
	  </dependency>

        <dependency>
        	<groupId>hsb-common-utils</groupId>
        	<artifactId>hsb-common-utils</artifactId>
        </dependency>
        
        <dependency>
        	<groupId>hsb-common-ex</groupId>
        	<artifactId>hsb-common-ex</artifactId>
        </dependency>
        
       <dependency>
	  	<groupId>hsb-common-dao</groupId>
	  	<artifactId>hsb-common-dao</artifactId>
	  </dependency>
  
       <!-- 整合 thymeleaf -->		 
		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
  
	     <!-- 链接池 -->
	  	<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>druid-spring-boot-starter</artifactId>
		</dependency>
		
		 <!-- mysql驱动 -->
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
		</dependency>
        
        <!-- 整合shiro -->
		<dependency>
		    <groupId>org.apache.shiro</groupId>
		    <artifactId>shiro-spring-boot-starter</artifactId>
		    <version>1.6.0</version>
		</dependency>
		
		<!-- Shiro 整合 Thymeleaf 依赖 页面才能使用shiro标签 -->
		<dependency>
			<groupId>com.github.theborakompanioni</groupId>
			<artifactId>thymeleaf-extras-shiro</artifactId>
			<version>2.0.0</version>
		</dependency>

      <!-- Shiro 整合 redis 必须整合 -->
      <dependency>
          <groupId>org.crazycake</groupId>
          <artifactId>shiro-redis</artifactId>
          <version>3.3.1</version>
      </dependency>

      <!-- 使用springboot redis配置对象 -->
      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-data-redis</artifactId>
      </dependency>

  </dependencies>

编辑配置对象

**
 * Shiro配置类<br>
 *   该类主要初始化启动装载Shiro类对象,完成相关实例化工作,为后面功能提供服务
 */
@Configuration
public class ShiroConfig {

	@Autowired
	RedisCacheManager cacheManager;
	@Autowired
	RedisSessionDAO sessionDAO;
	@Autowired
	SessionManager sessionManager;

	 /**
	  *      实例化一个Shiro安全管理器,相当于 SpringMVC 中的 DispatcherServlet<br>
	  *      所有具体的交互都通过 SecurityManager 进行控制;它管理着所有 Subject、且负责进行认证和授权、及会话、缓存的管理<br>
	  * @param userRealm  注入用户认证,权限安全域
	  * @return
	  */
	 @Bean
	 public DefaultWebSecurityManager securityManager(SysUserRealm userRealm) {
		 DefaultWebSecurityManager defaultSecurityManager = new DefaultWebSecurityManager();
		 //设置用户域
		 defaultSecurityManager.setRealm(userRealm);
		 //设置记住密码管理
		 defaultSecurityManager.setRememberMeManager(cookieRememberMeManager());
		 //创建了一个session管理器
		 defaultSecurityManager.setSessionManager(sessionManager);
		 //设置缓存管理器
		 defaultSecurityManager.setCacheManager(cacheManager);
		 ThreadContext.bind(defaultSecurityManager);//解决异常:ThreadContext or as a vm static singleton
		 return defaultSecurityManager;
	 }
	 
	 /**
	    *         配置shiro过滤器后,相当于servlet的filter<br>
	  *   1、页面才能正常使用shiro相关标签,不然使用无效,如< shiro:principal property="username"/>   <br>
	  *   2、其他类获取SysUserPO sysUserPO=(SysUserPO)SecurityUtils.getSubject().getPrincipal();才不会报错<br>
	  * @param securityManager
	  * @return
	  */
	 @Bean(name = "shiroFilter")
	 public ShiroFilterFactoryBean shiroFilter(DefaultWebSecurityManager securityManager) {
	    //创建一个过滤工厂Bean,跟我们以前web用的过滤器类似
	    ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
	    //注入安全管理类
	    shiroFilterFactoryBean.setSecurityManager(securityManager);
	    
	    //设置登录页面路径
	    shiroFilterFactoryBean.setLoginUrl("/login");
	    //设置没有权限访问进入路径,有权限异常拦截,这个配置无效,应对参考AdminExceptionHandler类实现
	    shiroFilterFactoryBean.setUnauthorizedUrl("/notAuth");
	    
	    //创建一个过滤路径map对象,key是路径,value值说明:anon代表匿名访问,authc代表必须登录subject.login认证后授权后才能访问
	    Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
	    //放开static静态文件路径不授权就可以访问,如果不放开,相关js,css将无法访问
	    filterChainDefinitionMap.put("/static/**", "anon");
	    //放开执行登录操作
	    filterChainDefinitionMap.put("/login", "anon");
	    //退出登录操作
	    filterChainDefinitionMap.put("/loginout", "logout");
	    //无权限页面
	    filterChainDefinitionMap.put("/notAuth", "anon");
	    
	    //authc其他所有路径都必须通过subject.login认证后才能访问
	    //filterChainDefinitionMap.put("/**", "authc");
	    //如果用了rememberMe记住我功能,那就必须把authc改成user,否则记住我功能失效,必须要记住了
	    filterChainDefinitionMap.put("/**", "user");
	    shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
	    
	    //自定义拦截器
        Map<String, Filter> filtersMap = new LinkedHashMap<String, Filter>();
        //这个key不能随便取,要对应上面filterChainDefinitionMap拦截对象value的值,然后才能拦截"/**"所有路径,这个要注意*****************************
        //过滤器对象要用new方式,不能用spring注入方式,否则会影响默认过滤器使用
		//filtersMap.put("user", new URLPermissionPathMatchingFilter());
		shiroFilterFactoryBean.setFilters(filtersMap);
		 

	    return shiroFilterFactoryBean;
	 }

	 /**
	  * Shiro 整合thymeleaf框架,加这个注解后才能使用标签,如<br>
	  * < shiro:principal property="username"/><br>
	  * @return
	  */
	 @Bean
	 public ShiroDialect shiroDialect() {
	     return new ShiroDialect();
	 }
	 
	 //<===============RememberMe配置==================>
	 /**
	  * Cookie记住我管理器
	  * @return
	  */
	 @Bean
	 public RememberMeManager cookieRememberMeManager() {
		 CookieRememberMeManager cookieRememberMeManager=new CookieRememberMeManager();
		 //rememberMe cookie加密的密钥  默认AES算法 密钥长度(128 256 512 位)  
		 //6BvVHd5gUs0FEA86DFAdAg== 是密码钥匙,建议每个项目都不一样
		 cookieRememberMeManager.setCipherKey(Base64.decode("6BvVHd5gUs0FEA86DFAdAg=="));
		 //设置cookie
		 cookieRememberMeManager.setCookie(simpleCookie());
		 return cookieRememberMeManager;
	 }

	 /**
	  *  自定义指定cookie多少天,如果没有操作的情况下自动过期
	 * @return
	 */
	@Bean
	public SimpleCookie simpleCookie() {
		 //创建一个cookie名称
		 SimpleCookie simpleCookie=new SimpleCookie("rememberMe");
		 simpleCookie.setMaxAge(2592000);//单位秒,折算成30天
		 return simpleCookie;
	 }
	 
	 /**
	  *  创建一个session管理器,解决重定向自动带有;jsessionid=0935FAEB578E70CDB29AA08303F91C69
	  * @return
	  */
	 @Bean("sessionManager")
	 public DefaultWebSessionManager defaultWebSessionManager() {
		 DefaultWebSessionManager defaultWebSessionManager=new DefaultWebSessionManager();
		 //移除重定向不自动带有;jsessionid=0935FAEB578E70CDB29AA08303F91C69功能
		 defaultWebSessionManager.setSessionIdUrlRewritingEnabled(false);
		 defaultWebSessionManager.setSessionDAO(sessionDAO);
		 return defaultWebSessionManager;
	 }

	 //===========================shiro整合redis=======================
	/**
	 * Redis集群使用RedisClusterManager,单个Redis使用RedisManager<br>
	 * 创建连接redis管理对象
	 * @param redisProperties
	 * @return
	 */
	@Bean
	public RedisManager redisManager(RedisProperties redisProperties) {
		RedisManager redisManager = new RedisManager();
		//从配置文件读取redis ip 和 端口,组成链接redis地址
		redisManager.setHost(redisProperties.getHost()+":"+redisProperties.getPort());
		redisManager.setPassword(redisProperties.getPassword());
		return redisManager;
	}

	/**
	 * 创建shiro管理redis对象
	 * @param redisManager
	 * @return
	 */
	@Bean
	public RedisCacheManager redisCacheManager(RedisManager redisManager) {
		RedisCacheManager redisCacheManager = new RedisCacheManager();
		redisCacheManager.setRedisManager(redisManager);
		redisCacheManager.setExpire(86400);
		redisCacheManager.setKeyPrefix("shiro:cache:");
		return redisCacheManager;
	}

	/**
	 * Shiro session缓存
	 * @param redisManager
	 * @return
	 */
	@Bean
	public RedisSessionDAO redisSessionDAO(RedisManager redisManager) {
		RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
		redisSessionDAO.setExpire(86400);
		redisSessionDAO.setKeyPrefix("shiro:session:");
		redisSessionDAO.setRedisManager(redisManager);
		return redisSessionDAO;
	}

	//===========================shiro 动态代理=======================
	/**s
	 * 注意,这个动态代理一定要配置,不配置可能导致缓存生效问题,跨域问题,因此一开始加入就对了
	 * @return
	 */
	@Bean
	public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
		DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
		defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
		return defaultAdvisorAutoProxyCreator;
	}

}


用户域代码


/**
 * Realm域,Shiro 从从 Realm 获取安全数据(如用户、角色、权限),就是说 SecurityManager 要验证用户身份<br>
 * 1、那么它需要从 Realm 获取相应的用户进行比较以确定用户身份是否合法<br>
 * 2、需要从 Realm 得到用户相应的角色 / 权限进行验证用户是否能进行操作<br>
 */
@Slf4j
@Configuration 
public class SysUserRealm extends AuthorizingRealm{//更改继承类AuthenticatingRealm -->AuthorizingRealm
	
	@Autowired
	SysUserService sysUserService;
	@Autowired
	SysActionDAO sysActionDAO;
	
	/**
	 *     身份认证 / 登录,验证用户是不是拥有相应的身份<br>
	 * SecurityUtils.getSubject().login()执行后--->进入这个方法执行具体登录校验<br>
	 * 
	 */
	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
		//得到用户密码token凭证
		UsernamePasswordToken usernamePasswordToken=(UsernamePasswordToken)token;
		//获得登录用户名,密码
		String userName=usernamePasswordToken.getUsername();
		log.info("登录授权,username={}",userName);
		
		//根据用户名查询用户对象
		SysUserPO sysUserPO=new SysUserPO();
		sysUserPO.setUsername(userName);
		sysUserPO=sysUserService.get(sysUserPO);
		
		//如果等于null,说明用户不存在。
		//问题如何返回用户提示不存在呢?
		if(sysUserPO==null) {
			//通过抛出异常来解决提示错误信息
			throw new UnknownAccountException("用户不存在=".concat(userName));
			
			//自定义抛出异常,如果不是AuthenticationException子类,那么异常信息不会向上抛出,因此自定义异常要继承这个类,Shiro提供的默认异常基本够用了
			//throw new RuntimeException("用户不存在");
		}
		
		//如果账号状态是禁用状态
		if(new Integer(0).equals(sysUserPO.getStatus())){
			throw new DisabledAccountException("用户被禁用=".concat(userName));
		}
		
		//账号正常,校验登录密码
		//第一个参数,存返回用户对象,第二个参数存密码,第三个登录用户名
		SimpleAuthenticationInfo simpleAuthenticationInfo=new SimpleAuthenticationInfo(sysUserPO, sysUserPO.getPassword(), sysUserPO.getUsername());
		//返回登录授权信息,shiro会自动校验转入的密码与数据库返回的密码是否一致,如果一致,代表登录成功
		return simpleAuthenticationInfo;
	}

	/**
	 *  授权权限,根据用户查询数据库对应的权限标识,把权限标识返回给shiro,shiro会自动做权限校验,成功就放行,错误就抛出异常
	 */
	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
		//获取登录对象
		SysUserPO sysUserPO=(SysUserPO)principals.getPrimaryPrincipal();
		
		//根据用户查询拥有权限
		List<SysActionPO> permissionList=sysActionDAO.getListByUserId(sysUserPO.getId());
		
		SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
		for (SysActionPO sysActionPO:permissionList) {
			//设置返回数据库权限
			info.addStringPermission(sysActionPO.getPermissions());
		}
	    log.info("用户={},拥有权限={}",sysUserPO.getUsername(),info.getStringPermissions());   
	    //最后把数据库权限信息返回到shiro中,shiro会自动与设置的权限进行校验,如果相等就授权通过,否则抛出授权认证异常
		return info;
	}
}

其他代码忽略,想要源码的联想我。

应用配置

server:
  port: 7010

spring:
  application:
    # 服务名,在注册时所用,调用方所用 
    name: hsb-admin
  mvc:
    #静态目录配置
    static-path-pattern: /static/** 

  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://192.168.0.103:3306/hsb?serverTimezone=UTC&useUnicode=true&zeroDateTimeBehavior=convertToNull&characterEncoding=UTF-8&autoReconnect=true&&useSSL=false
    username: root
    password: 123456
    
    druid: 
      #初始化链接数大小
      initial-size: 5
      #最大链接数大小
      max-active: 50
      #空闲链接数大小
      min-idle: 2
      #链接超时时间,单位ms
      max-wait: 50000
  thymeleaf:
    cache: false

  redis:
    host: 192.168.0.103
    port: 6379

mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

启动服务

登录,进入主页,权限查看,通过redis就可以看到缓存数据了,不管你刷新页面多少次,数据都是走缓存,不走数据库了。

总结

本文通过配置redis缓存,很方便了实现了shiro权限数据存储,会话存储,大家如有不清楚的,欢迎给我留言。

相关推荐

ES6中 Promise的使用场景?(es6promise用法例子)

一、介绍Promise,译为承诺,是异步编程的一种解决方案,比传统的解决方案(回调函数)更加合理和更加强大在以往我们如果处理多层异步操作,我们往往会像下面那样编写我们的代码doSomething(f...

JavaScript 对 Promise 并发的处理方法

Promise对象代表一个未来的值,它有三种状态:pending待定,这是Promise的初始状态,它可能成功,也可能失败,前途未卜fulfilled已完成,这是一种成功的状态,此时可以获取...

Promise的九大方法(promise的实例方法)

1、promise.resolv静态方法Promise.resolve(value)可以认为是newPromise方法的语法糖,比如Promise.resolve(42)可以认为是以下代码的语...

360前端一面~面试题解析(360前端开发面试题)

1.组件库按需加载怎么做的,具体打包配了什么-按需加载实现:借助打包工具(如Webpack的require.context或ES模块动态导入),在使用组件时才引入对应的代码。例如在V...

前端面试-Promise 的 finally 怎么实现的?如何在工作中使用?

Promise的finally方法是一个非常有用的工具,它无论Promise是成功(fulfilled)还是失败(rejected)都会执行,且不改变Promise的最终结果。它的实现原...

最简单手写Promise,30行代码理解Promise核心原理和发布订阅模式

看了全网手写Promise的,大部分对于新手还是比较难理解的,其中几个比较难的点:状态还未改变时通过发布订阅模式去收集事件实例化的时候通过调用构造函数里传出来的方法去修改类里面的状态,这个叫Re...

前端分享-Promise可以中途取消啦(promise可以取消吗)

传统Promise就像一台需要手动组装的设备,每次使用都要重新接线。而Promise.withResolvers的出现,相当于给开发者发了一个智能遥控器,可以随时随地控制异步操作。它解决了三大...

手写 Promise(手写输入法 中文)

前言都2020年了,Promise大家肯定都在用了,但是估计很多人对其原理还是一知半解,今天就让我们一起实现一个符合PromiseA+规范的Promise。附PromiseA+规范地址...

什么是 Promise.allSettled()!新手老手都要会?

Promise.allSettled()方法返回一个在所有给定的promise都已经fulfilled或rejected后的promise,并带有一个对象数组,每个对象表示对应的pr...

前端面试-关于Promise解析与高频面试题示范

Promise是啥,直接上图:Promise就是处理异步函数的API,它可以包裹一个异步函数,在异步函数完成时抛出完成状态,让代码结束远古时无限回掉的窘境。配合async/await语法糖,可...

宇宙厂:为什么前端离不开 Promise.withResolvers() ?

大家好,很高兴又见面了,我是"高级前端进阶",由我带着大家一起关注前端前沿、深入前端底层技术,大家一起进步,也欢迎大家关注、点赞、收藏、转发。1.为什么需要Promise.with...

Promise 新增了一个超实用的 API!

在JavaScript的世界里,Promise一直是处理异步操作的神器。而现在,随着ES2025的发布,Promise又迎来了一个超实用的新成员——Promise.try()!这个新方法简...

一次搞懂 Promise 异步处理(promise 异步顺序执行)

PromisePromise就像这个词的表面意识一样,表示一种承诺、许诺,会在后面给出一个结果,成功或者失败。现在已经成为了主流的异步编程的操作方式,写进了标准里面。状态Promise有且仅有...

Promise 核心机制详解(promise机制的实现原理)

一、Promise的核心状态机Promise本质上是一个状态机,其行为由内部状态严格管控。每个Promise实例在创建时处于Pending(等待)状态,此时异步操作尚未完成。当异步操作成功...

javascript——Promise(js实现promise)

1.PromiseES6开始支持,Promise对象用于一个异步操作的最终完成(包括成功和失败)及结果值的表示。简单说就是处理异步请求的。之所以叫Promise,就是我承诺,如果成功则怎么处理,失败怎...

取消回复欢迎 发表评论: