# open-scope **Repository Path**: ws_developer/open-scope ## Basic Information - **Project Name**: open-scope - **Description**: OpenScope是一种轻量级、易维护的“数据权限”的解决方案,它能处理比较复杂的“数据权限”操作逻辑。兼容Shiro等操作权限框架。 - **Primary Language**: Java - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 105 - **Created**: 2019-04-13 - **Last Updated**: 2020-12-19 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # open-scope #### 介绍 OpenScope是一种轻量级、易维护的数据权限的解决方案,它能处理比较复杂的权限操作逻辑。兼容操作权限Shiro等框架。 OpenScope提供了一种基于SQL的智能添加权限范围列的方案,相对原始的数据权限方案,它是轻量级的,它只有一些配置代码,同时它也是提高了代码的可维护性。另外它不需要额外的更改您的程序结构,就能轻松使您的项目支持数据权限操作。 什么是操作权限,什么是数据权限详细见[WIKI 简介](http://) #### 1.0.0.RELEASE 版本功能介绍 - 支持Mybatis- SQL 根据权限范围列动态数据行过滤(兼容PageHelper,OrderBy等其他Mybatis插件) - 支持多个范围类型,多个业务对象指定范围类型。 - 兼容shiro、spring-security等操作权限框架,也可以独立存在,因为它拥有完善的认证流程。 - 参数额外支持JSON(spring-web包) - 支持自定义错误异常返回,这个异常返回。 #### 1.0.1.RELEASE 版本功能介绍 - 将数据权限的粒度控制到数据列上。[-] - 对ORM-HIBERNATE兼容或者对ORM-JDBC(任选其一)[-] (注:[-] 表示待开发或者正在开发中,[√]表示已完成) #### 1.0.0.RELEASE原理: ORM-Mybatis过滤原理:通过对SQL智能的添加权限列来到的基于权限范围的数据过滤。 #### 1.0.0.RELEASE版本的默认流程: 考虑性能的原因: 我们给了一套默认的权限配置,对查询多条数据只进行数据过滤操作,对单挑数据的操作只执行转换操作和认证操作。这对Spring-Cloud相关的微服务项目很有帮助。减少了各个模块之间相互调用的次数,提升了服务器的处理能力和响应能力。 但是如果您不考虑性能的问题,而是考虑权限认证操作的流程的完整性。你仍然可以为每一个请求开启过滤、转换,认证三个操作。 (注:可以根据业务的范围或者实际需求进行调整,ORM-Mybatis过滤是基于SQL,所以请务必要保证您的查询结果集合里存在配置的范围列字段,否则执行权限范围过滤的过程中会抛出找不到范围列的异常信息。[SQLException: Column 您配置的权限范围列 not found异常。]) ##### 组件的过滤流程: 1. 控制层请求 > 2. 控制层切面 > 3. 执行范围提取器获得权限范围 > 4. 将权限范围设置到scopeCollections中 > 5. 进入业务切面 > 6. 将业务切面类上的TableScope内容完善到scopeCollections中 > 7. 进入权限拦截器 > 8. 根据scopeCollections的内容进行智能拼装SQL ##### 组件的认证流程 1. 控制层请求 > 2. 控制层切面 > 3. 进入权限范围转换器 获得业务对象,将业务对象转换成范围对象 > 4. 将范围对象交给权限认证器 > 5. 认证器向您的认证服务中心提供的认证权限的接口发起请求 > 6. 您的具体业务逻辑 > 7. 如果认证器里没有抛出异常,则认证成功 > 8. 如果认证器里抛出异常,则认证失败 > #### 软件架构 OpenScope分为多个组件 - 权限范围认证器(IScopeAuthenticator) 主要用于向您的认证中心发起认证消息。你可以在这里面写一些您的认证中心的发起逻辑。 - 权限范围提取器(IScopeExtractor) 主要用于从您的认证中心提取业务范围数据,并将其设置到List<Scope> scopeCollections中。用于权限拦截器动态拼装权限范围等内容。 - 权限范围转换器(IScopeConverter) 主要用于业务对象ID转换范围对象ID。 - 错误异常处理器(ThrowableHandler) 主要用于统一处理权限认证的错误异常类 - ORM-权限拦截器 (Permission) 智能的对SQL进行拦截,并为其添加权限范围过滤列。 #### 子包说明 - scope-common (公用的模型定义) - scope-annotation(公用的注解) - scope-orm-mybatis(ORM框架有关的内容) - scope-spring-web(Spring-web相关的内容) - scope-autoconfigure(自动配置内容) - scope-boot-starter(SpringBoot相关内容) #### 安装方法(SpringBoot) ``` com.mofum.scope scope-boot-starter 1.0.0.RELEASE ``` (注:如果是SpringCloud项目,如果子项目不需要Mybatis,请移除相关的依赖) ``` com.mofum.scope scope-boot-starter 1.0.0.RELEASE com.mofum.scope scope-orm-mybatis com.mofum.scope scope-autoconfiure ``` #### 使用方法(SpringBoot版本) 以下内容详细请见:[https://gitee.com/mofum/open-scope-demo](https://gitee.com/mofum/open-scope-demo) ### SpringBoot Application 类 ``` @SpringBootApplication @ComponentScan(value = { "com.mofum.scope.controller",//控制器 "com.mofum.scope.service",//本地逻辑具体业务 "com.mofum.scope.config" }) @MapperScan("com.mofum.scope.mapper") @EnableAutoConfiguration @EnableAspectJAutoProxy //开启切面控制 public class Application { public static void main(String[] args) { ApplicationContext applicationContext = SpringApplication.run(Application.class, args); IUserService userService = applicationContext.getBean(UserServiceImpl.class); userService.initTable(); } } ``` ### 控制层(Controller) ``` @RestController @RequestMapping("/user") public class UserController extends ScopeController { public static Logger logger = LoggerFactory.getLogger(UserController.class); @Autowired IUserService userService; @RequestMapping("/add") public Object addUser(User user) { userService.addUser(user); return "SUCCESS"; } @RequestMapping("/query") public Object query(UserDto userDto) { logger.info(userDto.toString()); return userService.queryUser(userDto); } @RequestMapping("/scope/query") @QueryScope public Object scopeQuery(UserDto userDto) { logger.info(userDto.toString()); return userService.queryUser(userDto); } @RequestMapping("/del") @UpdateScope(columns = { @ServiceColumn(value = "serviceIds") }) public Object del(UserDto userDto) { logger.info(userDto.toString()); return "SUCCESS"; } } ``` ### 业务层(Service) ``` @Service @TableScope(columns = { /** * @see com.mofum.scope.controller.ScopeController 中的extractorScopes()方法 */ @ColumnScope(type = "ScopeOne", value = "scope_one") //配置列type 是类型,scope_one是表中的列 }) public class UserServiceImpl implements IUserService { @Autowired UserMapper userMapper; @Override public void initTable() { userMapper.createTable(); } @Override public void addUser(User user) { if (user != null) { user.setId(UUID.randomUUID().toString()); } userMapper.insertUser(user); } @Override public List queryUser(UserDto user) { return userMapper.queryUser(user); } @Override public int delete(UserDto userDto) { return 0; } } ``` ### 配置业务对象转换器(ScopeConvert)【必须】 ``` public class ScopeController implements IScopeConverter { @Override public List convert2Scope(Object o) throws RuntimeException { //转换业务对象为ScopeId List scopes = new ArrayList<>(); Scope scope = new Scope(); scope.setId("1"); scope.setType("ScopeOne"); //Type和IUserService 中的注解ColumnScope要指定同一个注解才能生效 scopes.add(scope); return scopes; } } ``` ### 配置SQL范围提取器(ScopeExtractor)【必须】 ``` public class ScopeController implements IScopeExtractor { @Override public List extractorScopes(Object o) throws RuntimeException { //提取权限数据范围 //假设只有SCOPE_ONE只有权限范围1 List scopes = new ArrayList<>(); Scope scope = new Scope(); scope.setId("1"); scope.setType("ScopeOne"); //Type和IUserService 中的注解ColumnScope要指定同一个注解才能生效 scopes.add(scope); return scopes; } } ``` ### 配置认证器(ScopeAuthenticator)【必须】 ``` public class ScopeController implements IScopeAuthenticator, RuntimeException>{ @Override public boolean testAccess(List scopes) throws RuntimeException { //取用户ID String userId = getRequest().getParameter("userId"); //取请求URL String url = getRequest().getRequestURI(); boolean accessFlag = false; //验证权限逻辑 for (Scope scope : scopes) { if (scope.getId().equals("2")) { return true; } } if (!accessFlag) { throw new RuntimeException("Operation failed!Cause no permission!(action :" + url + ")"); } return accessFlag; } public HttpServletRequest getRequest() { HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); return request; } public HttpServletResponse getResponse() { HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse(); return response; } } ``` ### 配置权限拦截器(Config PermissionInterceptor )【必须】 ``` @Configuration public class MybatisScopeAutoConfiguration { @Autowired private List sqlSessionFactoryList; public MybatisScopeAutoConfiguration() { } @PostConstruct public void addTestInterceptor() { PermissionInterceptor interceptor = new PermissionInterceptor(); Iterator var3 = this.sqlSessionFactoryList.iterator(); while (var3.hasNext()) { SqlSessionFactory sqlSessionFactory = (SqlSessionFactory) var3.next(); sqlSessionFactory.getConfiguration().addInterceptor(interceptor); } } } ``` ### 配置业务切面 (Config Scan Service Aspect)【必须】 ``` @Component @Aspect public class ServiceAspectJ extends AbstractColumnAspectJ { //配置业务切面 @Override @Pointcut("execution(* com.mofum.scope.service..*.*(..))") public void config() { } } ``` ### 配置控制切面 (Config Scan Service Aspect)【必须】 ``` @Component @Aspect public class ControllerAspectJ extends AbstractControllerAspectJ { private ThrowableHandler throwableHandler; //配置控制切面 @Override @Pointcut("execution(* com.mofum.scope.controller..*.*(..))") public void config() { } @Override public ThrowableHandler getThrowableHandler() { if(throwableHandler == null){ throwableHandler = new ControllerThrowableHandler(); } return throwableHandler; } @Override public void setThrowableHandler(ThrowableHandler throwableHandler) { this.throwableHandler = throwableHandler; } } ``` ### 配置异常处理器 (Config ErrorHandler) 【非必须】 ``` public class ControllerThrowableHandler implements ThrowableHandler { @Override public void handler(Throwable throwable) throws Throwable { if (throwable != null) { getResponse().setCharacterEncoding("UTF-8"); getResponse().getWriter().println(throwable.getMessage()); throwable.printStackTrace(); } } public HttpServletRequest getRequest() { HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); return request; } public HttpServletResponse getResponse() { HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse(); return response; } } ```