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异常。])
组件的过滤流程:
控制层请求 >
控制层切面 >
执行范围提取器获得权限范围 >
将权限范围设置到scopeCollections中 >
进入业务切面 >
将业务切面类上的TableScope内容完善到scopeCollections中 >
进入权限拦截器 >
根据scopeCollections的内容进行智能拼装SQL
组件的认证流程
控制层请求 >
控制层切面 >
进入权限范围转换器 获得业务对象,将业务对象转换成范围对象 >
将范围对象交给权限认证器 >
认证器向您的认证服务中心提供的认证权限的接口发起请求 >
您的具体业务逻辑 >
如果认证器里没有抛出异常,则认证成功 >
如果认证器里抛出异常,则认证失败 >
软件架构
OpenScope分为多个组件
权限范围认证器(IScopeAuthenticator)
主要用于向您的认证中心发起认证消息。你可以在这里面写一些您的认证中心的发起逻辑。
权限范围提取器(IScopeExtractor)
主要用于从您的认证中心提取业务范围数据,并将其设置到List 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.1.RELEASE
(注:如果是SpringCloud项目,如果子项目不需要Mybatis,请移除相关的依赖)
com.mofum.scope
scope-boot-starter
1.0.0.1.RELEASE
com.mofum.scope
scope-orm-mybatis
com.mofum.scope
scope-autoconfiure
使用方法(SpringBoot版本)
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 extends Scope> 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();
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人)