diff --git a/README.md b/README.md index ef37742..00e9070 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,11 @@ RBAC的角色权限+基于Shiro的细粒度权限控制 #### 2、@AuthorizationWrapper 类/方法注解,在保证shiro的@RequirePermissions注解的功能基础上,增加名称、权限前缀特性,使用方式同@RequiresPermissions +#### 3、@AuthorizationCache +方法注解,在资源授权校验过程中,系统会频繁与数据库进行交互,故而提供缓存机制 + * 缓存时机:缓存会在用户第一次进行权限验证的之后缓存数据 + * 当前注解作用:如果通过系统调整权限,只需要将该注解加在更新或添加权限处,将会清空权限缓存,下次进入将重新加载权限 + #### 3、AuthorizationProperties 提供一些权限相关的配置,主要包括: - 权限环境变量:提供dev、test、prod三种选项 @@ -54,6 +59,11 @@ diboot.shiro.auth.env=dev diboot.shiro.auth.has-all-permissions-role-list[0]=ALL1 diboot.shiro.auth.has-all-permissions-role-list[1]=ALL2 diboot.shiro.auth.has-all-permissions-role-list[2]=ALL3 +#配置权限缓存机制 +##是否开启缓存 +diboot.shiro.cache.permission-caching-enabled=true +##缓存方式:暂时提供shiro内置的内存缓存 +diboot.shiro.cache.cache-way=memory ``` #### 4、AuthorizationStorage diff --git a/diboot-example/src/main/java/com/diboot/example/controller/DepartmentController.java b/diboot-example/src/main/java/com/diboot/example/controller/DepartmentController.java index 069c9f7..f4d33b0 100644 --- a/diboot-example/src/main/java/com/diboot/example/controller/DepartmentController.java +++ b/diboot-example/src/main/java/com/diboot/example/controller/DepartmentController.java @@ -163,8 +163,8 @@ public class DepartmentController extends BaseCrudRestController { /* * 根据组织ID获取部门kv list * */ - @GetMapping("/getDepartment/{orgId}") - public JsonResult getDepartment(@PathVariable Long orgId, HttpServletRequest request){ + @GetMapping("/getDepartmentKV/{orgId}") + public JsonResult getDepartmentKV(@PathVariable Long orgId, HttpServletRequest request){ Wrapper wrapper = null; //获取部门KV wrapper = new QueryWrapper() @@ -176,6 +176,21 @@ public class DepartmentController extends BaseCrudRestController { return new JsonResult(deptKvList); } + /* + * 根据组织ID获取部门list + * */ + @GetMapping("/getDepartmentList/{orgId}") + public JsonResult getDepartmentList(@PathVariable Long orgId, HttpServletRequest request) throws Exception { + // 构建分页 + Pagination pagination = buildPagination(request); + Wrapper wrapper = new QueryWrapper() + .lambda() + .eq(Department::getOrgId, orgId); + List voList = departmentService.getViewObjectList(wrapper, pagination, DepartmentVO.class); + + return new JsonResult(voList); + } + @Override protected BaseService getService() { return departmentService; diff --git a/diboot-example/src/main/java/com/diboot/example/controller/OrganizationController.java b/diboot-example/src/main/java/com/diboot/example/controller/OrganizationController.java index cb4d14f..53c92bf 100644 --- a/diboot-example/src/main/java/com/diboot/example/controller/OrganizationController.java +++ b/diboot-example/src/main/java/com/diboot/example/controller/OrganizationController.java @@ -6,6 +6,7 @@ import com.diboot.core.binding.RelationsBinder; import com.diboot.core.controller.BaseCrudRestController; import com.diboot.core.service.BaseService; import com.diboot.core.service.DictionaryService; +import com.diboot.core.util.V; import com.diboot.core.vo.JsonResult; import com.diboot.core.vo.KeyValue; import com.diboot.core.vo.Pagination; @@ -36,12 +37,13 @@ public class OrganizationController extends BaseCrudRestController { private DictionaryService dictionaryService; @GetMapping("/list") - public JsonResult getVOList(Organization organization, Pagination pagination, HttpServletRequest request) throws Exception{ - QueryWrapper queryWrapper = super.buildQueryWrapper(organization); + public JsonResult getVOList(HttpServletRequest request) throws Exception{ + QueryWrapper queryWrapper = buildQuery(request); + queryWrapper.lambda().eq(Organization::getParentId, 0); + // 构建分页 + Pagination pagination = buildPagination(request); // 查询当前页的Entity主表数据 - List entityList = organizationService.getEntityList(queryWrapper, pagination); - //筛选出在列表页展示的字段 - List voList = RelationsBinder.convertAndBind(entityList, OrganizationVO.class); + List voList = organizationService.getOrganizatioList(queryWrapper, pagination); // 返回结果 return new JsonResult(Status.OK, voList).bindPagination(pagination); } @@ -83,10 +85,11 @@ public class OrganizationController extends BaseCrudRestController { @GetMapping("/attachMore") public JsonResult attachMore(HttpServletRequest request, ModelMap modelMap){ Wrapper wrapper = null; - //获取组织机构KV + //获取父组织机构KV wrapper = new QueryWrapper() .lambda() - .select(Organization::getName, Organization::getId); + .select(Organization::getName, Organization::getId) + .eq(Organization::getParentId, 0); List orgKvList = organizationService.getKeyValueList(wrapper); modelMap.put("orgKvList", orgKvList); @@ -97,6 +100,25 @@ public class OrganizationController extends BaseCrudRestController { return new JsonResult(modelMap); } + @GetMapping("/getOrgTree") + public JsonResult getOrgTree() throws Exception{ + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.lambda().eq(Organization::getParentId, 0); + List orgList = organizationService.getEntityList(queryWrapper); + List voList = RelationsBinder.convertAndBind(orgList, OrganizationVO.class); + if(V.notEmpty(voList)){ + for(OrganizationVO vo : voList){ + queryWrapper = new QueryWrapper<>(); + queryWrapper.lambda() + .eq(Organization::getParentId, vo.getId()); + List childList = organizationService.getEntityList(queryWrapper); + List childvVoList = RelationsBinder.convertAndBind(childList, OrganizationVO.class); + vo.setChildren(childvVoList); + } + } + return new JsonResult(orgList); + } + @Override protected BaseService getService() { return organizationService; diff --git a/diboot-example/src/main/java/com/diboot/example/controller/PositionController.java b/diboot-example/src/main/java/com/diboot/example/controller/PositionController.java index 2dba533..0e7b77b 100644 --- a/diboot-example/src/main/java/com/diboot/example/controller/PositionController.java +++ b/diboot-example/src/main/java/com/diboot/example/controller/PositionController.java @@ -114,8 +114,8 @@ public class PositionController extends BaseCrudRestController { /* * 根据部门ID获取职位kv list * */ - @GetMapping("/getPosition/{deptId}") - public JsonResult getPosition(@PathVariable Long deptId, HttpServletRequest request){ + @GetMapping("/getPositionKV/{deptId}") + public JsonResult getPositionKV(@PathVariable Long deptId, HttpServletRequest request){ Wrapper wrapper = null; List positionIdList = new ArrayList<>(); wrapper = new LambdaQueryWrapper() diff --git a/diboot-example/src/main/java/com/diboot/example/controller/SysUserController.java b/diboot-example/src/main/java/com/diboot/example/controller/SysUserController.java index b020df2..61f612e 100644 --- a/diboot-example/src/main/java/com/diboot/example/controller/SysUserController.java +++ b/diboot-example/src/main/java/com/diboot/example/controller/SysUserController.java @@ -18,10 +18,15 @@ import com.diboot.example.service.DepartmentService; import com.diboot.example.service.SysUserService; import com.diboot.example.vo.SysUserListVO; import com.diboot.example.vo.SysUserVO; +import com.diboot.shiro.authz.annotation.AuthorizationPrefix; +import com.diboot.shiro.authz.annotation.AuthorizationWrapper; +import com.diboot.shiro.entity.Permission; import com.diboot.shiro.entity.Role; +import com.diboot.shiro.service.PermissionService; import com.diboot.shiro.service.RoleService; import com.diboot.shiro.util.JwtHelper; import com.diboot.shiro.vo.RoleVO; +import org.apache.shiro.authz.annotation.RequiresPermissions; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -34,6 +39,7 @@ import java.util.List; @RestController @RequestMapping("/sysUser") +@AuthorizationPrefix(name = "用户管理", code = "sysUser", prefix = "sysUser") public class SysUserController extends BaseCrudRestController { private static final Logger logger = LoggerFactory.getLogger(SysUserController.class); @@ -47,12 +53,19 @@ public class SysUserController extends BaseCrudRestController { @Autowired private RoleService roleService; + @Autowired + private PermissionService permissionService; + @Autowired private DepartmentService departmentService; + @GetMapping("/list") - public JsonResult getVOList(SysUser sysUser, Pagination pagination, HttpServletRequest request) throws Exception{ - QueryWrapper queryWrapper = super.buildQueryWrapper(sysUser); + @AuthorizationWrapper(value = @RequiresPermissions("list"), name = "列表") + public JsonResult getVOList(HttpServletRequest request) throws Exception{ + QueryWrapper queryWrapper = buildQuery(request); + // 构建分页 + Pagination pagination = buildPagination(request); // 查询当前页的Entity主表数据 List voList = sysUserService.getSysUserList(queryWrapper, pagination); //筛选出在列表页展示的字段 @@ -67,6 +80,7 @@ public class SysUserController extends BaseCrudRestController { * @throws Exception */ @PostMapping("/") + @AuthorizationWrapper(value = @RequiresPermissions("create"), name = "新建") public JsonResult createEntity(@RequestBody SysUser entity, BindingResult result, HttpServletRequest request) throws Exception{ boolean success = sysUserService.createSysUser(entity); @@ -85,6 +99,7 @@ public class SysUserController extends BaseCrudRestController { * @throws Exception */ @PutMapping("/{id}") + @AuthorizationWrapper(value = @RequiresPermissions("update"), name = "更新") public JsonResult updateModel(@PathVariable("id")Long id, @RequestBody SysUser entity, BindingResult result, HttpServletRequest request) throws Exception{ // Model属性值验证结果 @@ -107,6 +122,7 @@ public class SysUserController extends BaseCrudRestController { * @throws Exception */ @GetMapping("/{id}") + @AuthorizationWrapper(value = @RequiresPermissions("read"), name = "读取") public JsonResult getModel(@PathVariable("id")Long id, HttpServletRequest request) throws Exception{ SysUserVO sysUserVO = sysUserService.getSysUser(id); @@ -120,6 +136,7 @@ public class SysUserController extends BaseCrudRestController { * @throws Exception */ @DeleteMapping("/{id}") + @AuthorizationWrapper(value = @RequiresPermissions("delete"), name = "删除") public JsonResult deleteModel(@PathVariable("id")Long id, HttpServletRequest request) throws Exception{ boolean success = sysUserService.deleteSysUser(id); if(success){ @@ -215,7 +232,16 @@ public class SysUserController extends BaseCrudRestController { List roleVOList = roleService.getRelatedRoleAndPermissionListByUser(SysUser.class.getSimpleName(), user.getId()); if (V.isEmpty(roleVOList)){ - return new JsonResult(Status.FAIL_OPERATION, new String[]{"获取用户角色失败"}); + return new JsonResult(Status.FAIL_OPERATION, new String[]{"用户未配置角色,获取数据失败"}); + } + + // 如果具有管理员角色,则赋予所有权限 + for (RoleVO roleVO : roleVOList){ + if (roleVO.isAdmin()){ + List allPermissionList = permissionService.getEntityList(null); + roleVO.setPermissionList(allPermissionList); + break; + } } user.setRoleVOList(roleVOList); diff --git a/diboot-example/src/main/java/com/diboot/example/service/OrganizationService.java b/diboot-example/src/main/java/com/diboot/example/service/OrganizationService.java index b89cfbb..de00f98 100644 --- a/diboot-example/src/main/java/com/diboot/example/service/OrganizationService.java +++ b/diboot-example/src/main/java/com/diboot/example/service/OrganizationService.java @@ -1,7 +1,12 @@ package com.diboot.example.service; +import com.baomidou.mybatisplus.core.conditions.Wrapper; import com.diboot.core.service.BaseService; +import com.diboot.core.vo.Pagination; import com.diboot.example.entity.Organization; +import com.diboot.example.vo.OrganizationVO; + +import java.util.List; /** * 单位相关Service @@ -11,4 +16,6 @@ import com.diboot.example.entity.Organization; */ public interface OrganizationService extends BaseService { + List getOrganizatioList(Wrapper wrapper, Pagination pagination); + } diff --git a/diboot-example/src/main/java/com/diboot/example/service/impl/OrganizationServiceImpl.java b/diboot-example/src/main/java/com/diboot/example/service/impl/OrganizationServiceImpl.java index a36cb55..3773859 100644 --- a/diboot-example/src/main/java/com/diboot/example/service/impl/OrganizationServiceImpl.java +++ b/diboot-example/src/main/java/com/diboot/example/service/impl/OrganizationServiceImpl.java @@ -1,12 +1,20 @@ package com.diboot.example.service.impl; +import com.baomidou.mybatisplus.core.conditions.Wrapper; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.diboot.core.binding.RelationsBinder; import com.diboot.core.service.impl.BaseServiceImpl; +import com.diboot.core.util.V; +import com.diboot.core.vo.Pagination; import com.diboot.example.entity.Organization; import com.diboot.example.mapper.OrganizationMapper; import com.diboot.example.service.OrganizationService; +import com.diboot.example.vo.OrganizationVO; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +import java.util.List; + /** * 单位相关Service实现 * @author Mazhicheng @@ -17,4 +25,17 @@ import org.springframework.stereotype.Service; @Slf4j public class OrganizationServiceImpl extends BaseServiceImpl implements OrganizationService { + @Override + public List getOrganizatioList(Wrapper wrapper, Pagination pagination) { + List voList = super.getViewObjectList(wrapper, pagination, OrganizationVO.class); + if(V.notEmpty(voList)){ + for(OrganizationVO vo : voList){ + wrapper = new LambdaQueryWrapper().eq(Organization::getParentId, vo.getId()); + List orgList = super.getEntityList(wrapper); + List orgVoList = RelationsBinder.convertAndBind(orgList, OrganizationVO.class); + vo.setChildren(orgVoList); + } + } + return voList; + } } diff --git a/diboot-example/src/main/java/com/diboot/example/vo/OrganizationVO.java b/diboot-example/src/main/java/com/diboot/example/vo/OrganizationVO.java index f9f390a..7d5c999 100644 --- a/diboot-example/src/main/java/com/diboot/example/vo/OrganizationVO.java +++ b/diboot-example/src/main/java/com/diboot/example/vo/OrganizationVO.java @@ -5,6 +5,8 @@ import com.diboot.core.binding.annotation.BindField; import com.diboot.example.entity.Organization; import lombok.Data; +import java.util.List; + /** * @author wangyongliang * @version v2.0 @@ -22,4 +24,6 @@ public class OrganizationVO extends Organization { @BindDict(type = "INDUSTRY", field = "industry") private String industryLabel; + private List children; + } \ No newline at end of file diff --git a/diboot-example/src/main/resources/application.properties.default b/diboot-example/src/main/resources/application.properties.default index 72e2088..1d6ec9a 100644 --- a/diboot-example/src/main/resources/application.properties.default +++ b/diboot-example/src/main/resources/application.properties.default @@ -98,6 +98,10 @@ diboot.shiro.auth.has-all-permissions-role-list[0]=ALL1 diboot.shiro.auth.has-all-permissions-role-list[1]=ALL2 diboot.shiro.auth.has-all-permissions-role-list[2]=ALL3 +#权限缓存机制 +diboot.shiro.cache.permission-caching-enabled=true +diboot.shiro.cache.cache-way=memory + #------web页面访问的时候需要如下配置---- spring.mvc.view.prefix=/static spring.mvc.view.suffix=.html \ No newline at end of file diff --git a/diboot-shiro/build.gradle b/diboot-shiro/build.gradle index 53c96a8..867fa78 100644 --- a/diboot-shiro/build.gradle +++ b/diboot-shiro/build.gradle @@ -5,6 +5,7 @@ dependencies { // compile("org.springframework.boot:spring-boot-configuration-processor") compile("org.apache.shiro:shiro-spring:1.4.1") + compile("org.aspectj:aspectjweaver") compile("com.auth0:java-jwt:3.4.1", "io.jsonwebtoken:jjwt:0.9.1") diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/authz/annotation/AuthorizationCache.java b/diboot-shiro/src/main/java/com/diboot/shiro/authz/annotation/AuthorizationCache.java new file mode 100644 index 0000000..1d9c30d --- /dev/null +++ b/diboot-shiro/src/main/java/com/diboot/shiro/authz/annotation/AuthorizationCache.java @@ -0,0 +1,20 @@ +package com.diboot.shiro.authz.annotation; + +import java.lang.annotation.*; + +/** + * 权限缓存 + *

+ * 缓存目的:在资源授权校验过程中,系统会频繁与数据库进行交互,故而提供缓存机制
+ * 缓存时机:缓存会在用户第一次进行权限验证的之后缓存数据 + * 当前注解作用:如果通过系统调整角色的权限,只需要将该注解加在更新或添加权限处,将会清空缓存,下次进入将重新加载 + *

+ * @author : wee + * @version v1.0 + * @Date 2019-07-23 09:27 + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface AuthorizationCache { +} diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/authz/aspect/CacheHandler.java b/diboot-shiro/src/main/java/com/diboot/shiro/authz/aspect/CacheHandler.java new file mode 100644 index 0000000..438f4d0 --- /dev/null +++ b/diboot-shiro/src/main/java/com/diboot/shiro/authz/aspect/CacheHandler.java @@ -0,0 +1,57 @@ +package com.diboot.shiro.authz.aspect; + +import com.diboot.core.util.V; +import com.diboot.shiro.authz.annotation.AuthorizationCache; +import com.diboot.shiro.jwt.BaseJwtRealm; +import lombok.extern.slf4j.Slf4j; +import org.apache.shiro.cache.Cache; +import org.apache.shiro.cache.CacheManager; +import org.apache.tomcat.util.http.parser.Authorization; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.AfterReturning; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * 当有操作的时候,自动更新被注解的相关数据 + * @author : wee + * @version : v2.0 + * @Date 2019-07-24 23:20 + */ +@Slf4j +@Aspect +@Component +public class CacheHandler{ + + private static final String DEFAULT_AUTHORIZATION_CACHE_SUFFIX = ".authorizationCache"; + + @Autowired + private CacheManager cacheManager; + + /** + * 设置切片 + */ + @Pointcut("@annotation(com.diboot.shiro.authz.annotation.AuthorizationCache)") + public void proxyAspect() {} + + /** + * 当请求{@link AuthorizationCache}注解的方法执行完成后,自动触发此处切面 + * 作用:重新缓存权限和方法 + * @param joinPoint + */ + @AfterReturning("proxyAspect()") + public void afterReturning(JoinPoint joinPoint) { + try { + log.info("【修改权限】==> 正在调用【{}#{}()】方法修改!", joinPoint.getThis().getClass(), joinPoint.getSignature().getName()); + Cache cache = cacheManager.getCache(BaseJwtRealm.class.getName() + DEFAULT_AUTHORIZATION_CACHE_SUFFIX); + //统一删除所有的缓存,所有用户需重新加载缓存 + if (V.notEmpty(cache) && cache.size() > 0) { + cache.clear(); + } + } catch (Exception e) { + log.info("【修改权限】==> 调用【{}#{}()】异常:", joinPoint.getThis().getClass(), joinPoint.getSignature().getName(), e); + } + } +} diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/authz/cache/MemoryCondition.java b/diboot-shiro/src/main/java/com/diboot/shiro/authz/cache/MemoryCondition.java new file mode 100644 index 0000000..c32f8b9 --- /dev/null +++ b/diboot-shiro/src/main/java/com/diboot/shiro/authz/cache/MemoryCondition.java @@ -0,0 +1,26 @@ +package com.diboot.shiro.authz.cache; + +import com.diboot.shiro.authz.properties.AuthCacheProperties; +import org.springframework.context.annotation.Condition; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.core.type.AnnotatedTypeMetadata; + +/** + * + * memory条件类:用与创建memory缓存对象 + * @author : wee + * @version : v2.0 + * @Date 2019-08-05 14:39 + */ +public class MemoryCondition implements Condition { + + @Override + public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { + //获取配置信息 + Boolean enableCached = context.getEnvironment().getProperty(AuthCacheProperties.CACHE_PREFIX + ".permission-caching-enabled", Boolean.class); + enableCached = enableCached == null ? false : enableCached; + AuthCacheProperties.CacheWay cacheWay = context.getEnvironment().getProperty(AuthCacheProperties.CACHE_PREFIX + ".cache-way", AuthCacheProperties.CacheWay.class); + cacheWay = cacheWay == null ? AuthCacheProperties.CacheWay.MEMORY : cacheWay; + return enableCached && AuthCacheProperties.CacheWay.MEMORY.equals(cacheWay); + } +} diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/authz/cache/RedisCache.java b/diboot-shiro/src/main/java/com/diboot/shiro/authz/cache/RedisCache.java new file mode 100644 index 0000000..e227843 --- /dev/null +++ b/diboot-shiro/src/main/java/com/diboot/shiro/authz/cache/RedisCache.java @@ -0,0 +1,51 @@ +package com.diboot.shiro.authz.cache; + +import org.apache.shiro.cache.Cache; +import org.apache.shiro.cache.CacheException; + +import java.util.Collection; +import java.util.Set; + +/** + * TODO redis缓存处理,等候完善 + * @author : wee + * @version : v2.0 + * @Date 2019-08-05 16:20 + */ +public class RedisCache implements Cache { + @Override + public V get(K k) throws CacheException { + return null; + } + + @Override + public V put(K k, V v) throws CacheException { + return null; + } + + @Override + public V remove(K k) throws CacheException { + return null; + } + + @Override + public void clear() throws CacheException { + //TODO 根据模糊key获取redis中所有的权限,然后统一清空 + } + + @Override + public int size() { + //TODO 模糊key获取redis所有当前系统中的权限 + return 0; + } + + @Override + public Set keys() { + return null; + } + + @Override + public Collection values() { + return null; + } +} diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/authz/cache/RedisCacheManager.java b/diboot-shiro/src/main/java/com/diboot/shiro/authz/cache/RedisCacheManager.java new file mode 100644 index 0000000..d5de00a --- /dev/null +++ b/diboot-shiro/src/main/java/com/diboot/shiro/authz/cache/RedisCacheManager.java @@ -0,0 +1,28 @@ +package com.diboot.shiro.authz.cache; + +import lombok.extern.slf4j.Slf4j; +import org.apache.shiro.cache.Cache; +import org.apache.shiro.cache.CacheException; +import org.apache.shiro.cache.CacheManager; +import org.apache.shiro.util.Destroyable; + +/** + * TODO redis缓存管理:暂时不提供 + * @author : wee + * @version : v2.0 + * @Date 2019-08-05 16:15 + */ +@Slf4j +public class RedisCacheManager implements CacheManager, Destroyable { + + @Override + public Cache getCache(String s) throws CacheException { + log.error("【缓存】<== 尚未实现redis缓存,暂时不可用,请选择内存缓存"); + return null; + } + + @Override + public void destroy() throws Exception { + //清除redis内容 + } +} diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/authz/cache/RedisCondition.java b/diboot-shiro/src/main/java/com/diboot/shiro/authz/cache/RedisCondition.java new file mode 100644 index 0000000..201f17a --- /dev/null +++ b/diboot-shiro/src/main/java/com/diboot/shiro/authz/cache/RedisCondition.java @@ -0,0 +1,24 @@ +package com.diboot.shiro.authz.cache; + +import com.diboot.shiro.authz.properties.AuthCacheProperties; +import org.springframework.context.annotation.Condition; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.core.type.AnnotatedTypeMetadata; + +/** + * redis条件类:用与创建redis缓存对象 + * @author : wee + * @version : v2.0 + * @Date 2019-08-05 14:35 + */ +public class RedisCondition implements Condition { + + @Override + public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { + //获取配置信息 + Boolean enableCached = context.getEnvironment().getProperty(AuthCacheProperties.CACHE_PREFIX + ".permission-caching-enabled", Boolean.class); + enableCached = enableCached == null ? false : enableCached; + AuthCacheProperties.CacheWay cacheWay = context.getEnvironment().getProperty(AuthCacheProperties.CACHE_PREFIX + ".cache-way", AuthCacheProperties.CacheWay.class); + return enableCached && AuthCacheProperties.CacheWay.REDIS.equals(cacheWay); + } +} diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/authz/config/AuthorizationAutoConfiguration.java b/diboot-shiro/src/main/java/com/diboot/shiro/authz/config/AuthorizationAutoConfiguration.java index e066d79..652c7cb 100644 --- a/diboot-shiro/src/main/java/com/diboot/shiro/authz/config/AuthorizationAutoConfiguration.java +++ b/diboot-shiro/src/main/java/com/diboot/shiro/authz/config/AuthorizationAutoConfiguration.java @@ -1,12 +1,12 @@ package com.diboot.shiro.authz.config; import com.diboot.shiro.authz.properties.AuthorizationProperties; +import com.diboot.shiro.authz.properties.AuthCacheProperties; import com.diboot.shiro.authz.storage.AuthorizationStorage; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.core.annotation.Order; /** * 权限配置 @@ -15,7 +15,7 @@ import org.springframework.core.annotation.Order; * @Date 2019-06-27 10:30 */ @Configuration -@EnableConfigurationProperties(AuthorizationProperties.class) +@EnableConfigurationProperties({AuthorizationProperties.class, AuthCacheProperties.class}) public class AuthorizationAutoConfiguration { @Autowired diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/authz/properties/AuthCacheProperties.java b/diboot-shiro/src/main/java/com/diboot/shiro/authz/properties/AuthCacheProperties.java new file mode 100644 index 0000000..7469f01 --- /dev/null +++ b/diboot-shiro/src/main/java/com/diboot/shiro/authz/properties/AuthCacheProperties.java @@ -0,0 +1,50 @@ +package com.diboot.shiro.authz.properties; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.Getter; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +/** + * @author : wee + * @version : v2.0 + * @Date 2019-07-29 15:59 + */ +@Data +@ConfigurationProperties(AuthCacheProperties.CACHE_PREFIX) +public class AuthCacheProperties { + + public final static String CACHE_PREFIX = "diboot.shiro.cache"; + + /** + * 是否开启权限缓存:默认false + */ + private boolean permissionCachingEnabled = false; + + /** + * 缓存方式:默认内存缓存 + */ + private CacheWay cacheWay = CacheWay.MEMORY; + + /** + * 缓存方式 + *

+ * 当前提供本地缓存 + *

+ */ + @Getter + @AllArgsConstructor + public enum CacheWay { + /** + * 内存缓存 + */ + MEMORY, + /** + * redis缓存: TODO 尚未实现,暂不可用 + */ + @Deprecated + REDIS; + } + +} diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/authz/properties/AuthorizationProperties.java b/diboot-shiro/src/main/java/com/diboot/shiro/authz/properties/AuthorizationProperties.java index 454653f..dedb4a7 100644 --- a/diboot-shiro/src/main/java/com/diboot/shiro/authz/properties/AuthorizationProperties.java +++ b/diboot-shiro/src/main/java/com/diboot/shiro/authz/properties/AuthorizationProperties.java @@ -4,13 +4,12 @@ import lombok.AllArgsConstructor; import lombok.Data; import lombok.Getter; import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.core.annotation.Order; -import java.util.ArrayList; import java.util.List; /** * 权限入库配置文件 + * * @author : wee * @version : v2.0 * @Date 2019-06-27 10:16 @@ -19,26 +18,38 @@ import java.util.List; @ConfigurationProperties(prefix = "diboot.shiro.auth") public class AuthorizationProperties { - /**设置权限存储的环境:其中开发环境权限不会替换删除,测试和生产会替换删除*/ + /** + * 设置权限存储的环境:其中开发环境权限不会替换删除,测试和生产会替换删除 + */ private EnvEnum env = EnvEnum.DEV; - /**是否开启存储权限*/ + /** + * 是否开启存储权限 + */ private boolean storage = false; - /**具有所有权限的角色*/ + /** + * 具有所有权限的角色 + */ private List hasAllPermissionsRoleList; @Getter @AllArgsConstructor - public enum EnvEnum { + public enum EnvEnum { - /**生产环境*/ + /** + * 生产环境 + */ PROD("prod"), - /**测试环境*/ + /** + * 测试环境 + */ TEST("test"), - /**开发环境*/ + /** + * 开发环境 + */ DEV("dev"); private String env; diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/config/ShiroConfig.java b/diboot-shiro/src/main/java/com/diboot/shiro/config/ShiroConfig.java index 24eea52..fe9de9d 100644 --- a/diboot-shiro/src/main/java/com/diboot/shiro/config/ShiroConfig.java +++ b/diboot-shiro/src/main/java/com/diboot/shiro/config/ShiroConfig.java @@ -1,9 +1,16 @@ package com.diboot.shiro.config; +import com.diboot.core.util.V; import com.diboot.shiro.authz.aop.CustomAuthorizationAttributeSourceAdvisor; +import com.diboot.shiro.authz.cache.MemoryCondition; +import com.diboot.shiro.authz.cache.RedisCacheManager; +import com.diboot.shiro.authz.cache.RedisCondition; import com.diboot.shiro.authz.properties.AuthorizationProperties; +import com.diboot.shiro.authz.properties.AuthCacheProperties; import com.diboot.shiro.jwt.BaseJwtAuthenticationFilter; import com.diboot.shiro.jwt.BaseJwtRealm; +import org.apache.shiro.cache.CacheManager; +import org.apache.shiro.cache.MemoryConstrainedCacheManager; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.realm.Realm; import org.apache.shiro.spring.LifecycleBeanPostProcessor; @@ -14,8 +21,10 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.DependsOn; @@ -30,16 +39,47 @@ import java.util.Map; * @date 2019/6/6 */ @Configuration -@EnableConfigurationProperties(AuthorizationProperties.class) +@AutoConfigureAfter(AuthCacheProperties.class) +@EnableConfigurationProperties({AuthorizationProperties.class, AuthCacheProperties.class}) public class ShiroConfig { private static final Logger logger = LoggerFactory.getLogger(ShiroConfig.class); @Autowired private AuthorizationProperties authorizationProperties; + @Autowired + private AuthCacheProperties authCacheProperties; + + /** + * 将数据缓存到内存中 + * @return + */ + @Bean("cacheManager") + @Conditional(MemoryCondition.class) + public CacheManager memoryCacheManager() { + return new MemoryConstrainedCacheManager(); + } + + /** + * 将数据存储到redis缓存 + * @return + */ + @Bean("cacheManager") + @Conditional(RedisCondition.class) + public CacheManager redisCacheManager() { + return new RedisCacheManager(); + } + + @Bean public Realm realm(){ BaseJwtRealm realm = new BaseJwtRealm(); + if (authCacheProperties.isPermissionCachingEnabled()) { + //设置权限缓存 + realm.setCachingEnabled(true); + CacheManager cacheManager = V.notEmpty(redisCacheManager())? redisCacheManager(): memoryCacheManager(); + realm.setCacheManager(cacheManager); + } return realm; } diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/controller/PermissionController.java b/diboot-shiro/src/main/java/com/diboot/shiro/controller/PermissionController.java index 83dda3b..95a78ee 100644 --- a/diboot-shiro/src/main/java/com/diboot/shiro/controller/PermissionController.java +++ b/diboot-shiro/src/main/java/com/diboot/shiro/controller/PermissionController.java @@ -30,8 +30,8 @@ import java.util.List; * @date 2019/6/20 */ @RestController -@AuthorizationPrefix(prefix = "permission", code = "permission", name = "权限") @RequestMapping("/permission") +@AuthorizationPrefix(prefix = "permission", code = "permission", name = "权限管理") public class PermissionController extends BaseCrudRestController { private static final Logger logger = LoggerFactory.getLogger(PermissionService.class); @@ -46,7 +46,7 @@ public class PermissionController extends BaseCrudRestController { * @throws Exception */ @GetMapping("/{id}") - @AuthorizationWrapper(value = @RequiresPermissions("get"), name = "查看") + @AuthorizationWrapper(value = @RequiresPermissions("read"), name = "读取") public JsonResult getModel(@PathVariable("id")Long id, HttpServletRequest request) throws Exception{ PermissionVO vo = permissionService.getViewObject(id, PermissionVO.class); @@ -76,8 +76,8 @@ public class PermissionController extends BaseCrudRestController { * @return * @throws Exception */ - @RequiresPermissions("permission:add") @PostMapping("/") +// @AuthorizationWrapper(value = @RequiresPermissions("create"), name = "新建") public JsonResult createEntity(@ModelAttribute PermissionVO viewObject, BindingResult result, HttpServletRequest request) throws Exception{ // 转换 @@ -92,8 +92,8 @@ public class PermissionController extends BaseCrudRestController { * @return * @throws Exception */ - @RequiresPermissions("permission:update") @PutMapping("/{id}") +// @AuthorizationWrapper(value = @RequiresPermissions("update"), name = "更新") public JsonResult updateModel(@PathVariable("id")Long id, @ModelAttribute Permission entity, BindingResult result, HttpServletRequest request) throws Exception{ return super.updateEntity(entity, result); @@ -105,8 +105,8 @@ public class PermissionController extends BaseCrudRestController { * @return * @throws Exception */ - @RequiresPermissions("permission:delete") @DeleteMapping("/{id}") +// @AuthorizationWrapper(value = @RequiresPermissions("delete"), name = "删除") public JsonResult deleteModel(@PathVariable("id")Long id, HttpServletRequest request) throws Exception{ return super.deleteEntity(id); } diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/controller/RoleController.java b/diboot-shiro/src/main/java/com/diboot/shiro/controller/RoleController.java index 282af8f..d0b6089 100644 --- a/diboot-shiro/src/main/java/com/diboot/shiro/controller/RoleController.java +++ b/diboot-shiro/src/main/java/com/diboot/shiro/controller/RoleController.java @@ -10,10 +10,14 @@ import com.diboot.core.vo.JsonResult; import com.diboot.core.vo.KeyValue; import com.diboot.core.vo.Pagination; import com.diboot.core.vo.Status; +import com.diboot.shiro.authz.annotation.AuthorizationCache; +import com.diboot.shiro.authz.annotation.AuthorizationPrefix; +import com.diboot.shiro.authz.annotation.AuthorizationWrapper; import com.diboot.shiro.entity.Permission; import com.diboot.shiro.entity.Role; import com.diboot.shiro.service.RoleService; import com.diboot.shiro.vo.RoleVO; +import org.apache.shiro.authz.annotation.RequiresPermissions; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.ui.ModelMap; import org.springframework.validation.BindingResult; @@ -24,6 +28,7 @@ import java.util.List; @RestController @RequestMapping("/role") +@AuthorizationPrefix(prefix = "role", code = "role", name = "角色管理") public class RoleController extends BaseCrudRestController { @Autowired @@ -43,33 +48,25 @@ public class RoleController extends BaseCrudRestController { * @throws Exception */ @GetMapping("/list") - public JsonResult getVOList(Role role, Pagination pagination, HttpServletRequest request) throws Exception{ - QueryWrapper queryWrapper = super.buildQueryWrapper(role); + @AuthorizationWrapper(value = @RequiresPermissions("list"), name = "列表") + @AuthorizationCache + public JsonResult getVOList(HttpServletRequest request) throws Exception{ + QueryWrapper queryWrapper = buildQuery(request); + // 构建分页 + Pagination pagination = buildPagination(request); // 获取结果 List voList = roleService.getRoleList(queryWrapper, pagination); // 返回结果 return new JsonResult(Status.OK, voList).bindPagination(pagination); } - /*** - * 显示创建页面 - * @return - * @throws Exception - */ - @GetMapping("/toCreatePage") - public JsonResult toCreatePage(HttpServletRequest request, ModelMap modelMap) - throws Exception{ - List menuList = roleService.getAllMenu(); - modelMap.put("menuList", menuList); - return new JsonResult(modelMap); - } - /*** * 创建Entity * @return * @throws Exception */ @PostMapping("/") + @AuthorizationWrapper(value = @RequiresPermissions("create"), name = "新建") public JsonResult createEntity(@RequestBody Role entity, BindingResult result, HttpServletRequest request) throws Exception{ // 创建 @@ -81,19 +78,6 @@ public class RoleController extends BaseCrudRestController { } } - /*** - * 显示更新页面 - * @return - * @throws Exception - */ - @GetMapping("/toUpdatePage/{id}") - public JsonResult toUpdatePage(@PathVariable("id")Long id, HttpServletRequest request) - throws Exception{ - RoleVO roleVO = roleService.toUpdatePage(id); - return new JsonResult(roleVO); - } - - /*** * 更新Entity * @param id ID @@ -101,6 +85,7 @@ public class RoleController extends BaseCrudRestController { * @throws Exception */ @PutMapping("/{id}") + @AuthorizationWrapper(value = @RequiresPermissions("update"), name = "更新") public JsonResult updateModel(@PathVariable("id")Long id, @RequestBody Role entity, BindingResult result, HttpServletRequest request) throws Exception{ // Model属性值验证结果 @@ -125,6 +110,7 @@ public class RoleController extends BaseCrudRestController { * @throws Exception */ @GetMapping("/{id}") + @AuthorizationWrapper(value = @RequiresPermissions("read"), name = "读取") public JsonResult getModel(@PathVariable("id")Long id, HttpServletRequest request) throws Exception{ RoleVO roleVO = roleService.getRole(id); @@ -138,6 +124,7 @@ public class RoleController extends BaseCrudRestController { * @throws Exception */ @DeleteMapping("/{id}") + @AuthorizationWrapper(value = @RequiresPermissions("delete"), name = "删除") public JsonResult deleteModel(@PathVariable("id")Long id, HttpServletRequest request) throws Exception{ boolean success = roleService.deleteRole(id); if(success){ @@ -147,6 +134,34 @@ public class RoleController extends BaseCrudRestController { } } + + /*** + * 显示创建页面 + * @return + * @throws Exception + */ + @GetMapping("/toCreatePage") + public JsonResult toCreatePage(HttpServletRequest request, ModelMap modelMap) + throws Exception{ + List menuList = roleService.getAllMenu(); + modelMap.put("menuList", menuList); + return new JsonResult(modelMap); + } + + + /*** + * 显示更新页面 + * @return + * @throws Exception + */ + @GetMapping("/toUpdatePage/{id}") + public JsonResult toUpdatePage(@PathVariable("id")Long id, HttpServletRequest request) + throws Exception{ + RoleVO roleVO = roleService.toUpdatePage(id); + return new JsonResult(roleVO); + } + + /*** * 获取所有菜单,以及每个菜单下的所有权限 * @return diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/entity/Role.java b/diboot-shiro/src/main/java/com/diboot/shiro/entity/Role.java index 5b0db06..79a19b3 100644 --- a/diboot-shiro/src/main/java/com/diboot/shiro/entity/Role.java +++ b/diboot-shiro/src/main/java/com/diboot/shiro/entity/Role.java @@ -2,6 +2,7 @@ package com.diboot.shiro.entity; import com.baomidou.mybatisplus.annotation.TableField; import com.diboot.core.entity.BaseEntity; +import com.diboot.core.util.V; import lombok.Data; import java.util.List; @@ -34,4 +35,11 @@ public class Role extends BaseEntity { @TableField(exist = false) private List permissionList; + /*** + * 是否是管理员权限 + * @return + */ + public boolean isAdmin(){ + return V.equals(code, "ADMIN"); + } } diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/jwt/BaseJwtRealm.java b/diboot-shiro/src/main/java/com/diboot/shiro/jwt/BaseJwtRealm.java index 082f509..4da3bd5 100644 --- a/diboot-shiro/src/main/java/com/diboot/shiro/jwt/BaseJwtRealm.java +++ b/diboot-shiro/src/main/java/com/diboot/shiro/jwt/BaseJwtRealm.java @@ -13,12 +13,14 @@ import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.SimpleAuthenticationInfo; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; +import org.apache.shiro.cache.CacheManager; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import org.springframework.beans.factory.annotation.Autowired; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.stream.Collectors; diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/util/ProxyToTargetObjectHelper.java b/diboot-shiro/src/main/java/com/diboot/shiro/util/ProxyToTargetObjectHelper.java index 3ed3600..42ffb86 100644 --- a/diboot-shiro/src/main/java/com/diboot/shiro/util/ProxyToTargetObjectHelper.java +++ b/diboot-shiro/src/main/java/com/diboot/shiro/util/ProxyToTargetObjectHelper.java @@ -16,6 +16,7 @@ public class ProxyToTargetObjectHelper { /** * 获取代理对象的目标对象 + * 对象可能被多次代理,所以需要递归获取原始对象 * @param proxy * @return * @throws Exception @@ -27,12 +28,13 @@ public class ProxyToTargetObjectHelper { } //判断是jdk动态代理还是cglib代理 if(AopUtils.isJdkDynamicProxy(proxy)) { - return getJdkDynamicProxyTargetObject(proxy); + proxy = getJdkDynamicProxyTargetObject(proxy); } //cglib else { - return getCglibProxyTargetObject(proxy); + proxy = getCglibProxyTargetObject(proxy); } + return getTarget(proxy); }