这是系列搭建springcloud基础框架的文章,内容包括集成shiro、Mysql主从、seata、activiti、drools、hadoop大数据常用组件、keepalive+nginx https配置等;
1.将旧有框架拷贝shiro相相关代码
有部分代码需要 @Data注解的,需要 Lombok 插件,但是 plugin marketplace 无法连接,就到 https://plugins.jetbrains.com/plugin/6317-lombok/versions 下,注,需要下载对应版本的才可以;
另下载了 idea 2020.1.4 版本
2. 集成 mybatis mapper 代码,先用 git 下载 原有 gitee 源码 (原有框架)
下载地址 : https://blog.csdn.net/liuzehn/article/details/124313436
https://sourceforge.net/projects/git-osx-installer/
下载后,提示git 2.33 与 当前系统版本不兼容;
改用 2.31 版,终端窗口要关闭重开;
3. 遇到 alibaba-fastjson 下载不了的情况,采用方法:
链接至:Maven 配置多个远程仓库 (都生效办法)
在 settings.xml 的 mirrors 下配置,如果第一个地址可以通,是不会再访问第二个的;
4. mybatis 版本号要写
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>
mysql 的依赖,一直载不了,是因为有其他报错影响,先把其他的完成
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.18</version>
</dependency>
5. mybatis mapper 非定义实现添加完成 ;
补充 apache commons 包
<!-- common -->
<!-- Apache Commons 集合工具类 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.4</version>
</dependency>
<!-- Apache Commons 配置文件读取工具类 -->
<dependency>
<groupId>commons-configuration</groupId>
<artifactId>commons-configuration</artifactId>
<version>1.10</version>
</dependency>
<!-- Apache Commons IO 文件/流工具类 -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
<!-- Apache Commons Lang 字符串/对象工具类 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
<!-- Apache Commons 数学工具类 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-math3</artifactId>
<version>3.6.1</version>
</dependency>
<!-- Apache Commons 网络工具类 -->
<dependency>
<groupId>commons-net</groupId>
<artifactId>commons-net</artifactId>
<version>3.8.0</version>
</dependency>
<!-- Apache HttpClient HTTP 客户端工具类 -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.13</version>
</dependency>
6. 添加相关物理表 ;
1)首先 配置docker”阿里云镜像”;
Mac 安装 docker 及使用
2)再下载 mysql image:
a. docker search mysql;
b. docker pull musql:5.7
c. 安装 mysql-server 5.7
3) 下载 dbeaver;
新建 选择 mysql 连接; -> localhost: mysql:3306
输入 连接信息 localhost: 3306 及 密码 ;-> 首次需要安装一些组件
再次点击 连接测试,显示成功,再点击确认;
4) 导入数据 ;
在 左侧 “数据库” – 下某个数据库下,右键 “导入数据” -> 弹窗口 ;
选择 左侧 “输入文件” ,弹出窗口,选择文件类型 “*”;
切换到 sql 脚本所在目录下,选择要导入的文件,可多选;
然后,一直 “下一步”, “继续” 完成;
——–
以上导入数据有问题,使用下面的方式;
右键 “数据库”,“SQL编辑器”,sql框内,右键 “文件” – “导入sql脚本”,再点击左侧“第三个” 黄色按钮 “执 行sql脚本”;
左侧 “第一个” 是执行sql语句;
7. 将 核心 common 代码,包括mapper、redis、spring、constants 相关代码调整完;
8. chen-manager 相关源码及配置:
@SpringBootApplication
@EnableFeignClients(value = "com.chen.frame.api") //不是同一个包下
@ComponentScan(basePackages = {"com.chen.frame.common", "com.chen.frame.api", "com.chen.frame.shiro"})
public class ManagerApplication {
public static void main(String ...args) {
//SpringApplication.run(ManagerApplication.class, args);
new SpringApplicationBuilder(ManagerApplication.class)
.properties(new ConcurrentHashMap<String, Object>(){{
put("frame.module.basePackages", "com.chen.frame.manager.mapper");
}}).run(args);
}
@Bean
public static PropertySourcesPlaceholderConfigurer placeholderConfigurer () {
PropertySourcesPlaceholderConfigurer configurer = new PropertySourcesPlaceholderConfigurer();
configurer.setIgnoreUnresolvablePlaceholders(true);
return configurer;
}
@Bean
@Primary
public ObjectMapper xssObjectMapper(Jackson2ObjectMapperBuilder builder) {
// 解析器
ObjectMapper objectMapper = builder.createXmlMapper(false).build();
// 注册XSS SQL 解析器
SimpleModule xssModule = new SimpleModule("XssStringJsonSerializer");
xssModule.addSerializer(new XssSqlStringJsonSerializer());
objectMapper.registerModule(xssModule);
return objectMapper;
}
bootstrap.yml
server:
port: 8033
spring:
application:
name: chen-manager
cloud:
nacos:
discovery:
server-addr: localhost:8848
config:
server-addr: localhost:8848
file-extension: yml
group: DEFAULT_GROUP
shared-configs[0]:
data-id: application-dev.yml
group: DEFAULT_GROUP
refresh: true
profiles:
active: dev
redis:
host: 192.168.64.128
port: 6378
timeout: 3000
max-wait: 30000
max-active: 100
max-idle: 20
min-idle: 0
mybatis:
mapperLocations: classpath:mapper/**Mapper.xml
type-aliases-package: com.chen.frame.manager.mapper
configuration:
map-underscore-to-camel-case: true
运行 chen-manager 报错 :
1). 源码没错 程序包也存在 maven install提示不存在 (java(x, y): error: 程序包 xx.xx.xxx 不存在 )
解决,在被依赖包的 pom.xml 添加
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<skip>true</skip> <!-- skip 掉 -->
</configuration>
</plugin>
</plugins>
</build>
2) 添加 @FeignClient 修改 @EnableFeignClient的value值后,提示 :
SpringCloud启动报错Did you forget to include spring-cloud-starter-loadbalancer;
将如下 依赖添加到全局 :
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
3) 没有使用 却RequestParam.value() was empty on parameter 0 (给方法的 @RequestParam 添加了 value注解 或 去掉@RequestParam 也一样报错)
解决修改的地方 :
a. prferences -> build,execution, deployment -> compiler -> java compiler
添加 “additional common line parameters” 值 :-parameters 没有效果;
b. 在最顶级 pom.xml 的 compiler 插件添加 configuration/parameters:true 配置;
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
<parameters>true</parameters><!-- 这个配置 -->
</configuration>
</plugin>
4) 错误 : 数据库相关的报错,没有数据源相关的报错
参看 : SpringCloud基础框架搭建 –3 mysql+redis主从+sharding-sphere
5) 验证shrio
a. jwtFilter.java
public class JwtFilter extends BasicHttpAuthenticationFilter {
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
// 一般作为验证是能访问该页面
// 比较先验证下是否有登录,再验证是否有权限访问;
// subject.login(jwtToken);
// 或者
// 以下语句,判断当前是否有登录,如果有登录,再判断当前的url是否在自己的 jwtRealm 设置权限字符中;
// 因为在权限管理系统中,权限URL一般只会有左侧那些权限Url, 具体的那些 【新增,删除。。】没有配置,需只会有权限的标志符如 user:add, user:delete
// 因此 对于新增的url 只判断是否登录,然后在 新增 controller方法上使用 RequirePermissions value=user:add
//return SecurityUtils.getSubject().isPermitted(getPathWithinApplication(request));
//SecurityUtils.getSubject().getPrincipals()
// 以下只验证是否登录
return SecurityUtils.getSubject().isAuthenticated();
// return true
// true 表示验证验证,就不会再走 onAccessDenied;
// 但会再往走下一个 验证链 的 realm 对象;
}
public boolean onAccessDenied(...) {
//验证不通过时,会走到这里,一般是输出消息到前端转向到登录页;
// 这里只返回false, 登录由PermissionFilter验证转向;
return false;
}
}
PermissionFilter.java
@Slf4j
public class PermissionFilter extends /*PermissionsAuthorizationFilter*/AccessControlFilter {
public boolean isAccessAllowed(...) {
// 权限验证过滤,首先经过 jwt 再走 permissionFilter
// 因此能走到这里表示有jwt权限验证过了,这里只再验证下是否登录过 session是否过其他
// 如果返回 false 再走 onAccessDenied 让转前端登录;
return SecurityUtils.getSubject().isAuthenticated();
}
}
管理后台权限设计;
1)系统管理-菜单管理,这里的URL一般只作为前端界面的菜单显示用,因为还包括按钮上的控制器方法,有很多,如果
都用SecurityUtils.getSubject().isPermitted(getPathWithinApplication(request)); 验证,那得将所有URL都加到菜单里,或配置里,不实际; 所以针对的权限,一般该菜单下具体的某个方法的权限或角色;
这些菜单如 /manage/user/add, /manage/role/add 是具体的URL;
我们需要某类URL需要身份验证/权限验证,添加过滤链 filters 配置,如下:
@Override
public Map<String, String> initGetFilterChain() {
Map<String,String> filterChain = new LinkedHashMap<>();
// 已登录用户
List<String> needPerms = Arrays.asList("/manage/**", "/shiroLoged/**");
needPerms.forEach(auth -> filterChain.put(auth, "jwt,myPerm"));
List<String> guests = Arrays.asList("/user/**","/permission/**", "/shiro/**");
guests.forEach(guest -> filterChain.put(guest, "anon"));
return filterChain;
}
通常需要认证的是这样配置的:
/manag/**=authc;
没有特殊用途的话,只要用户有登录了就可以访问;
如果 要想某个具体的按钮功能(初始页列表数据,新增功能等)是否能操作,有两种处理:
a. 在按钮功能对应的 contoller.功能 method 上添加 @RequirePermission,@RequireRoles 注解;
这种方式 @RestControllerAdvice 可以捕捉到;
i. 如果是权限的,本源码使用 自定义的
public class UrlPermissionResolver implements PermissionResolver {...}
以及
public class ResourcePermission implements Permission, Serializable {...}
来实现 权限 permission 的验证;
但是 requirepermissions或requireroles 这两种注解,需要首先配置 adivce 的 aop 配置;
/**
* 添加注解支持
*/
@Bean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
// 强制使用cglib,防止重复代理和可能引起代理出错的问题
defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
return defaultAdvisorAutoProxyCreator;
}
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
advisor.setSecurityManager(securityManager);
return advisor;
}
猜:requireroles 通过上面的 bean 配置来拦截,首先通过 jwtFilter 的 isAccessAllowed ,验证成功后,再走角色验证的过程;
由于 jwtRealm 由自定义的 permission 实现 :
JwtRealm jwt = new JwtRealm();
jwt.setAuthenticationTokenClass(JwtToken.class);
jwt.setManagerServiceFeign(iSysUserService);
jwt.setRedisUtil(redisUtil);
jwt.setPermissionResolver(myPermissionResolver()); //自定义
realmList.add(jwt);
当是 requirepermissions 注解时,就会走自定义的 Permission 验证;
注:有配置 shiro 拦截的 如 /manage/** 只是表示这个前缀的资源需要认证(authc)
如果没有配置过 @requirePermissions或 @RequireRoles 则 或 /manage/user/**=perms[“user:add,…”] 这样的配置
则不会进入 shiro realm 的授权方法;
b. 或者配置 /manage/user/**=perms[“user:add,…”]
这种方式无法被 @RestControllerAdvice 捕获 unauthorized 401 异常,如果 jwtrealm 的权限设置里没有给 permission 字符集设置:
//permSet.add("user:perm");
时,会报这样的错:
{
"timestamp": "2023-04-28T19:48:36.703+00:00",
"status": 401,
"error": "Unauthorized",
"message": "No message available",
"path": "/shiroLoged/configPerms"
}
而不是自定义 ResultData(code,msg, data) 这种格式的错误;
=======================================================
相关controller 代码
package com.chen.frame.manager.controller;
import com.alibaba.fastjson.JSONObject;
import com.chen.frame.api.frame.bean.entity.SysUser;
import com.chen.frame.api.frame.bean.entity.Test;
import com.chen.frame.api.frame.bean.vo.SysUserVO;
import com.chen.frame.common.constants.CacheKeys;
import com.chen.frame.common.http.ResultData;
import com.chen.frame.common.http.ResultStatus;
import com.chen.frame.common.redis.RedisUtil;
import com.chen.frame.common.utils.AesUtil;
import com.chen.frame.common.utils.CommonStringUtils;
import com.chen.frame.common.utils.IpUtil;
import com.chen.frame.manager.service.SysUserService;
import com.chen.frame.manager.service.TestService;
import com.chen.frame.shiro.bean.LoginToken;
import com.chen.frame.shiro.utils.JwtUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.util.WebUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
@RestController
@RequestMapping(value = "/shiro")
public class ShiroController {
@Autowired
private TestService testService;
@Autowired
private SysUserService sysUserService;
@Resource
private RedisUtil redisUtil;
@PostMapping(value = "/insertTest")
public ResultData insertTest(@RequestBody Test test) {
testService.insert(test);
return new ResultData(ResultStatus.SUCCESS, "ok");
}
@GetMapping(value = "/queryTest")
public ResultData queryTest() {
List<Test> list = testService.queryTest(null);
return new ResultData(ResultStatus.SUCCESS, "ok", list);
}
@PostMapping(value = "/login")
public ResultData login(@RequestBody SysUserVO sysUser, HttpServletRequest request) {
//JSONObject loginKey = getKey(request);
//String encodePass = AesUtil.aesEncode(sysUser.getUserPass(), loginKey.getString("tokenKey"));
LoginToken loginToken =
new LoginToken(sysUser.getUserName(), sysUser.getUserPass());
Subject subject = SecurityUtils.getSubject();
SysUser userParams = new SysUser();
userParams.setUserName(sysUser.getUserName());
SysUser userData = sysUserService.getUser(userParams);
if (userData == null) {
return new ResultData(ResultStatus.NOT_EXISTS_USER, "用户不存在");
}
String token = "";
try {
subject.login(loginToken);
String timeMillis = String.valueOf(System.currentTimeMillis());
token = JwtUtils.createToken(sysUser.getUserName(), timeMillis, userData.getSecSalt());
redisUtil.set(CacheKeys.LOGIN_REFRESH_TOKEN + sysUser.getUserName(), timeMillis, CacheKeys.REFRESH_TIME_LONG);
}catch (AuthenticationException e) {
return new ResultData(ResultStatus.USER_AUTH_EXCEPTION, "用户认证失败"+ e.toString());
}catch (Exception e) {
return new ResultData(ResultStatus.USER_LOGIN_EXCEPTION, "用户登录异常"+ e.toString(), null);
}
return new ResultData(ResultStatus.SUCCESS, "登录成功", token);
}
public JSONObject getKey(ServletRequest request) {
//动态生成秘钥,redis存储秘钥供之后秘钥验证使用,设置有效期5秒用完即丢弃
String tokenKey = CommonStringUtils.getRandomString(16);
String userKey = CommonStringUtils.getRandomString(6);
try {
redisUtil.set("TOKEN_KEY_"+ IpUtil.getIpFromRequest(WebUtils.toHttp(request)).toUpperCase()+userKey.toUpperCase(),tokenKey,5);
JSONObject json = new JSONObject();
json.put("tokenKey", tokenKey);
json.put("userKey", userKey.toUpperCase());
return json;
}catch (Exception e) {
return null;
}
}
}
登录后的 shiro
package com.chen.frame.manager.controller;
import com.chen.frame.common.http.ResultData;
import com.chen.frame.common.http.ResultStatus;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.springframework.cloud.client.loadbalancer.RequestData;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Slf4j
@RestController
@RequestMapping(value = "/shiroLoged")
public class ShiroLogedController {
@GetMapping(value = "/welcome")
public ResultData welcome() {
log.info("shirologed welcome");
return new ResultData(ResultStatus.SUCCESS, "ok");
}
@PostMapping(value = "shiroRole")
@RequiresRoles(value = { "factoryHeader" })
public ResultData shiroRole() {
log.info("shirologed shiroRole");
return new ResultData(ResultStatus.SUCCESS, "ok");
}
@PostMapping(value = "shiroPermission")
@RequiresPermissions(value = {"sysUser"})
public ResultData shiroPermission() {
log.info("shirologed shiroPermission");
return new ResultData(ResultStatus.SUCCESS, "ok");
}
@GetMapping(value = "/configPerms")
public ResultData configPerms() {
log.info("shirologed configPerms");
return new ResultData(ResultStatus.SUCCESS, "ok");
}
}
目前的设计是
/静态资源或登录相关页面=anon
/如 /manage/**,/shiroLoged/** = jwt,myPerms
因此在 jwtFilter 如果判断没有登录,就会发送前端Response.write登录;
1)第一步在 jwtFilter 的 isAccessAllowed 进行验证当前登录,subject.login到 jwtRealm 认证;
2)再通过 requirepermissions或requireroles进行权限验证,这两个是通过 shiroConfig 配置 advice与 AuthorizationAttributeSourceAdvisor aop拦截来进行权限验证;
3)isAccessAllowed 通过后,再走 myPerms即 permissionFiler.java 的过滤器进行权限验证;
这里在 isAccessAllowed 只验证是否登录正常的验证;
4)因此,如果是 /xxx/** = jwt,myPerms 的地址,只要有登录过,都可以访问,只是到具体功能,如果有requirepermissions或requireroles 就会走权限验证;
=====================================
参考相关 :
https://www.pianshen.com/article/1945325406/
=============================================
添加 shiro 使用 redis 做缓存支持;
1)修改 chen-common/下 redisConfig 的 RedisTemplate bean注解:
@Primary
@Bean("redisTemplate")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
// 配置连接工厂
template.setConnectionFactory(factory);
//使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值(默认使用JDK的序列化方式)
// Jackson2JsonRedisSerializer jacksonSeial = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
// 指定要序列化的域,field,get和set,以及修饰符范围,ANY是都有包括private和public
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
// 指定序列化输入的类型,类必须是非final修饰的,final修饰的类,比如String,Integer等会跑出异常
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
//jacksonSeial.setObjectMapper(om);
GenericJackson2JsonRedisSerializer jacksonSeial = new GenericJackson2JsonRedisSerializer(om);
// 值采用json序列化
template.setValueSerializer(jacksonSeial);
template.setHashValueSerializer(jacksonSeial);
//使用StringRedisSerializer来序列化和反序列化redis的key值
template.setKeySerializer(new StringRedisSerializer());
// 设置hash key 和value序列化模式
template.setHashKeySerializer(new StringRedisSerializer());
template.afterPropertiesSet();
return template;
}
@Bean("shiroRedisTemplate")
public RedisTemplate<String, Object> shiroRedisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
// 配置连接工厂
template.setConnectionFactory(factory);
//使用StringRedisSerializer来序列化和反序列化redis的key值
RedisSerializer<Object> serializer = new JdkSerializationRedisSerializer();
template.setKeySerializer(serializer);
// 设置hash key 和value序列化模式
template.setHashKeySerializer(serializer);
template.setValueSerializer(serializer);
template.setHashValueSerializer(serializer);
template.afterPropertiesSet();
return template;
}
shiroRedisTemplate 只用于实现shiro缓存,其Key也需是Jdk序列化,不然会报错;
2)再在 chen-shiro/redis/ 下添加:
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Collection;
import java.util.Set;
@Component
public class RedisCache<K, V> implements Cache<K, V> {
//redis key
private final K prefix = (K)"shiro_cache";
@Resource(name = "shiroRedisTemplate")
private RedisTemplate shiroRedisTemplate;
public RedisTemplate getRedisTemplate() {
return shiroRedisTemplate;
}
public void setRedisTemplate(RedisTemplate shiroRedisTemplate) {
this.shiroRedisTemplate = shiroRedisTemplate;
}
//------------------------
@Override
public V get(K key) throws CacheException {
Object o = shiroRedisTemplate.opsForHash().get(prefix, key);
return o == null ? null : (V) o;
}
@Override
public V put(K key, V value) throws CacheException {
V old = this.get(key);
shiroRedisTemplate.opsForHash().put(prefix,key,value);
return old;
}
@Override
public V remove(K key) throws CacheException {
V old = this.get(key);
shiroRedisTemplate.opsForHash().delete(prefix,key);
return old;
}
@Override
public void clear() throws CacheException {
shiroRedisTemplate.delete(prefix);
}
@Override
public int size() {
return shiroRedisTemplate.opsForHash().size(prefix).intValue();
}
@Override
public Set<K> keys() {
return shiroRedisTemplate.opsForHash().keys(prefix);
}
@Override
public Collection<V> values() {
return shiroRedisTemplate.opsForHash().values(prefix);
}
}
以及:
import org.apache.shiro.cache.AbstractCacheManager;
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;
import org.springframework.stereotype.Component;
@Component
public class ChenRedisManager extends AbstractCacheManager {
@Autowired
private RedisCache redisCache;
@Override
protected Cache createCache(String name) throws CacheException {
return redisCache;
}
}
3)再配置 realm 开启缓存支持; shiro-chen/realm/RealmManger
@Autowired
private ChenRedisManager chenRedisManager;
JwtRealm jwt = new JwtRealm();
jwt.setAuthenticationTokenClass(JwtToken.class);
jwt.setManagerServiceFeign(iSysUserService);
jwt.setRedisUtil(redisUtil);
jwt.setPermissionResolver(myPermissionResolver());
//开启缓存,设置缓存管理器
jwt.setCachingEnabled(true);
jwt.setAuthenticationCachingEnabled(true);
jwt.setAuthorizationCachingEnabled(true);
jwt.setCacheManager(chenRedisManager);
这样如果有注解 @RequirePermissions 或 roles 时会进入realm 方法,就会缓存权限角色 集合;
获取方法:
Subject subject = SecurityUtils.getSubject();
// 检查用户是否已经认证
if (subject.isAuthenticated()) {
DefaultWebSecurityManager securityManager = (DefaultWebSecurityManager) SecurityUtils.getSecurityManager();
if (securityManager != null && securityManager.getRealms() != null) {
for (Realm realm : securityManager.getRealms()) {
if (!(realm instanceof JwtRealm)) {
continue;
}
JwtRealm jwtRealm = (JwtRealm) realm;
Cache<?, ?> cache = jwtRealm.getAuthorizationCache();
List<AuthorizationInfo> lists = (List<AuthorizationInfo>) cache.values();
if (CollectionUtils.isEmpty(lists)) {
break;
}
for (Object info: lists) {
if (!(info instanceof AuthorizationInfo)) {
continue;
}
AuthorizationInfo authorizationInfo = (AuthorizationInfo)info;
Collection<String> collection = authorizationInfo.getStringPermissions();
userVO.setPermissions(new HashSet<>(collection));
break;
}
break;
}
}
}
2023-09-19 补充:
如果 shiro 加入了缓存,会先判断是否有缓存,会先取shiro 定义的权限数据缓存,没有的话,再走
@Override doGetAuthorizationInfo()
入口为 isPermitted 方法,这里会先判决是否支持缓存;
所以如果 直接编辑数据表,会获取不到最新的数据;使用redis-cli或其他工具删除 ;
cd /usr/local/bin/
./redis-cli -p 6379
auth 123456
keys *shiro_cache*
> "xxxxx"
> del "xxxxx" -- 要包括双引号
页面更新用户权限时,需要先删除更新shiro 缓存;