- 中文名
- 权限
- 外文名
- privilege
- 词 性
- 名词
- 释 义
- 职能权利范围
-
2019-07-31 19:21:31
水平权限漏洞是指Web应用程序接收到用户请求时,没有判断数据的所属人,或者在判断数据所属人时是从用户提交的参数中获取了userid,导致攻击者可以自行修改userid修改不属于自己的数据。
漏洞示例:
XXX/getAddress?id=1
如上,攻击者修改addressId即可得到他人的address信息。开发容易习惯性的在生成CRUD(增查改删)表单(或AJAX请求)的时候根据认证过的用户身份来找出其有权限的被操作对象的id来提供入口,然后让用户提交请求,并根据这个id来操作相关对象。在处理CRUD请求时,都天真地默认只有有权限的用户才知道这个入口,进而才能操作相关对象,因此就不再校验权限了。可悲剧的是大多数对象的ID都被设置为自增整型,所以攻击者只要对相关id加1、减1、直至遍历,就可以操作其他用户所关联的对象了。
修复方案:
1、最正规的方案:把权限的控制转移到数据接口层中,避免出现select/update/delete ... where addressID=#addressID#的SQL语句,使用select/update/delete... where addressID=#addressID# and ownerId=#userId#来代替,要求web层在调用数据接口层的接口时额外提供userid,而这个userid在web层看来通常只能通过seesion来取到,以防用户造假。但这个方案的缺陷在于实现起来要改动底层的设计,所以不适合作为修复方案,更适合作为在最开始设计时的统一控制方案。
2、最直接有效的修复方案:在web层的逻辑中做鉴权,检查提交CRUD请求的操作者(通过session或token等加密信息中得到,以防造假)与目标对象的权限所有者是否一致,如果不一致则阻断。这个方案实现成本低、能确保漏洞的修复质量,缺点是增加了一次查库操作。我之前一直用这种方案来对已发生的水平权限漏洞做紧急修复。
另外的方法:
1、可对ID加密
2、使用UUID
3、每一个信息增加一个发布人的字段,修改的人必须与发布的人为同一个人才可以访问
垂直权限漏洞是指Web应用没有做权限控制,或仅仅在菜单上做了权限控制,导致恶意用户只要猜到了其他页面的URL,就可以访问或控制其他角色拥有的数据或页面,达到权限提升的目的。
修复方案:
只需要对url资源进行权限验证即可。
更多相关内容 -
权限开发手册,数据权限和接口权限配置
2022-01-27 17:15:34权限开发手册 一般来说,权限有许多种,我们经常用到的一般有操作权限和数据权限两种。 功能权限 所谓操作权限就是有或者没有做某种操作的权限,具体表现形式就是你看不到某个菜单或按钮,当然也有的是把菜单或按钮...权限开发手册
一般来说,权限有许多种,我们经常用到的一般有操作权限和数据权限两种。
功能权限
所谓操作权限就是有或者没有做某种操作的权限,具体表现形式就是你看不到某个菜单或按钮,当然也有的是把菜单或按钮灰掉的形式。操作权限一般都在显示界面做一次控制,过滤没有权限的操作菜单或按钮,另外在真正执行操作时,还要进行一次权限检查,确保控制非授权访问。操作权限都是围绕角色来开展的,目的就是要实现操作的角色化。
功能权限控制
在上图所示的ER图关系中,角色(blade_role)菜单表(blade_menu)通过 角色菜单关联表(blade_role_menu)建立多对多联系。
操作权限授权的过程即为给角色授予某一菜单的操作权限,用户与角色通过用户平台扩展表(blade_user_web)建立联系,若用户拥有多个角色,则在平台扩展表的角色字段role_id中多个id用英文逗号分隔。基本关系如图所示:
系统一级菜单代表系统,二极菜单代表功能管理,三级菜单代表操作按钮,如增删改查权限
数据权限
1)、什么是数据权限?
所谓数据权限,就是有或者没有对某些数据的访问权限,具体表现形式就是当某用户有操作权限的时候,但不代表其对所有的数据都有查看或者管理权限。
数据权限有两种表现形式:一种是行权限、另外一种是列权限。- 所谓行权限,就是限制用户对某些行的访问权限,比如:只能对本人、本部门、本组织的数据进行访问;也可以是根据数据的范围进行限制,比如:合同额大小来限制用户对数据的访问。
- 所谓列权限,就是限制用户对某些列的访问权限,比如:某些内容的摘要可以被查阅,但是详细内容就只有VIP用户才能查看。通过数据权限,可以从物理层级限制用户对数据的行或列进行获取。
再比如:同样一个部门经理的角色,看到的数据是不一样的,所以,牵扯到数据二字,就应该不和操作二字等同起来。所以我们是通过职位来解决数据权限的,职位也可以叫岗位,是和数据查看范围有关系的,也就是组织结构里的关系。
所以在设计数据结构的时候,每个有数据范围的数据实体,都需要具备数据拥有人的字段,比如A部门的小b同学,他创建的数据,只能由A部门的部门经理看到,而同样在角色里具有部门经理的B部门的部门经理是不能看到的。所以延伸出来了一个设计思路,根据数据拥有人,圈定查看范围。数据范围的维度有:全部、本集团、本公司、本部门、自己,五个维度,可以满足大部分业务场景。
还有一个维度是自定义维度,可以自定义机构进行设置。这样的设计就达到了数据权限的操作灵活性。● 系统都离不开权限模块,它是支撑整个系统运行的基础模块。而根据项目类型和需求的不同,权限模块的设计更是大相径庭。但不管怎么变,权限模块从大的方面来说,可以分为三种大的类型:功能权限、接口权限、数据权限。
● 功能权限:也就是我们最熟悉的菜单、按钮权限。可以配置各个角色能看到的菜单、按钮从而从最表层分配好权限
● 接口权限:顾名思义,配置不通角色调用接口的权限。有些敏感接口,是只能有固定的一些角色才能调用,普通角色是不能调用的。这种情况需要有一个明确的系统来控制对应的访问权限
● 数据权限:是大家最为需求也是最广为谈资的一个设计理念。我们需要控制不通的角色、机构人员有查看不通数据范围的权限。如果你动手去设计数据权限,当你去各大平台、百度、谷歌查找设计思路的时候,你会发现很难找到有用的资料,很多设计思路局限性非常大。详情请参考:https://www.jianshu.com/p/0ab125cf8258
2)、行级别权限
给某一角色授权的数据仅能看到自己所管理的部分数据
3)、列级别权限
某一角色用户只能看到所拥有菜单权限下的部分列字段数据
实现不同人看不同数据,不同人对同一个页面操作不同字段。系统按钮权限和表单权限原来是正控制,只有授权的人才有权限,未授权看不到对应按钮;
数据权限的配置
为解决这一类疑难问题,提供三种方式来实现数据权限。
1.提供代码层配置@DataAuth注解达到脱离数据库的全固定配置方式
● 如果是纯注解配置,那么是不通过数据库的,相当于是离线配置。
● 我们只需要关注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类,他的所有字段我们都是可以根据占位符来获得的。
2.提供代码层配置@DataAuth注解配置数据权限资源编码来达到依赖数据库的半自动配置方式
这个就需要关联数据库,根据数据权限code码来设置数据权限定义规则。
3.Web可视化全自动动态配置
● 数据权限动态配置需要依赖数据库,所以我们需要前往web端进行配置
● 配置逻辑与纯注解配置一致,其实就是把注解配置拓展,并做成了web可视化可视化页面开发中,
注解说明
数据权限的核心注解为@DataAuth,它的定义代码如下:
package com.dindo.core.datascope.annotation; import com.dindo.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,如果有修改,则需要定义对应的字段名接口权限配置
1)、功能介绍
- 接口权限:顾名思义,配置不通角色调用接口的权限。有些敏感接口,是只能有固定的一些角色才能调用,普通角色是不能调用的。这种情况需要有一个明确的系统来控制对应的访问权限
- 接口权限系统,可以控制某些接口只能由固定的角色调用,可以动态控制不同的角色对不同接口的访问权限
通过接口配置实现,对接口的访问权限控制和数据权限控制,
接口是REST接口,接口权限认证机制使用Json web token (JWT)
接口权限调用流程:
(1)通过接口用户的用户名密码,调用鉴权token接口获取接口用户的token
该token,2个小时内有效
(2)把获取的token作为参数,调用接口的时候,会根据token去鉴权
(3)鉴权通过,接口会根据接口定义的编码,检验是否有访问权限
有则可以继续访问,无则提示访问受限
(4)有访问权限,则获取接口的数据权限规则,根据授权的数据权限规则返回需要的数据
实现一个新的接口,无需关注token的鉴权机制,接口权限判断方式
使用AOP实现接口拦截:@PreAuth
鉴权配置注解名称为 @PreAuth ,在需要进行鉴权配置的方法加上 @PreAuth 注解,并在注解内写 入相关的鉴权方法。
@PreAuth的注解定义:
package com.dindo.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.dindo.core.secure.annotation.PreAuth) || " + "@within(com.dindo.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接口权限的才能访问
用户角色关系
代表了菜单权限和资源权限的一种组合方式,比如我设置了多个用户需要相同的菜单权限和资源权限, 就可以将这些权限组合起来,设置为角色,再将角色分配给用户简化操作。
功能关系
用户需要分配角色,角色需要分配菜单权限和资源权限
在项目中,不会直接对某个用户进行菜单权限或者资源权限的分配,而是提前根据岗位设定不同的角色,再将角色分配给用户就可以了。 -
Android权限管理及动态申请权限
2022-01-21 00:59:35Android权限管理 一、基本介绍 Android安全架构规定:默认情况下,任何应用都没有权限执行对其他应用、操作系统或用户有不利影响的任何操作。这包括读写用户的私有数据(如联系人或电子邮件等)、读写其他应用...Android权限管理
一、基本介绍
Android安全架构规定:默认情况下,任何应用都没有权限执行对其他应用、操作系统或用户有不利影响的任何操作。这包括读写用户的私有数据(如联系人或电子邮件等)、读写其他应用的文件、执行网络访问、使设备保持唤醒状态等等。
应用权限基于系统安全功能,并有助于 Android 支持与用户隐私相关的以下目标:- 控制:用户可以控制他们与应用分享的数据。
- 透明度:用户可以了解应用使用了哪些数据以及应用为何访问相关数据。
- 数据最小化:应用仅能访问和使用用户调用的特定任务或操作所需的数据。
权限类型
Android将权限分为不同的类型,包括安装时权限、运行时权限和特殊权限。每种权限类型都指明了当系统授予应用该权限后,应用可以访问的受限数据范围以及应用可以执行的受限操作范围。
1、安装时权限
安装时权限会授予应用对受限数据的受限访问权限,并允许应用执行对系统或其他应用只有最低影响的受限操作。如果在应用中声明了安装时权限,系统会在用户允许该应用安装时自动授予相应权限。
安装时权限分为多个子类型,包括普通权限和签名权限。-
普通权限
此类权限允许访问超出应用沙盒的数据和执行超出应用沙盒的操作。但是,这些数据和操作对用户隐私及对其他应用的操作带来的风险非常小。系统会为普通权限分配“normal”保护级别。 -
签名权限
当应用声明了其他应用已定义的签名权限时,如果两个应用使用同一个签名文件进行签名,系统会在安装时向前者授予该权限。否则,系统无法向前者授予该权限。系统会为签名权限分配“signature”保护级别。
2、运行时权限
运行时权限也称为危险权限,此类权限会授予应用对受限数据的额外访问权限,并允许应用执行对系统和其他应用具有更严重影响的受限操作。所以需要在运行时请求权限,然后才能访问受限数据或执行受限操作。当应用请求运行时权限时,系统会弹框显示运行时权限申请提示。许多运行时权限会访问用户私有数据,这是一种特殊的受限数据,其中包含可能比较敏感的信息。例如,位置信息和联系信息就属于用户私人数据。系统会为运行时权限分配“dangerous”保护级别。
运行时权限机制是Android 6.0(M)的新特性,因此不同Android版本的设备以及应用设置的targetSdkVersion都会影响到应用申请危险权限时的表现。- 在Android6.0版本之前的设备上使用时,应用还是使用旧的权限系统,危险权限还是在应用安装时就会进行申请,不管targetSdkVersion是否大于23。
- 在Android6.0版本及更高版本的设备上使用时,则会根据应用中设置的targetSdkVersion进行判断:1)若targetSdkVersion低于23,则继续使用旧规则,危险权限在安装时进行申请;2)若targetSdkVersion大于等于23时,则应用需要在运行时进行权限动态申请。
注:从Android 6.0(Marshmallow,API 23)开始,用户可以在任何时候撤销应用的某个权限,即使应用的targetSdkVersion小于23。因此必须确保在请求权限失败后,应用还能表现良好。
如果设备运行的是Android 6.0或更高版本,并且应用的targetSdkVersion是23或更高版本,则当用户请求危险权限时系统会发生以下行为:
- 如果应用请求一个已经在其清单文件中列出的危险权限,并且应用当前没有拥有该权限组的任何权限,那么系统就会向用户显示一个对话框询问用户是否授权,该对话框会描述应用想要访问的权限组而不是组内的特定权限。例如,如果一个应用请求READ_CONTACTS权限,系统会弹出对话框告知用户应用需要访问设备的联系人,如果用户允许授权,那么系统将授予应用所需的权限。
- 如果应用请求一个已经在其清单文件中列出的危险权限,并且应用当前已经拥有了该权限组的其它危险权限,系统会立即授予该权限而不需要通知用户。例如,如果一个应用之前已经请求过并已经被授予了READ_CONTACTS权限,那么之后它请求WRITE_CONTACTS时系统将立即授予该权限。
权限组概念:
根据设备的功能和特性,权限被分为权限组。系统以权限组的形式来处理权限请求,一个权限组可能对应Manifest中申请的几个权限。例如,STORAGE权限组包括READ_EXTERNAL_STORAGE和WRITE_EXTERNAL_STORAGE权限。权限组的方式让用户更容易理解权限,和更方便处理APP的权限请求,防止过多的复杂的授予单独的权限。
任何权限都可以属于一个权限组,包括正常权限和应用自定义的权限。但权限组仅当权限危险时才影响用户体验。可以忽略正常权限的权限组。危险权限以及对应分组如下:
3、特殊权限
特殊权限与特定的应用操作相对应。只有平台和原始设备制造商 (OEM) 可以定义特殊权限。此外,如果平台和 OEM 想要防止有人执行功能特别强大的操作(例如通过其他应用绘图),通常会定义特殊权限。系统会为特殊权限分配“appop”保护级别。在Android 6.0以后,只有系统应用才能够使用这些特殊权限。
二、动态权限申请
1、主要使用方法
(1)ContextCompat.checkSelfPermission
检查应用是否具有某个危险权限,如果应用具有此权限,方法将返回 PackageManager.PERMISSION_GRANTED;如果应用不具有此权限,方法将返回 PackageManager.PERMISSION_DENIED。(2)ActivityCompat.requestPermissions
应用可以通过这个方法动态申请权限,调用后会弹出一个对话框提示用户授权所申请的权限组。(3)ActivityCompat.shouldShowRequestPermissionRationale
检查此权限上次是否被拒绝过。如果应用之前请求过此权限但用户拒绝了请求,但可继续请求,则此方法将返回 true。而以下几种情况均返回false: 1)用户从未申请过该权限;2)用户在过去拒绝了权限请求,并在权限请求系统对话框中选择了 Don’t ask again 选项;3)用户允许了该权限申请请求;4)设备规范禁止应用具有该权限。
因此单独使用该方法去做判断,是没用的,应该在请求权限回调中使用。注:不同手机系统对于权限处理方面可能存在差异,部分手机系统在弹出授权申请时选择拒绝就默认了不再弹出,此时调用此方法返回false。
(4)onRequestPermissionsResult
当应用请求权限时,系统将向用户显示一个对话框。当用户响应时,系统将调用应用的 onRequestPermissionsResult() 方法,向其传递用户响应,处理对应的场景。2、调用流程
(1)在AndroidManifest.xml中对要申请的权限进行申明。
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"></uses-permission> <uses-permission android:name="android.permission.CAMERA"></uses-permission> <uses-permission android:name="android.permission.WRITE_CONTACTS"></uses-permission> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"></uses-permission>
(2)申请权限前对权限进行检查,若应用不具有该权限,则进行权限申请;若一次申请多个权限,则应该逐个进行权限检查,最后对未授权的权限进行统一申请。
private String PM_SINGLE=Manifest.permission.WRITE_EXTERNAL_STORAGE; //申请单个权限 public void applyForSinglePermission(){ Log.i(TAG,"applyForSinglePermission"); try{ //如果操作系统SDK级别在23之上(android6.0),就进行动态权限申请 if(Build.VERSION.SDK_INT>=23){ //判断是否拥有权限 int nRet=ContextCompat.checkSelfPermission(this,PM_SINGLE); Log.i(TAG,"checkSelfPermission nRet="+nRet); if(nRet!= PackageManager.PERMISSION_GRANTED){ Log.i(TAG,"进行权限申请..."); ActivityCompat.requestPermissions(this,new String[]{PM_SINGLE},10000); } else{ showToast("权限已授权"); } } }catch(Exception e){ e.printStackTrace(); } } private String[] PM_MULTIPLE={ Manifest.permission.ACCESS_COARSE_LOCATION,Manifest.permission.CAMERA,Manifest.permission.WRITE_CONTACTS }; //申请多个权限 public void applyForMultiplePermissions(){ Log.i(TAG,"applyForMultiplePermissions"); try{ //如果操作系统SDK级别在23之上(android6.0),就进行动态权限申请 if(Build.VERSION.SDK_INT>=23){ ArrayList<String> pmList=new ArrayList<>(); //获取当前未授权的权限列表 for(String permission:PM_MULTIPLE){ int nRet=ContextCompat.checkSelfPermission(this,permission); Log.i(TAG,"checkSelfPermission nRet="+nRet); if(nRet!= PackageManager.PERMISSION_GRANTED){ pmList.add(permission); } } if(pmList.size()>0){ Log.i(TAG,"进行权限申请..."); String[] sList=pmList.toArray(new String[0]); ActivityCompat.requestPermissions(this,sList,10000); } else{ showToast("全部权限都已授权"); } } }catch(Exception e){ e.printStackTrace(); } }
(3)重写onRequestPermissionsResult回调方法,对用户的权限授权操作进行监听处理。
@Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); try{ ArrayList<String> requestList=new ArrayList<>();//允许询问列表 ArrayList<String> banList=new ArrayList<>();//禁止列表 for(int i=0;i<permissions.length;i++){ if(grantResults[i] == PackageManager.PERMISSION_GRANTED){ Log.i(TAG,"【"+permissions[i]+"】权限授权成功"); } else{ //判断是否允许重新申请该权限 boolean nRet=ActivityCompat.shouldShowRequestPermissionRationale(this,permissions[i]); Log.i(TAG,"shouldShowRequestPermissionRationale nRet="+nRet); if(nRet){//允许重新申请 requestList.add(permissions[i]); } else{//禁止申请 banList.add(permissions[i]); } } } //优先对禁止列表进行判断 if(banList.size()>0){//告知该权限作用,要求手动授予权限 showFinishedDialog(); } else if(requestList.size()>0){//告知权限的作用,并重新申请 showTipDialog(requestList); } else{ showToast("权限授权成功"); } }catch (Exception e){ e.printStackTrace(); showToast("权限申请回调中发生异常"); } } public void showFinishedDialog(){ AlertDialog dialog = new AlertDialog.Builder(this) .setTitle("警告") .setMessage("请前往设置中打开相关权限,否则功能无法正常运行!") .setPositiveButton("确定", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { // 一般情况下如果用户不授权的话,功能是无法运行的,做退出处理 finish(); } }) .create(); dialog.show(); } public void showTipDialog(ArrayList<String> pmList){ AlertDialog dialog = new AlertDialog.Builder(this) .setTitle("提示") .setMessage("【"+pmList.toString()+"】权限为应用必要权限,请授权") .setPositiveButton("确定", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { String[] sList=pmList.toArray(new String[0]); //重新申请该权限 ActivityCompat.requestPermissions(MainActivity.this,sList,10000); } }) .create(); dialog.show(); }
三、PermissionUtil工具类
这个类是自己封装的一个Android权限申请的工具类,可以通过权限组名或权限名来对多个权限进行申请,并将对用户授权操作的处理逻辑封装起来,可以通过传入回调接口,针对用户授权情况来定义不同的响应处理。
PermissionUtil实现:
import android.Manifest; import android.app.Activity; import android.content.Context; import android.content.pm.PackageManager; import android.os.Build; import android.util.Log; import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; /** * author:chenjs */ public class PermissionUtil { private static final String TAG=PermissionUtil.class.getSimpleName(); private static final boolean LOG_FLAG=true;//日志标识 //日历 private static final String[] Group_Calendar={ Manifest.permission.READ_CALENDAR,Manifest.permission.WRITE_CALENDAR }; //照相机 private static final String[] Group_Camera={ Manifest.permission.CAMERA }; //通讯录 private static final String[] Group_Contacts={ Manifest.permission.WRITE_CONTACTS,Manifest.permission.GET_ACCOUNTS, Manifest.permission.READ_CONTACTS }; //定位 private static final String[] Group_Location={ Manifest.permission.ACCESS_FINE_LOCATION,Manifest.permission.ACCESS_COARSE_LOCATION }; //麦克风 private static final String[] Group_Microphone={ Manifest.permission.RECORD_AUDIO }; //电话 private static final String[] Group_Phone={ Manifest.permission.READ_PHONE_STATE,Manifest.permission.CALL_PHONE, Manifest.permission.READ_CALL_LOG,Manifest.permission.WRITE_CALL_LOG, Manifest.permission.ADD_VOICEMAIL,Manifest.permission.USE_SIP, Manifest.permission.PROCESS_OUTGOING_CALLS }; //传感器 private static final String[] Group_Sensors={ Manifest.permission.BODY_SENSORS }; //短信 private static final String[] Group_Sms={ Manifest.permission.READ_SMS,Manifest.permission.SEND_SMS, Manifest.permission.RECEIVE_SMS,Manifest.permission.RECEIVE_MMS, Manifest.permission.RECEIVE_WAP_PUSH }; //存储 private static final String[] Group_Storage={ Manifest.permission.READ_EXTERNAL_STORAGE,Manifest.permission.WRITE_EXTERNAL_STORAGE }; private static Map<String,String[]> m_PermissionGroupList=null; static{ initMap(); } /** * 通过权限组名来申请一组权限 * @param context * @param permissionGroupName * @param requestCode * @param listener */ public static void requestByGroupName(Activity context, String permissionGroupName,int requestCode,OnPermissionsListener listener){ requestByGroupName(context, new String[]{permissionGroupName}, requestCode, listener); } /** * 通过权限组名来申请多组权限 * @param context Activity上下文 * @param pgNameArray 多个要申请的权限组名称 * @param requestCode 请求码 * @param listener 回调接口 */ public static void requestByGroupName(Activity context, String[] pgNameArray,int requestCode,OnPermissionsListener listener){ showLog("requestByPermissionGroup"); try{ //如果操作系统SDK级别在23之上(android6.0),就进行动态权限申请 if(Build.VERSION.SDK_INT>=23 && pgNameArray!=null){ String[] permissionsList=getAppPermissionsList(context);//应用权限列表 ArrayList<String> targetList=new ArrayList<>(); if(permissionsList==null || permissionsList.length==0){ showLog("获得权限列表为空"); return; } for(String groupName:pgNameArray){ ArrayList<String> tmpPermissionList=isPermissionDeclared(permissionsList,groupName); if(tmpPermissionList==null){//未找到 showLog("未找到["+groupName+"]中的权限"); continue; } for(int i=0;i<tmpPermissionList.size();i++){ //判断是否拥有权限 int nRet=ContextCompat.checkSelfPermission(context,tmpPermissionList.get(i)); if(nRet!= PackageManager.PERMISSION_GRANTED){ targetList.add(tmpPermissionList.get(i)); } } } if(targetList.size()>0){ showLog("进行以下权限申请:"+targetList.toString()); String[] sList=targetList.toArray(new String[0]); ActivityCompat.requestPermissions(context,sList,requestCode); } else{ showLog("全部权限都已授权"); if(listener!=null){ listener.onPermissionsOwned(); } } } }catch(Exception e){ e.printStackTrace(); } } /** * 通过权限名来申请一组权限 * @param context * @param permission * @param requestCode * @param listener */ public static void requestByPermissionName(Activity context, String permission,int requestCode,OnPermissionsListener listener){ requestByPermissionName(context, new String[]{permission}, requestCode, listener); } /** * 通过权限名来申请多组权限 * @param context Activity上下文 * @param permissionArray 多个要申请的权限名称 * @param requestCode 请求码 * @param listener 回调接口 */ public static void requestByPermissionName(Activity context, String[] permissionArray,int requestCode,OnPermissionsListener listener){ showLog("requestPermissions"); try{ //如果操作系统SDK级别在23之上(android6.0),就进行动态权限申请 if(Build.VERSION.SDK_INT>=23 && permissionArray!=null){ ArrayList<String> targetList=new ArrayList<>(); for(String strPermission:permissionArray){ //判断是否拥有权限 int nRet=ContextCompat.checkSelfPermission(context,strPermission); if(nRet!= PackageManager.PERMISSION_GRANTED){ targetList.add(strPermission); } } if(targetList.size()>0){ showLog("进行以下权限申请:"+targetList.toString()); String[] sList=targetList.toArray(new String[0]); ActivityCompat.requestPermissions(context,sList,requestCode); } else{ showLog("全部权限都已授权"); if(listener!=null){ listener.onPermissionsOwned(); } } } }catch(Exception e){ e.printStackTrace(); } } /** * 针对申请权限时的用户操作进行处理 * @param context * @param permissions 申请的权限 * @param grantResults 各权限的授权状态 * @param listener 回调接口 * @param controlFlag 控制标识,用于判断当响应禁止列表后,是否继续处理可再申请列表(避免出现同时处理禁止列表和可再申请列表,互相干扰,比如弹出两个提示框) */ public static void onRequestPermissionsResult(Activity context,String[] permissions, int[] grantResults,OnPermissionsListener listener,boolean controlFlag) { try{ ArrayList<String> requestList=new ArrayList<>();//可再申请列表 ArrayList<String> banList=new ArrayList<>();//禁止列表 for(int i=0;i<permissions.length;i++){ if(grantResults[i] == PackageManager.PERMISSION_GRANTED){ showLog("["+permissions[i]+"]权限授权成功"); } else{ boolean nRet=ActivityCompat.shouldShowRequestPermissionRationale(context,permissions[i]); //Log.i(TAG,"shouldShowRequestPermissionRationale nRet="+nRet); if(nRet){//允许重新申请 requestList.add(permissions[i]); } else{//禁止申请 banList.add(permissions[i]); } } } do{ //优先对禁止列表进行判断 if(banList.size()>0){ if(listener!=null){ listener.onPermissionsForbidden(permissions,grantResults,banList); } if(!controlFlag){//对禁止列表处理后,且控制标识为false,则跳过对可再申请列表的处理 break; } } if(requestList.size()>0){ if(listener!=null){ listener.onPermissionsDenied(permissions,grantResults,requestList); } } if(banList.size()==0 && requestList.size()==0){ showLog("权限授权成功"); if(listener!=null){ listener.onPermissionsSucceed(); } } }while (false); }catch (Exception e){ e.printStackTrace(); } } /** * 判断权限状态 * @param context * @param permission 权限名 * @return */ public static boolean checkPermission(Context context,String permission){ try{ //如果操作系统SDK级别在23之上(android6.0),就进行动态权限申请 if(Build.VERSION.SDK_INT>=23){ int nRet= ContextCompat.checkSelfPermission(context,permission); showLog("checkSelfPermission nRet="+nRet); return nRet==PackageManager.PERMISSION_GRANTED? true : false; } return true; }catch(Exception e){ e.printStackTrace(); return false; } } /** * 获得当前应用清单中的权限列表 * @param context 应用上下文 * @return */ public static String[] getAppPermissionsList(Context context){ try{ PackageManager packageManager = context.getApplicationContext().getPackageManager(); String packageName=context.getApplicationContext().getPackageName(); String[] array = packageManager.getPackageInfo(packageName,PackageManager.GET_PERMISSIONS).requestedPermissions; return array; }catch (Exception e){ e.printStackTrace(); } return null; } /** * 判断权限列表中是否声明了指定权限组中的权限 * @param permissionList 权限列表 * @param permissionGroup 权限组名 * @return 存在则返回找到的权限组权限,否则返回null */ public static ArrayList<String> isPermissionDeclared(String[] permissionList, String permissionGroup){ try{ if(permissionList!=null && permissionGroup!=null){ String[] pmGroup=m_PermissionGroupList.get(permissionGroup); if(pmGroup!=null){ ArrayList<String> arrayList=new ArrayList<>(); //遍历 for(int i=0;i<pmGroup.length;i++){ String strPermission=pmGroup[i]; for(int j=0;j< permissionList.length;j++){ if(strPermission.equals(permissionList[j])){//找到指定权限组中的权限 arrayList.add(strPermission); break; } } } if(arrayList.size()==0){ return null; } return arrayList; } } }catch (Exception e){ e.printStackTrace(); } return null; } private static void initMap(){ if(m_PermissionGroupList==null){ m_PermissionGroupList=new HashMap<>(); m_PermissionGroupList.put(Manifest.permission_group.CALENDAR,Group_Calendar); m_PermissionGroupList.put(Manifest.permission_group.CAMERA,Group_Camera); m_PermissionGroupList.put(Manifest.permission_group.CONTACTS,Group_Contacts); m_PermissionGroupList.put(Manifest.permission_group.LOCATION,Group_Location); m_PermissionGroupList.put(Manifest.permission_group.MICROPHONE,Group_Microphone); m_PermissionGroupList.put(Manifest.permission_group.PHONE,Group_Phone); m_PermissionGroupList.put(Manifest.permission_group.SENSORS,Group_Sensors); m_PermissionGroupList.put(Manifest.permission_group.SMS,Group_Sms); m_PermissionGroupList.put(Manifest.permission_group.STORAGE,Group_Storage); } } private static void showLog(String str){ if(LOG_FLAG){ Log.i(TAG,str); } } public interface OnPermissionsListener { /** * 权限都已拥有时的处理 */ void onPermissionsOwned(); /** * 权限被禁止时的处理 * @param permissions 申请的全部权限 * @param grantResults 各权限的授权状态 * @param pmList 禁止申请的权限列表 */ void onPermissionsForbidden(String[] permissions, int[] grantResults,ArrayList<String> pmList); /** * 权限被拒绝时的处理 * @param permissions * @param grantResults * @param pmList 可再申请的权限列表 */ void onPermissionsDenied(String[] permissions, int[] grantResults,ArrayList<String> pmList); /** * 权限申请成功时的处理 */ void onPermissionsSucceed(); } }
使用方式可以参考以下例子:
private final int RequestCode3_1 =10004; //由使用者根据自身需求进行重写 private PermissionUtil.OnPermissionsListener mListener3_1=new PermissionUtil.OnPermissionsListener() { @Override public void onPermissionsOwned() { showTip("该权限已拥有"); } @Override public void onPermissionsForbidden(String[] permissions, int[] grantResults, ArrayList<String> pmList) { showTip("以下权限被禁止:"+pmList.toString()); AlertDialog dialog = new AlertDialog.Builder(mContext) .setTitle("警告") .setMessage("请前往设置中手动打开"+pmList.toString()+"权限!") .setPositiveButton("确定", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { } }) .create(); dialog.show(); } @Override public void onPermissionsDenied(String[] permissions, int[] grantResults, ArrayList<String> pmList) { showTip("以下权限被拒绝授权:"+pmList.toString()); //重新请求权限 AlertDialog dialog = new AlertDialog.Builder(mContext) .setTitle("提示") .setMessage("【"+pmList.toString()+"】权限为应用必要权限,请授权") .setPositiveButton("确定", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { String[] sList=pmList.toArray(new String[0]); //重新申请权限,通过权限名的方式申请多组权限 PermissionUtil.requestByPermissionName(mContext,sList, RequestCode3_1,mListener3_1); } }) .create(); dialog.show(); } @Override public void onPermissionsSucceed() { showTip("权限申请成功"); } }; public void requestPermission3_1(){ String[] pgArray=new String[]{ Manifest.permission_group.SENSORS,Manifest.permission_group.SMS,Manifest.permission_group.STORAGE }; showTip("进行[传感器+短信+存储]权限申请..."); //通过权限组名来申请指定权限 PermissionUtil.requestByGroupName(mContext,pgArray, RequestCode3_1,mListener3_1); } @Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); switch (requestCode){//可以针对不同的权限申请操作进行不同处理,也可以统一以相同方式处理(不对requestCode进行判断) case RequestCode3_1:{ PermissionUtil.onRequestPermissionsResult(mContext,permissions,grantResults,mListener3_1,false); break; } } }
具体的工具类以及演示工程放在Github上,感兴趣的同学可以了解一下。如果有什么问题,欢迎来一起讨论,共同进步。
GitHub地址:https://github.com/JINSHENGCCC/Android_Common/tree/master/AndroidPermission/src
四、参考
-
一个免费的java权限后台管理系统框架
2015-01-23 17:52:35完整的功能应用,包括:后台权限、人员机构、参数代码、角色权限、上传文件、日志管理等内容。 您可以直接在其上面开发业务模块,具体下载和演示可访问:www.walkersoft.net。 开发文档整理中,很快会更新到网站中。... -
android6.0 GPS 使用demo(包括动态权限申请)
2016-06-16 15:22:03android6.0 GPSdemo 动态申请权限 -
43.Django权限系统auth模块详解
2021-09-10 08:05:19①检查用户权限: user.has_perm方法用于检查用户是否拥有操作某个模型的权限: user.has_perm(‘blog.add_article’) user.has_perm(‘blog.change_article’) user.has_perm(‘blog.delete_article’) 上述语句...昨天我们为了登录admin,通过命令创建了超级用户,你是不是有个疑问——这创建的超级用户的信息是存放在哪里了呢?
这就想到了我们映射数据库时,Django自动创建的一些表(这也是之前进行数据库迁移时没有提到的那些表)!!!0.初接触
如上图就是Django自带的auth系统对应的表,也就是存放了之前创建的超级用户信息的表(也也就是之前没有提及到的数据库迁移生成的表~)
注意点:上面所示表中有多对多表关系生成的中间表,而Django很人性化的一点是:如果是多对多关系产生的中间表,其命名方式是主表在前,从表在后!比如auth_group_permissions表,其中auth_group就是主表,auth_permissions就是从表,如果要进行两表关联,则从auth_group到auth_permissions是正向!!!
从上图表的名称我们就能看出,
auth_user,auth_group,auth_permission分别
存放了用户,用户组,权限的信息表.
另外三张表就是多对多关系的中间表1.Django权限系统auth模块
-
auth模块是Django提供的标准权限管理系统,可以提供用户
-
-
【100个 Unity踩坑小知识点】| Unity调用API ,动态获取Android权限,附带所有Android权限表格
2022-03-10 11:31:59--如需使用人脸识别,还要添加:摄相头权限,拍照需要用到 --> <uses-permission android:name="android.permission.CAMERA" /> Android危险权限 编号 权限组 权限 0 CALENDAR READ_CALENDAR WRITE_CALENDAR 1 ... -
Linux系统管理---权限管理
2022-02-11 09:12:301、权限管理 1)文件权限:读写执行 2)目录权限 3)目录与文件权限的RWX对比 2、文件隐藏属性: 1)命令:lsattr 描述:查看文件系统属性 2)echo命令:输出内容 3)文件权限的修改: 4)文件归属修改: 3... -
android-support-v4 最新版本 解决Android6.0以上系统权限问题 下载
2016-05-18 12:06:42android-support-v4 最新版本v4,可以解决Android6.0以上系统权限问题,内部有requestPermissions 等新的方法 -
Linux的权限
2022-02-22 14:50:34Linux的权限一、Linux权限的概念二、Linux权限管理1 、文件访问者的分类2 、文件类型和访问权限3、文件访问权限的相关设置方法 一、Linux权限的概念 Linux下有两种用户:超级用户(root)、普通用户 超级用户:可以... -
uniapp 模块权限配置 权限管理 权限设置
2020-07-22 19:28:36在uni-app中如何配置权限 首先,我们先通过API了解,他对权限的需求,比如在上个帖子中 ===>>对APP后台静默更新升级对文件管理和文件下载权限的需求 当时我们使用了这个权限: android.permission.WRITE_... -
linux修改文件夹-文件目录权限
2021-05-11 05:31:27Linux、Fedora、Ubuntu修改文件、文件夹权限的方法差不多。很多人开始接触Linux时都很头痛Linux的文件权限问题。这里告诉大家如何修改Linux文件-文件夹权限。以主文件夹下的一个名为“cc”的文件夹为例。下面一步... -
前端如何来做权限管理?
2021-02-06 18:16:53说道角色管理-永远分不开权限管理,这里我们简单说一下权限管理,权限管理简单来说就是控制用户所能使用当前系统的哪些功能,具体细化到模块,按钮等等。一个用户可能会有多种角色,而一个角色中又会对应着多个页面... -
chmod 777命令_Linux权限详解 命令之 chmod:修改权限
2020-11-10 20:44:14阅读目录(Content)权限简介Linux系统上对文件的权限有着严格的控制,用于如果相对某个文件执行某种操作,必须具有对应的权限方可执行成功。Linux下文件的权限类型一般包括读,写,执行。对应字母为 r、w、x。Linux下... -
原来在Android中请求权限也可以有这么棒的用户体验
2020-09-15 07:28:00然而随着我发现这个项目不仅有学习的价值,还可以真正投入到实际项目的使用当中,于是后面又对PermissionX进行了多个版本的迭代,目前已经成为了一个非常稳定和方便的权限请求库。 在1.3.0版本当中,PermissionX... -
Windows的权限(用户、组和访问控制)
2022-02-04 11:05:53⑦特别的权限:其他不常用的权限,比如删除权限的权限; New Technology File System 文件系统(NTFS) 文件夹的NTFS权限 (文件夹内的文件或文件夹会默认继承上一级目录的权限)。 ①完全控制:对文件或者文件夹可... -
PermissionX 1.5发布,支持申请Android特殊权限啦
2021-07-24 11:55:16Hello大家早上好,说起PermissionX,其实我已经有段时间没有更新这个框架了。一是因为现在工作确实比较忙,没有过去那么多的闲暇时间来写开源项目,二是因为...而这些特殊权限并不属于Android运行时权限的一部分,所以 -
MySQL查看用户权限及权限管理
2021-05-21 17:56:22一、 MySQL权限级别介绍 全局——可以管理整个MySQL 库——可以管理指定的数据库 表——可以管理指定数据库的指定表 字段——可以管理指定数据库的指定表的指定字段 权限存储在mysql库的user, db, tables_priv, ... -
Android权限管理--权限类型
2022-03-14 17:21:12Android 将权限分为不同的类型,包括安装时权限、运行时权限和特殊权限。每种权限类型都指明了当系统授予应用该权限后,应用可以访问的受限数据范围以及应用可以执行的受限操作范围。 1.普通权限(normal) 此类... -
C#插件开发框架Ribbon界面含权限管理系统附NorthWind示例源码
2013-02-12 14:31:09C#插件开发框架,Ribbon界面实现软件的快速开发,含权限管理系统,在下载中包含有NorthWind数据库的操作示例,各功能与Ribbon界面之间实现了解耦,在不更改现有代码的前提下,能够向系统增加功能.以下是一个向... -
权限系统的基本概念和架构
2020-12-21 19:33:06权限系统是我们在系统设计和应用中一种非常常见的系统。一般来说权限系统的功能分为认证和授权两种。认证就非常简单的,验证完用户名密码就算认证成功,而授权里面的套路就很多了,本文将会详细讲解权限系统中的一些... -
Android申请权限(相机权限和读写权限)
2022-02-10 11:26:41为Android应用申请权限,失败则退出应用 -
MySQL数据库的用户授权_查看权限
2021-10-05 11:39:20文章目录授予用户拥有某个数据库的全部权限授予用户拥有所有数据库的全部权限授予用户拥有某个数据库的部分权限授予用户拥有某个数据库中的某个表的部分权限授予用户也具有给其它用户授权的权限查看用户所拥有的权限... -
最全的权限系统设计
2020-03-22 20:47:53权限管理是公司数据安全的重要保证,针对不同的岗位,不同的级别看到的数据是不一样的,操作数据的限制也是不一样的。比如涉及到资金的信息只开放给财务的相关岗位,涉及到配置的信息只开放给运营的相关岗位,这样各... -
java权限管理系统的jar包
2012-11-10 10:42:19java权限管理系统的jar包配合http://www.oschina.net/code/snippet_59256_15087使用。 -
Linux文件权限
2022-02-13 09:36:27Linux权限的理解 -
数仓工具—Hive进阶之权限管理(7)
2021-01-13 17:18:391. 我们可以通过视图的方式达到更喜欢的权限管理,例如实现字段级别的权限控制 2. 对象(表、视图、databases) 的所有权一般是归创建者所有的,包括执行授权的权限 3. admin 用户可以在配置文件里进行配置,角色的... -
数据权限设计研究-行数据权限
2019-01-17 19:22:56数据权限设计研究-行数据权限关于权限设计功能权限数据权限前提数据分类几种场景设计方案与思路映射表提供过滤sql的方法测试实际应用查询新增修改删除修改数据的私有,公开,部门属性私有改为部门私有改为公开部门改... -
基于vue2 + koa 2.0的前后端登录权限和路由权限控制实践
2022-01-21 14:21:59前后台交互过程中,涉及到用户登陆权限和前端路由模块是比较复杂的模块,这里需要我们理清楚整个过程,才能把整个工程架构搭起来。其实在我之前的一篇文章(https://juejin.cn/post/6983287769426559007) 中也大体... -
Hive表权限
2020-10-09 11:13:27Hive表权限 hive 一、赋角色权限 –创建和删除角色 create role role_name; drop role role_name; –展示所有roles show roles –赋予角色权限 grant select on database db_name to role role_name; grant select ...