+iam缓存刷新即时生效方案优化

This commit is contained in:
mazhicheng 2020-03-14 13:20:36 +08:00
parent e9003ad3bc
commit da9148943f
11 changed files with 179 additions and 63 deletions

View File

@ -26,10 +26,10 @@ MySQL、MariaDB、ORACLE、SQLServer、PostgreSQL
以下依赖在引入diboot-core-starter依赖中可以不再引入。
:::
* **javax.servlet-api**(javax.servlet:javax.servlet-api:4.0.1)
* **spring-boot-starter-web**(org.springframework.boot:spring-boot-starter-web:2.2.1.RELEASE)
* **spring-boot-starter-web**(org.springframework.boot:spring-boot-starter-web:2.2.4.RELEASE)
* **mybatis-plus-boot-starter**(com.baomidou:mybatis-plus-boot-starter:3.2.0)
* **commons-io**(commons-io:commons-io:2.6)
* **commons-lang3**(org.apache.commons:commons-lang3:3.8.1)
* **commons-lang3**(org.apache.commons:commons-lang3:3.9)
* **fastjson**(com.alibaba:fastjson:1.2.60)
:::tip

View File

@ -44,6 +44,6 @@ public class ApiPermission implements Serializable {
private String permissionCode;
public String buildUniqueKey(){
return apiMethod + "," + apiUri + "," + permissionCode;
return className + "," + apiMethod + "," + apiUri + "," + permissionCode;
}
}

View File

@ -30,28 +30,39 @@ import java.util.Set;
@Slf4j
public class ApiPermissionExtractor {
/**
* 接口权限缓存
*/
private static List<ApiPermissionWrapper> API_PERMISSION_CACHE = null;
/**
* 唯一KEY
*/
private static Set<String> UNIQUE_KEY_SET = null;
/**
* 提取所有的权限定义
* @return
*/
public static List<ApiPermissionWrapper> extractAllApiPermissions(){
List<ApiPermissionWrapper> apiPermissionWrappers = new ArrayList<>();
// 提取rest controller
List<Object> controllerList = ContextHelper.getBeansByAnnotation(RestController.class);
extractApiPermissions(controllerList, apiPermissionWrappers);
// 提取controller
controllerList = ContextHelper.getBeansByAnnotation(Controller.class);
extractApiPermissions(controllerList, apiPermissionWrappers);
// 缓存抓取结果
return apiPermissionWrappers;
if(API_PERMISSION_CACHE == null){
API_PERMISSION_CACHE = new ArrayList<>();
UNIQUE_KEY_SET = new HashSet<>();
// 初始化
// 提取rest controller
List<Object> controllerList = ContextHelper.getBeansByAnnotation(RestController.class);
extractApiPermissions(controllerList);
// 提取controller
controllerList = ContextHelper.getBeansByAnnotation(Controller.class);
extractApiPermissions(controllerList);
}
return API_PERMISSION_CACHE;
}
/**
* 提取permission
* @param controllerList
* @param apiPermissionWrappers
*/
private static void extractApiPermissions(List<Object> controllerList, List<ApiPermissionWrapper> apiPermissionWrappers){
private static void extractApiPermissions(List<Object> controllerList){
if(V.notEmpty(controllerList)) {
for (Object obj : controllerList) {
Class controllerClass = AopUtils.getTargetClass(obj);
@ -80,7 +91,7 @@ public class ApiPermissionExtractor {
ApiPermissionWrapper wrapper = new ApiPermissionWrapper(title);
buildApiPermissionsInClass(wrapper, controllerClass, codePrefix);
if(V.notEmpty(wrapper.getChildren())){
apiPermissionWrappers.add(wrapper);
API_PERMISSION_CACHE.add(wrapper);
}
}
}
@ -101,7 +112,6 @@ public class ApiPermissionExtractor {
List<Method> annoMethods = AnnotationUtils.extractAnnotationMethods(controllerClass, BindPermission.class);
if(V.notEmpty(annoMethods)){
List<ApiPermission> apiPermissions = new ArrayList<>();
Set<String> existKey = new HashSet<>();
for(Method method : annoMethods){
// 忽略私有方法
if(Modifier.isPrivate(method.getModifiers())){
@ -121,14 +131,14 @@ public class ApiPermissionExtractor {
if(bindPermission != null){
String permissionCode = (codePrefix != null)? codePrefix+":"+bindPermission.code() : bindPermission.code();
// 提取请求url-permission code的关系
buildApiPermission(apiPermissions, controllerClass, urlPrefix, wrapper.getClassTitle(), permissionCode, methodAndUrl, bindPermission.name(), existKey);
buildApiPermission(apiPermissions, controllerClass, urlPrefix, wrapper.getClassTitle(), permissionCode, methodAndUrl, bindPermission.name());
}
// 处理RequirePermissions注解
else if(requiresPermissions != null){
String[] permissionCodes = requiresPermissions.value();
for(String permissionCode : permissionCodes){
// 提取请求url-permission code的关系
buildApiPermission(apiPermissions, controllerClass, urlPrefix, wrapper.getClassTitle(), permissionCode, methodAndUrl, null, existKey);
buildApiPermission(apiPermissions, controllerClass, urlPrefix, wrapper.getClassTitle(), permissionCode, methodAndUrl, null);
}
}
}
@ -150,7 +160,7 @@ public class ApiPermissionExtractor {
* @param apiName
*/
private static void buildApiPermission(List<ApiPermission> apiPermissions, Class controllerClass, String urlPrefix, String title,
String permissionCode, String[] methodAndUrl, String apiName, Set<String> existKey){
String permissionCode, String[] methodAndUrl, String apiName){
String requestMethod = methodAndUrl[0], url = methodAndUrl[1];
for(String m : requestMethod.split(Cons.SEPARATOR_COMMA)){
for(String u : url.split(Cons.SEPARATOR_COMMA)){
@ -158,18 +168,18 @@ public class ApiPermissionExtractor {
for(String path : urlPrefix.split(Cons.SEPARATOR_COMMA)){
ApiPermission apiPermission = new ApiPermission().setClassName(controllerClass.getName()).setClassTitle(title);
apiPermission.setApiMethod(m).setApiName(apiName).setApiUri(path + u).setPermissionCode(permissionCode).setValue(m + ":" + path + u);
if(!existKey.contains(apiPermission.buildUniqueKey())){
if(!UNIQUE_KEY_SET.contains(apiPermission.buildUniqueKey())){
apiPermissions.add(apiPermission);
existKey.add(apiPermission.buildUniqueKey());
UNIQUE_KEY_SET.add(apiPermission.buildUniqueKey());
}
}
}
else{
ApiPermission apiPermission = new ApiPermission().setClassName(controllerClass.getName()).setClassTitle(title);
apiPermission.setApiMethod(m).setApiName(apiName).setApiUri(u).setPermissionCode(permissionCode);
if(!existKey.contains(apiPermission.buildUniqueKey())){
if(!UNIQUE_KEY_SET.contains(apiPermission.buildUniqueKey())){
apiPermissions.add(apiPermission);
existKey.add(apiPermission.buildUniqueKey());
UNIQUE_KEY_SET.add(apiPermission.buildUniqueKey());
}
}
}

View File

@ -12,7 +12,9 @@ import com.diboot.iam.entity.IamAccount;
import com.diboot.iam.entity.IamRole;
import com.diboot.iam.service.IamRolePermissionService;
import com.diboot.iam.service.IamUserRoleService;
import com.diboot.iam.util.IamSecurityUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
@ -89,6 +91,8 @@ public class BaseJwtRealm extends AuthorizingRealm {
if(userObject == null){
throw new AuthenticationException("用户不存在");
}
// 清空当前用户缓存
this.clearCachedAuthorizationInfo(IamSecurityUtils.getSubject().getPrincipals());
return new SimpleAuthenticationInfo(userObject, jwtToken.getCredentials(), this.getName());
}
}

View File

@ -36,4 +36,12 @@ public interface IamAccountService extends BaseIamService<IamAccount> {
*/
boolean changePwd(ChangePwdDTO changePwdDTO, IamAccount iamAccount) throws Exception;
/**
* 获取认证账号username
* @param userType
* @param userId
* @return
*/
String getAuthAccount(String userType, Long userId);
}

View File

@ -1,5 +1,7 @@
package com.diboot.iam.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.diboot.core.exception.BusinessException;
import com.diboot.core.util.V;
import com.diboot.core.vo.Status;
@ -60,7 +62,6 @@ public class IamAccountServiceImpl extends BaseIamServiceImpl<IamAccountMapper,
}
}
@Override
public boolean changePwd(ChangePwdDTO changePwdDTO, IamAccount iamAccount) throws Exception {
// 验证账号信息是否存在
@ -88,4 +89,15 @@ public class IamAccountServiceImpl extends BaseIamServiceImpl<IamAccountMapper,
return success;
}
@Override
public String getAuthAccount(String userType, Long userId) {
LambdaQueryWrapper<IamAccount> queryWrapper = new QueryWrapper<IamAccount>().lambda()
.select(IamAccount::getAuthAccount)
.eq(IamAccount::getUserType, userType)
.eq(IamAccount::getUserId, userId);
IamAccount account = getSingleEntity(queryWrapper);
return account!=null? account.getAuthAccount() : null;
}
}

View File

@ -8,6 +8,7 @@ import com.diboot.iam.mapper.IamRolePermissionMapper;
import com.diboot.iam.service.IamFrontendPermissionService;
import com.diboot.iam.service.IamRolePermissionService;
import com.diboot.iam.service.IamRoleService;
import com.diboot.iam.util.IamSecurityUtils;
import com.diboot.iam.vo.IamFrontendPermissionVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
@ -78,7 +79,9 @@ public class IamRolePermissionServiceImpl extends BaseIamServiceImpl<IamRolePerm
IamRolePermission rolePermission = new IamRolePermission(roleId, permissionId);
rolePermissionList.add(rolePermission);
}
return createEntities(rolePermissionList);
boolean success = createEntities(rolePermissionList);
IamSecurityUtils.clearAllAuthorizationCache();
return success;
}
@Override
@ -95,9 +98,12 @@ public class IamRolePermissionServiceImpl extends BaseIamServiceImpl<IamRolePerm
IamRolePermission rolePermission = new IamRolePermission(roleId, permissionId);
rolePermissionList.add(rolePermission);
}
return createEntities(rolePermissionList);
boolean success = createEntities(rolePermissionList);
IamSecurityUtils.clearAllAuthorizationCache();
return success;
}
@Override
public IamRoleService getRoleService() {
return iamRoleService;

View File

@ -11,6 +11,7 @@ import com.diboot.iam.entity.IamRole;
import com.diboot.iam.entity.IamUserRole;
import com.diboot.iam.exception.PermissionException;
import com.diboot.iam.mapper.IamUserRoleMapper;
import com.diboot.iam.service.IamAccountService;
import com.diboot.iam.service.IamRoleService;
import com.diboot.iam.service.IamUserRoleService;
import com.diboot.iam.util.IamSecurityUtils;
@ -19,6 +20,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.validation.constraints.AssertTrue;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@ -38,7 +40,10 @@ public class IamUserRoleServiceImpl extends BaseIamServiceImpl<IamUserRoleMapper
private IamUserRoleMapper iamUserRoleMapper;
@Autowired
private IamRoleService iamRoleService;
@Autowired
private IamAccountService iamAccountService;
// 扩展接口
private IamExtensible iamExtensible;
private boolean iamExtensibleImplChecked = false;
@ -73,26 +78,39 @@ public class IamUserRoleServiceImpl extends BaseIamServiceImpl<IamUserRoleMapper
if(superAdminRoleId != null && superAdminRoleId.equals(entity.getRoleId())){
checkSuperAdminIdentity();
}
return super.createEntity(entity);
boolean success = super.createEntity(entity);
if(success){
// 清空缓存
clearUserAuthCache(entity.getUserType(), entity.getUserId());
}
return success;
}
@Override
@Transactional(rollbackFor = {Exception.class})
public boolean createEntities(Collection entityList) {
if (V.notEmpty(entityList)) {
Long superAdminRoleId = getSuperAdminRoleId();
boolean hasSuperAdmin = false;
for(Object entity : entityList){
IamUserRole iamUserRole = (IamUserRole)entity;
if(superAdminRoleId != null && superAdminRoleId.equals(iamUserRole.getRoleId())){
hasSuperAdmin = true;
}
}
if(hasSuperAdmin){
checkSuperAdminIdentity();
if (V.isEmpty(entityList)) {
return true;
}
Long superAdminRoleId = getSuperAdminRoleId();
boolean hasSuperAdmin = false;
for(Object entity : entityList){
IamUserRole iamUserRole = (IamUserRole)entity;
if(superAdminRoleId != null && superAdminRoleId.equals(iamUserRole.getRoleId())){
hasSuperAdmin = true;
}
}
return super.createEntities(entityList);
if(hasSuperAdmin){
checkSuperAdminIdentity();
}
boolean success = super.createEntities(entityList);
if(success){
// 清空用户缓存
IamUserRole entity = ((List<IamUserRole>) entityList).get(0);
clearUserAuthCache(entity.getUserType(), entity.getUserId());
}
return success;
}
@Override
@ -110,7 +128,12 @@ public class IamUserRoleServiceImpl extends BaseIamServiceImpl<IamUserRoleMapper
for(Long roleId : roleIds){
entityList.add(new IamUserRole(userType, userId, roleId));
}
return super.createEntities(entityList);
boolean success = super.createEntities(entityList);
if(success){
// 清空用户缓存
clearUserAuthCache(userType, userId);
}
return success;
}
@Override
@ -148,7 +171,12 @@ public class IamUserRoleServiceImpl extends BaseIamServiceImpl<IamUserRoleMapper
for(Long roleId : roleIds){
entityList.add(new IamUserRole(userType, userId, roleId));
}
return super.createEntities(entityList);
boolean success = super.createEntities(entityList);
if(success){
// 清空用户缓存
clearUserAuthCache(userType, userId);
}
return success;
}
/**
@ -176,4 +204,17 @@ public class IamUserRoleServiceImpl extends BaseIamServiceImpl<IamUserRoleMapper
throw new PermissionException("非超级管理员用户不可授予其他用户超级管理员权限!");
}
}
/**
* 清空用户的认证缓存以便权限变化及时生效
* @param userType
* @param userId
*/
private void clearUserAuthCache(String userType, Long userId){
String username = iamAccountService.getAuthAccount(userType, userId);
if(V.notEmpty(username)){
IamSecurityUtils.clearAuthorizationCache(username);
}
}
}

View File

@ -16,7 +16,7 @@ import org.springframework.core.env.Environment;
public class IamBasePluginManager implements PluginManager {
// 验证SQL
private static final String VALIDATE_SQL = "SELECT id FROM ${SCHEMA}.iam_permission WHERE id=0";
private static final String VALIDATE_SQL = "SELECT id FROM ${SCHEMA}.iam_role WHERE id=0";
public void initPlugin(IamBaseProperties iamBaseProperties){
// 检查数据库字典是否已存在

View File

@ -4,8 +4,13 @@ import com.diboot.core.util.S;
import com.diboot.core.util.V;
import com.diboot.iam.config.Cons;
import com.diboot.iam.entity.IamAccount;
import com.diboot.iam.jwt.BaseJwtRealm;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.mgt.RealmSecurityManager;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.ByteSource;
@ -17,6 +22,7 @@ import javax.servlet.http.HttpServletRequest;
* @version v2.0
* @date 2019/12/26
*/
@Slf4j
public class IamSecurityUtils extends SecurityUtils {
/**
@ -47,6 +53,38 @@ public class IamSecurityUtils extends SecurityUtils {
}
}
/**
* 清空指定用户账户的权限信息的缓存 使其立即生效
*/
public static void clearAuthorizationCache(String username){
RealmSecurityManager rsm = (RealmSecurityManager) IamSecurityUtils.getSecurityManager();
BaseJwtRealm baseJwtRealm = (BaseJwtRealm)rsm.getRealms().iterator().next();
if(baseJwtRealm != null){
Cache<Object, AuthorizationInfo> cache = baseJwtRealm.getAuthorizationCache();
if(cache != null) {
cache.remove(username);
log.debug("已清空账号 {} 的权限缓存,以便新权限生效.", username);
}
}
}
/**
* 清空所有权限信息的缓存 使其立即生效
*/
public static void clearAllAuthorizationCache(){
RealmSecurityManager rsm = (RealmSecurityManager) IamSecurityUtils.getSecurityManager();
BaseJwtRealm baseJwtRealm = (BaseJwtRealm)rsm.getRealms().iterator().next();
if(baseJwtRealm != null){
Cache<Object, AuthorizationInfo> cache = baseJwtRealm.getAuthorizationCache();
if(cache != null) {
for(Object key : cache.keys()) {
cache.remove(key);
}
log.debug("已清空全部登录用户的权限缓存,以便新权限生效.");
}
}
}
/***
* 对用户密码加密
* @param iamAccount

View File

@ -41,10 +41,10 @@ create unique index idx_iam_account on iam_account(auth_account, auth_type, user
-- 角色表
create table iam_role
(
id int auto_increment comment 'ID' primary key,
id bigint auto_increment comment 'ID' primary key,
name varchar(20) not null comment '名称',
code varchar(20) not null comment '编码',
description varchar(100) null comment '备注',
description varchar(100) null comment '备注',
is_deleted tinyint(1) default 0 not null comment '是否删除',
create_time timestamp default CURRENT_TIMESTAMP null comment '创建时间'
)AUTO_INCREMENT=10000 DEFAULT CHARSET=utf8 COMMENT '角色';
@ -52,42 +52,39 @@ create table iam_role
-- 用户角色表
create table iam_user_role
(
id int auto_increment comment 'ID' primary key,
id bigint auto_increment comment 'ID' primary key,
user_type varchar(100) default 'IamUser' not null comment '用户类型',
user_id bigint not null comment '用户ID',
role_id int not null comment '角色ID',
role_id bigint not null comment '角色ID',
is_deleted tinyint(1) default 0 not null comment '是否删除',
create_time timestamp default CURRENT_TIMESTAMP not null comment '创建时间'
)AUTO_INCREMENT=10000 DEFAULT CHARSET=utf8 COMMENT '用户角色关联';
-- 索引
create index idx_iam_user_role on iam_user_role (user_type, user_id);
-- 权限表
create table iam_permission
-- 前端资源权限表
create table iam_frontend_permission
(
id int auto_increment comment 'ID' primary key,
parent_id int default 0 not null comment '上级ID',
application varchar(50) default 'MS' not null comment '所属应用',
type varchar(10) default 'MENU' not null comment '权限类别',
name varchar(20) not null comment '名称',
code varchar(50) null comment '编码',
operation_name varchar(50) null comment '操作名称',
operation_code varchar(50) null comment '操作编码',
sort_id smallint(6) default 999 not null comment '排序号',
extdata varchar(100) null comment '扩展属性',
id bigint auto_increment comment 'ID' primary key,
parent_id bigint default 0 not null comment '父级菜单',
display_type varchar(20) not null comment '展现类型',
display_name varchar(100) not null comment '显示名称',
frontend_code varchar(100) not null comment '前端编码',
api_set varchar(5120) null comment '接口列表',
is_deleted tinyint(1) default 0 not null comment '是否删除',
create_time timestamp default CURRENT_TIMESTAMP not null comment '创建时间',
update_time timestamp null on update CURRENT_TIMESTAMP comment '更新时间'
)AUTO_INCREMENT=10000 DEFAULT CHARSET=utf8 COMMENT '权限';
)AUTO_INCREMENT=10000 DEFAULT CHARSET=utf8 COMMENT '前端菜单';
-- 索引
create index idx_iam_permission on iam_permission (code);
create index idx_iam_frontend_permission on iam_frontend_permission (parent_id);
-- 角色-权限
create table iam_role_permission
(
id int auto_increment comment 'ID' primary key,
role_id int not null comment '角色ID',
permission_id int not null comment '权限ID',
id bigint auto_increment comment 'ID' primary key,
role_id bigint not null comment '角色ID',
permission_id bigint not null comment '权限ID',
is_deleted tinyint(1) default 0 not null comment '是否删除',
create_time timestamp default CURRENT_TIMESTAMP not null comment '创建时间'
)AUTO_INCREMENT=10000 DEFAULT CHARSET=utf8 COMMENT '角色权限';