diff --git a/diboot-docs/guide/diboot-core/安装.md b/diboot-docs/guide/diboot-core/安装.md index b3c7221..f9c2f0b 100644 --- a/diboot-docs/guide/diboot-core/安装.md +++ b/diboot-docs/guide/diboot-core/安装.md @@ -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 diff --git a/iam-base-starter/src/main/java/com/diboot/iam/annotation/process/ApiPermission.java b/iam-base-starter/src/main/java/com/diboot/iam/annotation/process/ApiPermission.java index f6811ad..89bbd69 100644 --- a/iam-base-starter/src/main/java/com/diboot/iam/annotation/process/ApiPermission.java +++ b/iam-base-starter/src/main/java/com/diboot/iam/annotation/process/ApiPermission.java @@ -44,6 +44,6 @@ public class ApiPermission implements Serializable { private String permissionCode; public String buildUniqueKey(){ - return apiMethod + "," + apiUri + "," + permissionCode; + return className + "," + apiMethod + "," + apiUri + "," + permissionCode; } } \ No newline at end of file diff --git a/iam-base-starter/src/main/java/com/diboot/iam/annotation/process/ApiPermissionExtractor.java b/iam-base-starter/src/main/java/com/diboot/iam/annotation/process/ApiPermissionExtractor.java index 7052165..63b506d 100644 --- a/iam-base-starter/src/main/java/com/diboot/iam/annotation/process/ApiPermissionExtractor.java +++ b/iam-base-starter/src/main/java/com/diboot/iam/annotation/process/ApiPermissionExtractor.java @@ -30,28 +30,39 @@ import java.util.Set; @Slf4j public class ApiPermissionExtractor { + /** + * 接口权限缓存 + */ + private static List API_PERMISSION_CACHE = null; + /** + * 唯一KEY + */ + private static Set UNIQUE_KEY_SET = null; + /** * 提取所有的权限定义 * @return */ public static List extractAllApiPermissions(){ - List apiPermissionWrappers = new ArrayList<>(); - // 提取rest controller - List 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 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 controllerList, List apiPermissionWrappers){ + private static void extractApiPermissions(List 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 annoMethods = AnnotationUtils.extractAnnotationMethods(controllerClass, BindPermission.class); if(V.notEmpty(annoMethods)){ List apiPermissions = new ArrayList<>(); - Set 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 apiPermissions, Class controllerClass, String urlPrefix, String title, - String permissionCode, String[] methodAndUrl, String apiName, Set 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()); } } } diff --git a/iam-base-starter/src/main/java/com/diboot/iam/jwt/BaseJwtRealm.java b/iam-base-starter/src/main/java/com/diboot/iam/jwt/BaseJwtRealm.java index 3f78183..4125080 100644 --- a/iam-base-starter/src/main/java/com/diboot/iam/jwt/BaseJwtRealm.java +++ b/iam-base-starter/src/main/java/com/diboot/iam/jwt/BaseJwtRealm.java @@ -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()); } } diff --git a/iam-base-starter/src/main/java/com/diboot/iam/service/IamAccountService.java b/iam-base-starter/src/main/java/com/diboot/iam/service/IamAccountService.java index 7a66e7b..7571ad0 100644 --- a/iam-base-starter/src/main/java/com/diboot/iam/service/IamAccountService.java +++ b/iam-base-starter/src/main/java/com/diboot/iam/service/IamAccountService.java @@ -36,4 +36,12 @@ public interface IamAccountService extends BaseIamService { */ boolean changePwd(ChangePwdDTO changePwdDTO, IamAccount iamAccount) throws Exception; + /** + * 获取认证账号username + * @param userType + * @param userId + * @return + */ + String getAuthAccount(String userType, Long userId); + } \ No newline at end of file diff --git a/iam-base-starter/src/main/java/com/diboot/iam/service/impl/IamAccountServiceImpl.java b/iam-base-starter/src/main/java/com/diboot/iam/service/impl/IamAccountServiceImpl.java index d14c696..8d2937d 100644 --- a/iam-base-starter/src/main/java/com/diboot/iam/service/impl/IamAccountServiceImpl.java +++ b/iam-base-starter/src/main/java/com/diboot/iam/service/impl/IamAccountServiceImpl.java @@ -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 queryWrapper = new QueryWrapper().lambda() + .select(IamAccount::getAuthAccount) + .eq(IamAccount::getUserType, userType) + .eq(IamAccount::getUserId, userId); + IamAccount account = getSingleEntity(queryWrapper); + return account!=null? account.getAuthAccount() : null; + } + } diff --git a/iam-base-starter/src/main/java/com/diboot/iam/service/impl/IamRolePermissionServiceImpl.java b/iam-base-starter/src/main/java/com/diboot/iam/service/impl/IamRolePermissionServiceImpl.java index 5ba6ec2..7750106 100644 --- a/iam-base-starter/src/main/java/com/diboot/iam/service/impl/IamRolePermissionServiceImpl.java +++ b/iam-base-starter/src/main/java/com/diboot/iam/service/impl/IamRolePermissionServiceImpl.java @@ -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) entityList).get(0); + clearUserAuthCache(entity.getUserType(), entity.getUserId()); + } + return success; } @Override @@ -110,7 +128,12 @@ public class IamUserRoleServiceImpl extends BaseIamServiceImpl 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 cache = baseJwtRealm.getAuthorizationCache(); + if(cache != null) { + for(Object key : cache.keys()) { + cache.remove(key); + } + log.debug("已清空全部登录用户的权限缓存,以便新权限生效."); + } + } + } + /*** * 对用户密码加密 * @param iamAccount diff --git a/iam-base-starter/src/main/resources/META-INF/sql/init-iam-base-mysql.sql b/iam-base-starter/src/main/resources/META-INF/sql/init-iam-base-mysql.sql index 8fe19a3..83d77d0 100644 --- a/iam-base-starter/src/main/resources/META-INF/sql/init-iam-base-mysql.sql +++ b/iam-base-starter/src/main/resources/META-INF/sql/init-iam-base-mysql.sql @@ -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 '角色权限';