服务器怎么装网站吗,网页制作实训总结800字,wordpress vip会员系统,做哪些网站好文章目录 前言1. 直接上代码最后在讲解1.1 新增的pom依赖1.2 RedisCache.java1.3 RedisCacheManager.java1.4 jwt的三个类1.5 ShiroConfig.java新增Bean 2. 源码讲解。2.1 shiro 缓存的代码流程。2.2 缓存流程2.2.1 认证和授权简述2.2.2 AuthenticatingRealm.getAuthentication… 文章目录 前言1. 直接上代码最后在讲解1.1 新增的pom依赖1.2 RedisCache.java1.3 RedisCacheManager.java1.4 jwt的三个类1.5 ShiroConfig.java新增Bean 2. 源码讲解。2.1 shiro 缓存的代码流程。2.2 缓存流程2.2.1 认证和授权简述2.2.2 AuthenticatingRealm.getAuthenticationInfo() 认证缓存。2.2.3 AuthorizingRealm.getAuthorizationInfo() 授权缓存。 2.3 redis缓存的设置入口个人认为是重点 3. 问题解决3.1 授权的流程是怎么做的3.2 RequiresPermissions(sys:user:list)是如何工作的**1. 基本原理****2. 具体流程****1. 用户登录阶段****2. 调用 listUsers 方法时****3. 匹配机制** 前言 shiro学习一了解shiro学习执行shiro的流程。使用springboot的测试模块学习shiro单应用demo 6个 shiro学习二shiro的加密认证详解加盐与不加盐两个版本。 shiro学习三shiro的源码分析 密码专辑对密码加盐加密对密码进行md5加密封装成密码工具类 shiro学习四使用springboot整合shiro正常的企业级后端开发shiro认证鉴权流程。使用redis做token的过滤。md5做密码的加密。 代码所在地https://github.com/fengfanli/springboot-shiro 记得给个星哦。 本文详细介绍了在Java Spring Boot项目中使用Apache Shiro进行权限管理的实现方式。通过整合Redis作为缓存管理器实现了用户权限的缓存提高了权限验证的效率。文章还解析了RequiresPermissions注解的工作原理说明了如何通过AOP机制拦截方法并进行权限字符串的匹配校验确保只有具备相应权限的用户能够访问受保护的方法。整体上文章为Shiro在Spring Boot项目中的应用提供了全面的技术指导。
1. 直接上代码最后在讲解
1.1 新增的pom依赖
!--JWT--dependencygroupIdio.jsonwebtoken/groupIdartifactIdjjwt/artifactIdversion0.9.1/version/dependency1.2 RedisCache.java
package com.feng.shiro;import com.alibaba.fastjson.JSON;
import com.feng.constant.Constant;
import com.feng.jwt.JwtTokenUtil;
import com.feng.utils.RedisUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
import org.apache.shiro.util.CollectionUtils;import java.util.*;
import java.util.concurrent.TimeUnit;/*** ClassName: RedisCache* Description 缓存工具类* createTime: 2020/2/9 20:42* Author: 冯凡利* UpdateUser: 冯凡利* Version: 0.0.1*//*** 这里的 RedisUtils 不能注入** param K* param V*/
Slf4j
public class RedisCacheK, V implements CacheK, V {private final static String PREFIX shiro-cache:;private String cacheKey;private long expire 24; // 24 小时private RedisUtil redisUtil;public RedisCache(String name, RedisUtil redisUtil) {
// this.cacheKeyPREFIXname:;this.cacheKey Constant.IDENTIFY_CACHE_KEY;this.redisUtil redisUtil;}/*** 根据 key 值 获取 权限信息** param key jwt* return* throws CacheException*/Overridepublic V get(K key) throws CacheException {log.info(Shiro 从缓存中获取数据 KEY 值[{}], key);if (key null) {return null;}try {String redisCacheKey getRedisCacheKey(key);Object rawValue redisUtil.get(redisCacheKey); // 根据key 获取 数据if (rawValue null) {return null;}SimpleAuthorizationInfo info JSON.parseObject(rawValue.toString(), SimpleAuthorizationInfo.class);V value (V) info;return value;} catch (Exception e) {throw new CacheException(e);}}/*** 存值** param key jwt* param value* return* throws CacheException*/Overridepublic V put(K key, V value) throws CacheException {log.info(put key [{}], key);if (key null) {log.warn(Saving a null key is meaningless, return value directly without call Redis.);return value;}try {String redisCacheKey getRedisCacheKey(key); // cacheKey userIdredisUtil.set(redisCacheKey, value ! null ? value : null, expire, TimeUnit.HOURS);return value;} catch (Exception e) {throw new CacheException(e);}}/*** 根据 key 值 删除缓存的值** param key* return* throws CacheException*/Overridepublic V remove(K key) throws CacheException {log.info(remove key [{}], key);if (key null) {return null;}try {String redisCacheKey getRedisCacheKey(key);Object rawValue redisUtil.get(redisCacheKey);V previous (V) rawValue;redisUtil.delete(redisCacheKey);return previous;} catch (Exception e) {throw new CacheException(e);}}/*** 清除 所有的值** throws CacheException*/Overridepublic void clear() throws CacheException {log.debug(clear cache);SetString keys null;try {keys redisUtil.keys(this.cacheKey *);} catch (Exception e) {log.error(get keys error, e);}if (keys null || keys.size() 0) {return;}for (String key : keys) {redisUtil.delete(key);}}/*** 获取 redis 所存的 缓存数的大小** return*/Overridepublic int size() {int result 0;try {result redisUtil.keys(this.cacheKey *).size();} catch (Exception e) {log.error(get keys error, e);}return result;}/*** 获取key值** return*/SuppressWarnings(unchecked)Overridepublic SetK keys() {SetString keys null;try {keys redisUtil.keys(this.cacheKey *);} catch (Exception e) {log.error(get keys error, e);return Collections.emptySet();}if (CollectionUtils.isEmpty(keys)) {return Collections.emptySet();}SetK convertedKeys new HashSet();for (String key : keys) {try {convertedKeys.add((K) key);} catch (Exception e) {log.error(deserialize keys error, e);}}return convertedKeys;}/*** 获取 value值** return*/Overridepublic CollectionV values() {SetString keys null;try {keys redisUtil.keys(this.cacheKey *);} catch (Exception e) {log.error(get values error, e);return Collections.emptySet();}if (CollectionUtils.isEmpty(keys)) {return Collections.emptySet();}ListV values new ArrayListV(keys.size());for (String key : keys) {V value null;try {value (V) redisUtil.get(key);} catch (Exception e) {log.error(deserialize values error, e);}if (value ! null) {values.add(value);}}return Collections.unmodifiableList(values);}/*** 获取 redis 中的 缓存 key ,很重要** param key* return*/private String getRedisCacheKey(K key) {if (null key) {return null;} else {return this.cacheKey JwtTokenUtil.getUserId(key.toString());}}
}1.3 RedisCacheManager.java
package com.feng.shiro;import com.feng.utils.RedisUtil;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
import org.apache.shiro.cache.CacheManager;
import org.springframework.beans.factory.annotation.Autowired;/*** ClassName: RedisCacheManager* Description 描述* createTime: 2020/2/9 21:14* Author: 冯凡利* UpdateUser: 冯凡利* Version: 0.0.1*/
public class RedisCacheManager implements CacheManager {Autowiredprivate RedisUtil redisUtil;Overridepublic K, V CacheK, V getCache(String s) throws CacheException {return new RedisCache(s, redisUtil);}
}
1.4 jwt的三个类
我不贴了不是核心代码可直接看GitHub。 四个部分
JwtTokenUtil.javajwt的工具类JwtPropertiesConfig.javaapplication.properties 中的jwt的属性读取类InitializerJwtPropertiesConfig.java将 JwtPropertiesConfig.java进行注入。application.properties 中的jwt的自定义属性配置。
1.5 ShiroConfig.java新增Bean
新增了redisCacheManager bean然后再 getShiroRealm() 函数中 通过 RealmShiroRealm 进行设置 缓存。 /*** shiro 的 缓存管理器* 需要在 ShiroRealm bean 中进行设置* return*/Bean(name redisCacheManager)public RedisCacheManager redisCacheManager() {return new RedisCacheManager();}/*** 登录的 认证域** param hashedCredentialsMatcher* return*/Bean(name shiroRealm)public ShiroRealm getShiroRealm(Qualifier(shiroHashedCredentialsMatcher) HashedCredentialsMatcher hashedCredentialsMatcher,Qualifier(redisCacheManager) RedisCacheManager redisCacheManager) {ShiroRealm shiroRealm new ShiroRealm();// 自定义 处理 token 过滤shiroRealm.setCredentialsMatcher(hashedCredentialsMatcher);// 自定义 缓存管理器shiroRealm.setCacheManager(redisCacheManager);return shiroRealm;}shiro 新增的 shiro 缓存 代码到此结束。 其他代码没有展示是因为 shiro 缓存的代码只有这些。
2. 源码讲解。
2.1 shiro 缓存的代码流程。
首先编写 RedisCache.java实现 Cache 接口再这里重写redis的一些操作函数编写RedisCacheManager.java 类实现 CacheManager 接口将RedisCache.java 类加入到 缓存管理中。编写ShiroConfig.java 类将RedisCacheManager注入生Bean然后再Realm本案例是ShiroRealm中设置 缓存管理同时也是设置 资格匹配 的地方。
2.2 缓存流程
shiro的核心是再 数据域中也就是Realm本案例的是ShiroRealm继承 AuthorizingRealm 授权域 授权域 继承了 AuthenticatingRealm 认证域认证域 又继承了 CachingRealm 缓存域。这三个类是shiro 认证、授权、缓存 三个最重要的类。
2.2.1 认证和授权简述
继承了 AuthorizingRealm 授权域就要重写两个最重要的方法 认证函数 doGetAuthenticationInfo()既然认证重写的就是 AuthenticatingRealm 认证域 中的函数。在函数getAuthenticationInfo() 中。源码截图如下 授权函数 doGetAuthorizationInfo()既然授权重写的就是 AuthorizingRealm 授权域 中的函数。在函数 getAuthorizationInfo() 中,源码截图如下
在 Shiro 中认证缓存authentication cache 和 授权缓存authorization cache 是两个不同的缓存它们用于不同的目的
认证缓存 (authenticationCache)用于缓存用户的认证信息如用户是否存在凭证是否正确等。这通常是一个比较频繁更新的缓存因为用户登录的凭证如密码是动态变化的可能会变得无效。授权缓存 (authorizationCache)用于缓存用户的授权信息如用户的角色、权限等。授权信息通常不太会频繁变动因此可以缓存很长时间。它一般是用于存储用户的角色和权限信息以减少每次请求时对数据库或外部系统的查询。
2.2.2 AuthenticatingRealm.getAuthenticationInfo() 认证缓存。
该函数的第一句就是AuthenticationInfo info this.getCachedAuthenticationInfo(token); 该函数就是从缓存中获取认证信息。 先说答案shiro不推荐、默认不支持 认证从缓存中获取。
代码中可以看到 在AuthenticatingRealm类属性 this.authenticationCachingEnabled false; 在构造函数中 默认为 false不开启缓存。
究其原因用于缓存用户的认证信息如用户是否存在凭证是否正确等。这通常是一个比较频繁更新的缓存因为用户登录的凭证如密码是动态变化的可能会变得无效。
所以不推荐缓存通过debug也可以看到。
2.2.3 AuthorizingRealm.getAuthorizationInfo() 授权缓存。
授权缓存 在shiro 中是支持的。因为 AuthorizingRealm 授权域类中的 类属性 this.authorizationCachingEnabled true; 在构造函数中默认为true的。
getAuthorizationInfo() 函数就是从缓存中进行获取的没有获取到在 进入 到 doGetAuthorizationInfo() 函数中进行获取、授权。
可以通过debug的方式一点点去查看。
如果没有设置redis作为缓存管理默认可以配置缓存但是shiro并没有帮我指定缓存估计是怕默认指定之后怕内存使用过高需要自行设定接下来讲解。
2.3 redis缓存的设置入口个人认为是重点
前边讲的都是逻辑流程如何从缓存中获取。我在读源码的时候我就很好奇那设置redis作为缓存的地方在哪里呢默认是用内存作为缓存的设置又在哪里呢抱着这些疑问继续走。
答案入口就在ShiroConfig中 Realm 的配置中。 ShiroRealm shiroRealm new ShiroRealm();// 自定义 处理 token 过滤shiroRealm.setCredentialsMatcher(hashedCredentialsMatcher);// 自定义 缓存管理器shiroRealm.setCacheManager(redisCacheManager);new ShiroRealm()时 ;会执行 AuthorizingRealm、AuthenticatingRealm的构造函数。构造函数中会设置 缓存启动情况、缓存的配置情况。 其中 AuthenticatingRealm 认证域的构造函数中会默认指定 SimpleCredentialsMatcher 作为 凭证匹配器 的 类shiro还提供了 HashedCredentialsMatcher 作为 凭证匹配器 类具体有几个看 CredentialsMatcher 类的实现类有几个即可下图所示。后面我们自定义了 凭证匹配器 类 CustomHashedCredentialsMatcher其中的 doCredentialsMatch 函数就是默认 认证的最核心的函数。 AuthorizingRealm 授权域的构造函数中会默认指定 缓存管理在CachingRealm 类中、凭证匹配器认证域类中 的初始化默认都是null。 shiroRealm.setCredentialsMatcher(hashedCredentialsMatcher);调用 认证域类 中的方法设置 凭证匹配器 。 shiroRealm.setCacheManager(redisCacheManager);调用 缓存域类 中的方法设置缓存管理器 以及 实现缓存的方式第二行语句该方法是个接口有两个实现方法一个是 认证域一个是授权域。往下走。 这里就是 授权域 中的 afterCacheManagerSet() 函数 第一个函数调用父类也就是认证域上的函数会继续调用 this.getAvailableAuthenticationCache()。认证域前边说了认证缓存shiro默认是关闭的debug走一边流程就明白了重点就是授权域了。在第二个函数上先判断是否有缓存为null然后再看 授权域 是否支持 授权缓存默认支持前边说了然后进入懒加载缓存下面第二张图到获取缓存的地方缓存管理不为null是redis的接着从158行获取缓存158行往下debug就是上面的1.3小节的 RedisCacheManager.java将redis返回。 我又来问题了如果没有redis走的是哪个呢?首先我也不知道得先把redis缓存管理给去掉。看看使用的哪个。CacheManager.java 是接口其实现类截图如下第一个是我实现的其余三个都是shiro自带的第二个应该不是看源码。 通过源码查看shiro并没有默认使用缓存配置仅仅是打开了缓存配置可以供我们使用如果想用使用缓存需要自行配置缓存想上面配置 redis一样将下面的第二个或者第四个配置到缓存管理中去。
到这里为止源码讲解完啦。
3. 问题解决
3.1 授权的流程是怎么做的
这个需要有背景的。 我基于这个项目来说一下 前端 LayUIthymeleaf 后端使用springboot、shiro作为权限管理。 根据请求 /api/users进行举例。 前端发请求后会先认证此处略。 然后会先从前端标签获取 权限字符串 sys:user:list这里使用到了 thymeleaf然后 通过 doGetAuthorizationInfo()获取所有权限字符串和角色然后与从前端拿到的 权限字符串 sys:user:list进行对比若包含这通过。否则失败
3.2 RequiresPermissions(“sys:user:list”)是如何工作的
1. 基本原理
RequiresPermissions(sys:user:list) 是 Apache Shiro 提供的权限控制注解用于声明访问某个方法或类需要的特定权限。
它的工作机制如下 拦截注解 Shiro 提供了 AuthorizationAttributeSourceAdvisor 和 AOP切面编程机制来拦截被注解标记的类或方法。当调用被标记的方法时Shiro 拦截并执行权限校验。 获取当前用户的权限 Shiro 从当前登录用户的 Subject主体中获取用户的权限信息通常是一组权限字符串。 权限字符串匹配 Shiro 将注解中的权限字符串如 sys:user:list与当前用户的权限列表进行比对。如果用户的权限列表中包含注解指定的权限字符串则校验通过否则抛出 AuthorizationException拒绝访问。 2. 具体流程
假设你有以下代码
RequiresPermissions(sys:user:list)
public void listUsers() {// 获取用户列表逻辑
}1. 用户登录阶段
用户通过登录接口认证成功后Shiro 会根据用户身份如用户名从数据库或缓存中加载用户的所有权限并存储在 Subject 中。示例权限列表[sys:user:list, sys:user:edit, sys:user:delete]2. 调用 listUsers 方法时
Shiro 拦截 RequiresPermissions 注解通过当前用户的 Subject 获取权限列表。调用 Subject.isPermitted(sys:user:list) 方法具体逻辑如下 Shiro 将注解中的权限字符串 sys:user:list 传递给 AuthorizingRealm 的 doGetAuthorizationInfo 方法。doGetAuthorizationInfo 返回用户的权限列表。Shiro 比较 sys:user:list 是否存在于用户的权限列表中 存在校验通过继续执行方法。不存在抛出异常 AuthorizationException。
3. 匹配机制
权限字符串可以使用通配符Wildcard进行匹配 完全匹配sys:user:list 对比 sys:user:list通过。通配符匹配sys:user:* 对比 sys:user:list通过。多级通配符sys:*:* 对比 sys:user:list通过。