一般来说,权限有许多种,我们经常用到的一般有操作权限和数据权限两种。
所谓操作权限就是有或者没有做某种操作的权限,具体表现形式就是你看不到某个菜单或按钮,当然也有的是把菜单或按钮灰掉的形式。操作权限一般都在显示界面做一次控制,过滤没有权限的操作菜单或按钮,另外在真正执行操作时,还要进行一次权限检查,确保控制非授权访问。操作权限都是围绕角色来开展的,目的就是要实现操作的角色化。
在上图所示的ER图关系中,角色(blade_role)菜单表(blade_menu)通过 角色菜单关联表(blade_role_menu)建立多对多联系。
操作权限授权的过程即为给角色授予某一菜单的操作权限,用户与角色通过用户平台扩展表(blade_user_web)建立联系,若用户拥有多个角色,则在平台扩展表的角色字段role_id中多个id用英文逗号分隔。基本关系如图所示:

系统一级菜单代表系统,二极菜单代表功能管理,三级菜单代表操作按钮,如增删改查权限
所谓数据权限,就是有或者没有对某些数据的访问权限,具体表现形式就是当某用户有操作权限的时候,但不代表其对所有的数据都有查看或者管理权限。
数据权限有两种表现形式:一种是行权限、另外一种是列权限。
再比如:同样一个部门经理的角色,看到的数据是不一样的,所以,牵扯到数据二字,就应该不和操作二字等同起来。所以我们是通过职位来解决数据权限的,职位也可以叫岗位,是和数据查看范围有关系的,也就是组织结构里的关系。
所以在设计数据结构的时候,每个有数据范围的数据实体,都需要具备数据拥有人的字段,比如A部门的小b同学,他创建的数据,只能由A部门的部门经理看到,而同样在角色里具有部门经理的B部门的部门经理是不能看到的。所以延伸出来了一个设计思路,根据数据拥有人,圈定查看范围。数据范围的维度有:全部、本集团、本公司、本部门、自己,五个维度,可以满足大部分业务场景。
还有一个维度是自定义维度,可以自定义机构进行设置。这样的设计就达到了数据权限的操作灵活性。
● 系统都离不开权限模块,它是支撑整个系统运行的基础模块。而根据项目类型和需求的不同,权限模块的设计更是大相径庭。但不管怎么变,权限模块从大的方面来说,可以分为三种大的类型:功能权限、接口权限、数据权限。
● 功能权限:也就是我们最熟悉的菜单、按钮权限。可以配置各个角色能看到的菜单、按钮从而从最表层分配好权限
● 接口权限:顾名思义,配置不通角色调用接口的权限。有些敏感接口,是只能有固定的一些角色才能调用,普通角色是不能调用的。这种情况需要有一个明确的系统来控制对应的访问权限
● 数据权限:是大家最为需求也是最广为谈资的一个设计理念。我们需要控制不通的角色、机构人员有查看不通数据范围的权限。如果你动手去设计数据权限,当你去各大平台、百度、谷歌查找设计思路的时候,你会发现很难找到有用的资料,很多设计思路局限性非常大。
详情请参考:https://www.jianshu.com/p/0ab125cf8258
给某一角色授权的数据仅能看到自己所管理的部分数据
某一角色用户只能看到所拥有菜单权限下的部分列字段数据
实现不同人看不同数据,不同人对同一个页面操作不同字段。系统按钮权限和表单权限原来是正控制,只有授权的人才有权限,未授权看不到对应按钮;
为解决这一类疑难问题,提供三种方式来实现数据权限。
● 如果是纯注解配置,那么是不通过数据库的,相当于是离线配置。
● 我们只需要关注column、type、value这三个字段。
● column:需要过滤的数据库字段
● type:数据权限过滤的类型
● value:当数据权限类型为自定义的时候,配置的sql条件语句
1)、所在部门可见
配置DataAuth注解,因为默认字段就是create_dept,所以无需配置column

2)、所在机构及其子集可见
配置DataAuth注解,因为默认字段就是create_dept,所以无需配置column

3)、个人可见
配置DataAuth注解,由于创建人字段为create_user,不是默认,所以需要指定

4)、自定义配置
● 配置DataAuth注解,配置自定义sql
● 在这个配置的sql里我使用里占位符${userId},没错,这么写在底层就可以直接获取到当前登录用户的deptId字段,除此之外我们还可以用更多的参数,比如${deptId}、${roleId}、${tenantId}、${account}、${userName}等等
● 这些参数可以参考BladeUser类,他的所有字段我们都是可以根据占位符来获得的。


这个就需要关联数据库,根据数据权限code码来设置数据权限定义规则。
● 数据权限动态配置需要依赖数据库,所以我们需要前往web端进行配置
● 配置逻辑与纯注解配置一致,其实就是把注解配置拓展,并做成了web可视化
可视化页面开发中,
数据权限的核心注解为@DataAuth,它的定义代码如下:
package com.springblade.core.datascope.annotation;
import com.springblade.core.datascope.enums.DataScopeEnum;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface DataAuth {
/**
* 资源编号
*/
String code() default "";
/**
* 数据权限对应字段
*/
String column() default DataScopeConstant.DEFAULT_COLUMN;
/**
* 数据权限规则
*/
DataScopeEnum type() default DataScopeEnum.ALL;
/**
* 可见字段
*/
String field() default "*";
/**
* 数据权限规则值域
*/
String value() default "";
}

● 可以看到,目前的数据权限类型一共有五种,前面四种都是不需要自定义写sql的,只有选择了CUSTOM类型,才需要定义注解的value属性
● 注解默认过滤的字段名为create_dept,如果有修改,则需要定义对应的字段名。
/**
* mybatis 数据权限拦截器
* @author L.cm, Chill
*/
@Slf4j
@RequiredArgsConstructor
@SuppressWarnings({"rawtypes"})
public class DataScopeInterceptor implements QueryInterceptor {
private final ConcurrentMap<String, DataAuth> dataAuthMap = new ConcurrentHashMap<>(8);
private final DataScopeHandler dataScopeHandler;
private final DataScopeProperties dataScopeProperties;
@Override
public void intercept(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
//未启用则放行
if (!dataScopeProperties.getEnabled()) {
return;
}
//未取到用户则放行
BladeUser bladeUser = AuthUtil.getUser();
if (bladeUser == null) {
return;
}
if (SqlCommandType.SELECT != ms.getSqlCommandType() || StatementType.CALLABLE == ms.getStatementType()) {
return;
}
String originalSql = boundSql.getSql();
//查找注解中包含DataAuth类型的参数
DataAuth dataAuth = findDataAuthAnnotation(ms);
//注解为空并且数据权限方法名未匹配到,则放行
String mapperId = ms.getId();
String className = mapperId.substring(0, mapperId.lastIndexOf(StringPool.DOT));
String mapperName = ClassUtil.getShortName(className);
String methodName = mapperId.substring(mapperId.lastIndexOf(StringPool.DOT) + 1);
boolean mapperSkip = dataScopeProperties.getMapperKey().stream().noneMatch(methodName::contains)
|| dataScopeProperties.getMapperExclude().stream().anyMatch(mapperName::contains);
if (dataAuth == null && mapperSkip) {
return;
}
//创建数据权限模型
DataScopeModel dataScope = new DataScopeModel();
//若注解不为空,则配置注解项
if (dataAuth != null) {
dataScope.setResourceCode(dataAuth.code());
dataScope.setScopeColumn(dataAuth.column());
dataScope.setScopeType(dataAuth.type().getType());
dataScope.setScopeField(dataAuth.field());
dataScope.setScopeValue(dataAuth.value());
}
//获取数据权限规则对应的筛选Sql
String sqlCondition = dataScopeHandler.sqlCondition(mapperId, dataScope, bladeUser, originalSql);
if (!StringUtil.isBlank(sqlCondition)) {
PluginUtils.MPBoundSql mpBoundSql = PluginUtils.mpBoundSql(boundSql);
mpBoundSql.sql(sqlCondition);
}
}
/**
* 获取数据权限注解信息
*
* @param mappedStatement mappedStatement
* @return DataAuth
*/
private DataAuth findDataAuthAnnotation(MappedStatement mappedStatement) {
String id = mappedStatement.getId();
return dataAuthMap.computeIfAbsent(id, (key) -> {
String className = key.substring(0, key.lastIndexOf(StringPool.DOT));
String mapperBean = StringUtil.firstCharToLower(ClassUtil.getShortName(className));
Object mapper = SpringUtil.getBean(mapperBean);
String methodName = key.substring(key.lastIndexOf(StringPool.DOT) + 1);
Class<?>[] interfaces = ClassUtil.getAllInterfaces(mapper);
for (Class<?> mapperInterface : interfaces) {
for (Method method : mapperInterface.getDeclaredMethods()) {
if (methodName.equals(method.getName()) && method.isAnnotationPresent(DataAuth.class)) {
return method.getAnnotation(DataAuth.class);
}
}
}
return null;
});
}
}
需要注意的是,下面的DataScopeEnum这个判断,如果角色是ADMINISTRATOR的话也是不执行直接返回null的,我就是在这里掉坑,所以还是要看源码了解执行过程找问题。
/**
* 默认数据权限规则
* 获取过滤sql
* @param mapperId 数据查询类
* @param dataScope 数据权限类
* @param bladeUser 当前用户信息
* @param originalSql 原始Sql
* @author Chill
*/
@RequiredArgsConstructor
public class BladeDataScopeHandler implements DataScopeHandler {
private final ScopeModelHandler scopeModelHandler;
@Override
public String sqlCondition(String mapperId, DataScopeModel dataScope, BladeUser bladeUser, String originalSql) {
//数据权限资源编号
String code = dataScope.getResourceCode();
//根据mapperId从数据库中获取对应模型
DataScopeModel dataScopeDb = scopeModelHandler.getDataScopeByMapper(mapperId, bladeUser.getRoleId());
//mapperId配置未取到则从数据库中根据资源编号获取
if (dataScopeDb == null && StringUtil.isNotBlank(code)) {
dataScopeDb = scopeModelHandler.getDataScopeByCode(code);
}
//未从数据库找到对应配置则采用默认
dataScope = (dataScopeDb != null) ? dataScopeDb : dataScope;
//判断数据权限类型并组装对应Sql
Integer scopeRule = Objects.requireNonNull(dataScope).getScopeType();
DataScopeEnum scopeTypeEnum = DataScopeEnum.of(scopeRule);
List<Long> ids = new ArrayList<>();
String whereSql = "where scope.{} in ({})";
//需要注意的是,下面的这个判断,如果角色是ADMINISTRATOR的话也是不执行直接返回null的,我就是在这里掉坑
if (DataScopeEnum.ALL == scopeTypeEnum || StringUtil.containsAny(bladeUser.getRoleName(), RoleConstant.ADMINISTRATOR)) {
return null;
} else if (DataScopeEnum.CUSTOM == scopeTypeEnum) {
whereSql = PlaceholderUtil.getDefaultResolver().resolveByMap(dataScope.getScopeValue(), BeanUtil.toMap(bladeUser));
} else if (DataScopeEnum.OWN == scopeTypeEnum) {
ids.add(bladeUser.getUserId());
} else if (DataScopeEnum.OWN_DEPT == scopeTypeEnum) {
ids.addAll(Func.toLongList(bladeUser.getDeptId()));
} else if (DataScopeEnum.OWN_DEPT_CHILD == scopeTypeEnum) {
List<Long> deptIds = Func.toLongList(bladeUser.getDeptId());
ids.addAll(deptIds);
deptIds.forEach(deptId -> {
List<Long> deptIdList = scopeModelHandler.getDeptAncestors(deptId);
ids.addAll(deptIdList);
});
}
return StringUtil.format(" select {} from ({}) scope " + whereSql, Func.toStr(dataScope.getScopeField(), "*"), originalSql, dataScope.getScopeColumn(), StringUtil.join(ids));
}
}
纯注解我们只需要关注下面三个字段即可,当中的数据权限规则枚举类我们来看下构成:
● 可以看到,目前的数据权限类型一共有五种,前面四种都是不需要自定义写sql的,只有选择了CUSTOM类型,才需要定义注解的value属性
● 注解默认过滤的字段名为create_dept,如果有修改,则需要定义对应的字段名

通过接口配置实现,对接口的访问权限控制和数据权限控制,
接口是REST接口,接口权限认证机制使用Json web token (JWT)
(1)通过接口用户的用户名密码,调用鉴权token接口获取接口用户的token
该token,2个小时内有效
(2)把获取的token作为参数,调用接口的时候,会根据token去鉴权
(3)鉴权通过,接口会根据接口定义的编码,检验是否有访问权限
有则可以继续访问,无则提示访问受限
(4)有访问权限,则获取接口的数据权限规则,根据授权的数据权限规则返回需要的数据
实现一个新的接口,无需关注token的鉴权机制,
使用AOP实现接口拦截:@PreAuth
鉴权配置注解名称为 @PreAuth ,在需要进行鉴权配置的方法加上 @PreAuth 注解,并在注解内写 入相关的鉴权方法。
@PreAuth的注解定义:
package com.springblade.core.secure.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface PreAuth {
String value();
}
具体实现方法:
@Aspect
public class AuthAspect implements ApplicationContextAware {
/**
* 表达式处理
*/
private static final ExpressionParser EXPRESSION_PARSER = new SpelExpressionParser();
/**
* 切 方法 和 类上的 @PreAuth 注解
*
* @param point 切点
* @return Object
* @throws Throwable 没有权限的异常
*/
@Around(
"@annotation(com.springblade.core.secure.annotation.PreAuth) || " +
"@within(com.springblade.core.secure.annotation.PreAuth)"
)
public Object preAuth(ProceedingJoinPoint point) throws Throwable {
if (handleAuth(point)) {
return point.proceed();
}
throw new SecureException(ResultCode.UN_AUTHORIZED);
}
/**
* 处理权限
*
* @param point 切点
*/
private boolean handleAuth(ProceedingJoinPoint point) {
MethodSignature ms = (MethodSignature) point.getSignature();
Method method = ms.getMethod();
// 读取权限注解,优先方法上,没有则读取类
PreAuth preAuth = ClassUtil.getAnnotation(method, PreAuth.class);
// 判断表达式
String condition = preAuth.value();
if (StringUtil.isNotBlank(condition)) {
Expression expression = EXPRESSION_PARSER.parseExpression(condition);
// 方法参数值
Object[] args = point.getArgs();
StandardEvaluationContext context = getEvaluationContext(method, args);
return expression.getValue(context, Boolean.class);
}
return false;
}
/**
* 获取方法上的参数
*
* @param method 方法
* @param args 变量
* @return {SimpleEvaluationContext}
*/
private StandardEvaluationContext getEvaluationContext(Method method, Object[] args) {
// 初始化Sp el表达式上下文,并设置 AuthFun
StandardEvaluationContext context = new StandardEvaluationContext(new AuthFun());
// 设置表达式支持spring bean
context.setBeanResolver(new BeanFactoryResolver(applicationContext));
for (int i = 0; i < args.length; i++) {
// 读取方法参数
MethodParameter methodParam = ClassUtil.getMethodParameter(method, i);
// 设置方法 参数名和值 为sp el变量
context.setVariable(methodParam.getParameterName(), args[i]);
}
return context;
}
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(@NonNull ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
public class AuthFun {
/**
* 权限校验处理器
*/
private static IPermissionHandler permissionHandler;
private static IPermissionHandler getPermissionHandler() {
if (permissionHandler == null) {
permissionHandler = SpringUtil.getBean(IPermissionHandler.class);
}
return permissionHandler;
}
/**
* 判断角色是否具有接口权限
*
* @return {boolean}
*/
public boolean permissionAll() {
return getPermissionHandler().permissionAll();
}
/**
* 判断角色是否具有接口权限
*
* @param permission 权限编号
* @return {boolean}
*/
public boolean hasPermission(String permission) {
return getPermissionHandler().hasPermission(permission);
}
/**
* 放行所有请求
*
* @return {boolean}
*/
public boolean permitAll() {
return true;
}
/**
* 只有超管角色才可访问
*
* @return {boolean}
*/
public boolean denyAll() {
return hasRole(RoleConstant.ADMIN);
}
/**
* 是否已授权
*
* @return {boolean}
*/
public boolean hasAuth() {
return Func.isNotEmpty(AuthUtil.getUser());
}
/**
* 是否有时间授权
*
* @param start 开始时间
* @param end 结束时间
* @return {boolean}
*/
public boolean hasTimeAuth(Integer start, Integer end) {
Integer hour = DateUtil.hour();
return hour >= start && hour <= end;
}
/**
* 判断是否有该角色权限
*
* @param role 单角色
* @return {boolean}
*/
public boolean hasRole(String role) {
return hasAnyRole(role);
}
/**
* 判断是否具有所有角色权限
*
* @param role 角色集合
* @return {boolean}
*/
public boolean hasAllRole(String... role) {
for (String r : role) {
if (!hasRole(r)) {
return false;
}
}
return true;
}
/**
* 判断是否有该角色权限
*
* @param role 角色集合
* @return {boolean}
*/
public boolean hasAnyRole(String... role) {
BladeUser user = AuthUtil.getUser();
if (user == null) {
return false;
}
String userRole = user.getRoleName();
if (StringUtil.isBlank(userRole)) {
return false;
}
String[] roles = Func.toStrArray(userRole);
for (String r : role) {
if (CollectionUtil.contains(roles, r)) {
return true;
}
}
return false;
}
}
使用方法:可以注释到类上、方法上
@PreAuth("hasPermission(#test) and @PreAuth.hasPermission(#test)")
例如:下图表示只能有test接口权限的才能访问

代表了菜单权限和资源权限的一种组合方式,比如我设置了多个用户需要相同的菜单权限和资源权限, 就可以将这些权限组合起来,设置为角色,再将角色分配给用户简化操作。
用户需要分配角色,角色需要分配菜单权限和资源权限

在项目中,不会直接对某个用户进行菜单权限或者资源权限的分配,而是提前根据岗位设定不同的角色,再将角色分配给用户就可以了。
我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i
我正在编写一个包含C扩展的gem。通常当我写一个gem时,我会遵循TDD的过程,我会写一个失败的规范,然后处理代码直到它通过,等等......在“ext/mygem/mygem.c”中我的C扩展和在gemspec的“扩展”中配置的有效extconf.rb,如何运行我的规范并仍然加载我的C扩展?当我更改C代码时,我需要采取哪些步骤来重新编译代码?这可能是个愚蠢的问题,但是从我的gem的开发源代码树中输入“bundleinstall”不会构建任何native扩展。当我手动运行rubyext/mygem/extconf.rb时,我确实得到了一个Makefile(在整个项目的根目录中),然后当
我有一个在Linux服务器上运行的ruby脚本。它不使用rails或任何东西。它基本上是一个命令行ruby脚本,可以像这样传递参数:./ruby_script.rbarg1arg2如何将参数抽象到配置文件(例如yaml文件或其他文件)中?您能否举例说明如何做到这一点?提前谢谢你。 最佳答案 首先,您可以运行一个写入YAML配置文件的独立脚本:require"yaml"File.write("path_to_yaml_file",[arg1,arg2].to_yaml)然后,在您的应用中阅读它:require"yaml"arg
我已经在Sinatra上创建了应用程序,它代表了一个简单的API。我想在生产和开发上进行部署。我想在部署时选择,是开发还是生产,一些方法的逻辑应该改变,这取决于部署类型。是否有任何想法,如何完成以及解决此问题的一些示例。例子:我有代码get'/api/test'doreturn"Itisdev"end但是在部署到生产环境之后我想在运行/api/test之后看到ItisPROD如何实现? 最佳答案 根据SinatraDocumentation:EnvironmentscanbesetthroughtheRACK_ENVenvironm
有时我需要处理键/值数据。我不喜欢使用数组,因为它们在大小上没有限制(很容易不小心添加超过2个项目,而且您最终需要稍后验证大小)。此外,0和1的索引变成了魔数(MagicNumber),并且在传达含义方面做得很差(“当我说0时,我的意思是head...”)。散列也不合适,因为可能会不小心添加额外的条目。我写了下面的类来解决这个问题:classPairattr_accessor:head,:taildefinitialize(h,t)@head,@tail=h,tendend它工作得很好并且解决了问题,但我很想知道:Ruby标准库是否已经带有这样一个类? 最佳
我们的git存储库中目前有一个Gemfile。但是,有一个gem我只在我的环境中本地使用(我的团队不使用它)。为了使用它,我必须将它添加到我们的Gemfile中,但每次我checkout到我们的master/dev主分支时,由于与跟踪的gemfile冲突,我必须删除它。我想要的是类似Gemfile.local的东西,它将继承从Gemfile导入的gems,但也允许在那里导入新的gems以供使用只有我的机器。此文件将在.gitignore中被忽略。这可能吗? 最佳答案 设置BUNDLE_GEMFILE环境变量:BUNDLE_GEMFI
这似乎非常适得其反,因为太多的gem会在window上破裂。我一直在处理很多mysql和ruby-mysqlgem问题(gem本身发生段错误,一个名为UnixSocket的类显然在Windows机器上不能正常工作,等等)。我只是在浪费时间吗?我应该转向不同的脚本语言吗? 最佳答案 我在Windows上使用Ruby的经验很少,但是当我开始使用Ruby时,我是在Windows上,我的总体印象是它不是Windows原生系统。因此,在主要使用Windows多年之后,开始使用Ruby促使我切换回原来的系统Unix,这次是Linux。Rub
我正在玩HTML5视频并且在ERB中有以下片段:mp4视频从在我的开发环境中运行的服务器很好地流式传输到chrome。然而firefox显示带有海报图像的视频播放器,但带有一个大X。问题似乎是mongrel不确定ogv扩展的mime类型,并且只返回text/plain,如curl所示:$curl-Ihttp://0.0.0.0:3000/pr6.ogvHTTP/1.1200OKConnection:closeDate:Mon,19Apr201012:33:50GMTLast-Modified:Sun,18Apr201012:46:07GMTContent-Type:text/plain
我正在尝试使用Curbgem执行以下POST以解析云curl-XPOST\-H"X-Parse-Application-Id:PARSE_APP_ID"\-H"X-Parse-REST-API-Key:PARSE_API_KEY"\-H"Content-Type:image/jpeg"\--data-binary'@myPicture.jpg'\https://api.parse.com/1/files/pic.jpg用这个:curl=Curl::Easy.new("https://api.parse.com/1/files/lion.jpg")curl.multipart_form_
无论您是想搭建桌面端、WEB端或者移动端APP应用,HOOPSPlatform组件都可以为您提供弹性的3D集成架构,同时,由工业领域3D技术专家组成的HOOPS技术团队也能为您提供技术支持服务。如果您的客户期望有一种在多个平台(桌面/WEB/APP,而且某些客户端是“瘦”客户端)快速、方便地将数据接入到3D应用系统的解决方案,并且当访问数据时,在各个平台上的性能和用户体验保持一致,HOOPSPlatform将帮助您完成。利用HOOPSPlatform,您可以开发在任何环境下的3D基础应用架构。HOOPSPlatform可以帮您打造3D创新型产品,HOOPSSDK包含的技术有:快速且准确的CAD