1 Star 0 Fork 102

tesla_ly / open-scope

forked from mofum / open-scope 
加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
README.md 12.91 KB
一键复制 编辑 原始数据 按行查看 历史
玉米 提交于 2020-08-21 12:46 . update README.md.

open-scope

介绍

OpenScope是一种轻量级、易维护的数据权限的解决方案,它能处理比较复杂的权限操作逻辑。兼容操作权限Shiro等框架。

OpenScope提供了一种基于SQL的智能添加权限范围列的方案,相对原始的数据权限方案,它是轻量级的,它只有一些配置代码,同时它也是提高了代码的可维护性。另外它不需要额外的更改您的程序结构,就能轻松使您的项目支持数据权限操作。

什么是操作权限,什么是数据权限详细见WIKI 简介

1.0.0.0.RELEASE 版本功能介绍

  • 支持Mybatis- SQL 根据权限范围列动态数据行过滤(兼容PageHelper,OrderBy等其他Mybatis插件)
  • 支持多个范围类型,多个业务对象指定范围类型。
  • 兼容shiro、spring-security等操作权限框架,也可以独立存在,因为它拥有完善的认证流程。
  • 参数额外支持JSON(spring-web包)
  • 支持自定义错误异常返回,这个异常返回。

1.0.1.0.RELEASE 版本功能介绍

  • 将数据权限的粒度控制到数据列上。[-]

  • 对ORM-HIBERNATE兼容或者对ORM-JDBC(任选其一)[-]

    (注:[-] 表示待开发或者正在开发中,[√]表示已完成)

1.0.0.0.RELEASE原理:

 ORM-Mybatis过滤原理:通过对SQL智能的添加权限列来到的基于权限范围的数据过滤。

1.0.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)

<!-- openScope依赖 -->
<dependency>
    <groupId>com.mofum.scope</groupId>
    <artifactId>scope-boot-starter</artifactId>
    <version>1.0.0.1.RELEASE</version>
</dependency>
            

(注:如果是SpringCloud项目,如果子项目不需要Mybatis,请移除相关的依赖)

<!-- openScope依赖(不包含orm-mybatis) -->
<dependency>
  <groupId>com.mofum.scope</groupId>
  <artifactId>scope-boot-starter</artifactId>
  <version>1.0.0.1.RELEASE</version>
  <exclusions>
    <exclusion>
      <groupId>com.mofum.scope</groupId>
      <artifactId>scope-orm-mybatis</artifactId>
    </exclusion>
    <exclusion>
      <groupId>com.mofum.scope</groupId>
      <artifactId>scope-autoconfiure</artifactId>
    </exclusion>
  </exclusions>
</dependency>

使用方法(SpringBoot版本)

以下内容详细请见: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<User> queryUser(UserDto user) {
        return userMapper.queryUser(user);
    }

    @Override
    public int delete(UserDto userDto) {
        return 0;
    }
}

配置业务对象转换器(ScopeConvert)【必须】

public class ScopeController implements IScopeConverter<Object, RuntimeException> {

    @Override
    public List<Scope> convert2Scope(Object o) throws RuntimeException {
        //转换业务对象为ScopeId
        List<Scope> 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<Object, RuntimeException> {

    
    @Override
    public List<? extends Scope> extractorScopes(Object o) throws RuntimeException {

        //提取权限数据范围

        //假设只有SCOPE_ONE只有权限范围1

        List<Scope> 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<List<Scope>, RuntimeException>{


    @Override
    public boolean testAccess(List<Scope> 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<SqlSessionFactory> sqlSessionFactoryList;

    public MybatisScopeAutoConfiguration() {
    }

    @PostConstruct
    public void addTestInterceptor() {
        PermissionInterceptor interceptor = new PermissionInterceptor();
        interceptor.setRestructureProcessor(new DruidRestructureProcessor());
        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;
    }

}

联系我们

QQ交流群:1062019634(200人)

Java
1
https://gitee.com/itstudy_liyong/open-scope.git
git@gitee.com:itstudy_liyong/open-scope.git
itstudy_liyong
open-scope
open-scope
master

搜索帮助