写权限遇到的坑
写权限遇到的坑
项目中希望增加一层权限管理,分为多个层级,自顶向下为一、二、三...
顶级的权限往往是最高的,上下层之间的权限是无关的,也就是面上写层级,实际上是一个flat的权限列表
分层的意义在于,同一个接口(数据修改接口)各层级的不同权限都有可能允许访问
问题分析
多个层,每个层有多个角色(可以认为从0开始编号,编号从数据库或配置中心获取,不同层的权限标识获取渠道不一)
作用于方法(API接口)
一个数据修改接口,允许混合多层的角色,例如:
L1: true // super user layer, if true then the user is admin
L2: [2] // group user layer
L3: [1,3] // project user layer
对于这类复合权限,一个简单的思路是建立一个带逻辑连接的注解
实施
目录树
├─annotation
│ SuperUserLayerPermission.java
│ GroupUserLayerPermission.java
│ ProjectUserLayerPermission.java
│ UnionUserLayerPermission.java
│
├─aspect
│ PermissionAspect.java
│
├─common
│ UserThreadLocal.java
│
├─enums
│ GroupUserRole.java
│ LogicPermission.java
│ ProjectUserRole.java
│
├─service
│ SuperUserLayerPermissionService.java
│ GroupUserLayerPermissionService.java
│ ProjectUserLayerPermissionService.java
│ UnionUserLayerPermissionService.java
│
└─web
分层,对于每一层:
Enum
L1 --> 不需要建立,没有多个权限角色,只有 {true, false}
L2 --> G1 ~ G7
public enum GroupUserRole {
G1(1, "组内权限1"),
G2(2, "组内权限2"),
G3(3, "组内权限3"),
G4(4, "组内权限4"),
G5(5, "组内权限5"),
G6(6, "组内权限6"),
G7(7, "组内权限7"),
;
final int index;
final String code;
GroupUserRole(int index, String code) {
this.index = index;
this.code = code;
}
public int getIndex() {
return index;
}
public String getCode() {
return code;
}
}
L3 --> P1 ~ P4
public enum ProjectUserRole {
P1(1, "项目权限1"),
P2(2, "项目权限2"),
P3(3, "项目权限3"),
P4(4, "项目权限4"),
;
final int index;
final String code;
ProjectUserRole(int index, String code) {
this.index = index;
this.code = code;
}
public int getIndex() {
return index;
}
public String getCode() {
return code;
}
}
逻辑连接枚举
最简单的逻辑连接:AND, OR
public enum LogicPermission {
AND(1,"和"),
OR(2, "或"),
;
final int index;
final String code;
LogicPermission(int index, String code) {
this.index = index;
this.code = code;
}
public int getIndex() {
return index;
}
public String getCode() {
return code;
}
}
Annotation
L1 --> super user layer 用于标志是否为超级用户
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SuperUserLayerPermission {}
L2 --> group user layer
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface GroupUserLayerPermission {
GroupUserRole[] roles() default {};
}
L3 --> project user layer
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ProjectUserLayerPermission {
ProjectUserRole[] roles() default {};
}
组合
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface UnionUserLayerPermission {
LogicPermission relation() default LogicPermission.OR;
SuperUserLayerPermission[] superUserLayerPermission() default {};
GroupUserLayerPermission[] groupUserLayerPermission() default {};
ProjectUserLayerPermission[] projectUserLayerPermission() default {};
}
Service
superUser
@Service
public class SuperUserLayerPermissionService {
@Resource
private Map<String, Boolean> superUserMap; // 模拟用户权限的配置
public boolean checkSuperUser(SuperUserLayerPermission permission, String userId) {
return checkSU(userId);
}
private boolean checkSU(String userId) {
// 模拟判断用户权限的过程
return BooleanUtils.isTrue(superUserMap.get(userId));
}
}
groupUser
@Service
public class GroupUserLayerPermissionService {
@Resource
private Map<String, Integer> groupUserMap; // 模拟用户权限的配置
public boolean checkGroupUser(GroupUserLayerPermission permission, String userId) {
GroupUserRole[] roles;
if (permission == null || (roles = permission.roles()) == null || roles.length == 0) {
return false;
}
Integer roleIndex = checkGroup(userId);
if (roleIndex == null) {
return false;
}
return Arrays.stream(roles).anyMatch(r -> r.getIndex() == roleIndex);
}
private Integer checkGroup(String userId) {
return groupUserMap.get(userId);
}
}
projectUser
@Service
public class ProjectUserLayerPermissionService {
@Resource
private Map<String, Integer> projectUserMap; // 模拟用户权限的配置
public boolean checkProjectUser(ProjectUserLayerPermission permission, String userId) {
ProjectUserRole[] roles;
if (permission == null || (roles = permission.roles()) == null || roles.length == 0) {
return false;
}
Integer roleIndex = checkProject(userId);
if (roleIndex == null) {
return false;
}
return Arrays.stream(roles).anyMatch(r -> r.getIndex() == roleIndex);
}
private Integer checkProject(String userId) {
return projectUserMap.get(userId);
}
}
union
@Service
public class UnionUserLayerPermissionService {
@Resource
private SuperUserLayerPermissionService superUserLayerPermissionService;
@Resource
private GroupUserLayerPermissionService groupUserLayerPermissionService;
@Resource
private ProjectUserLayerPermissionService projectUserLayerPermissionService;
public boolean checkUnion(UnionUserLayerPermission permission, String userId) {
LogicPermission relation = permission.relation();
if (relation == null) {
return false;
}
Boolean superUserPermission = null;
SuperUserLayerPermission[] superUserLayerPermissions = permission.superUserLayerPermission();
if (permission.superUserLayerPermission().length > 0) {
superUserPermission = superUserLayerPermissionService.checkSuperUser(superUserLayerPermissions[0], userId);
}
Boolean groupUserPermission = null;
GroupUserLayerPermission[] groupUserLayerPermissions = permission.groupUserLayerPermission();
if (groupUserLayerPermissions.length > 0) {
groupUserPermission = groupUserLayerPermissionService.checkGroupUser(groupUserLayerPermissions[0], userId);
}
Boolean projectUserPermission = null;
ProjectUserLayerPermission[] projectUserLayerPermissions = permission.projectUserLayerPermission();
if (projectUserLayerPermissions.length > 0) {
projectUserPermission = projectUserLayerPermissionService.checkProjectUser(projectUserLayerPermissions[0], userId);
}
Stream<Boolean> booleanStream = Stream.of(superUserPermission, groupUserPermission, projectUserPermission).filter(Objects::nonNull);
if (relation == LogicPermission.AND) {
return booleanStream.allMatch(bool -> bool); // 都为 true
} else if (relation == LogicPermission.OR) {
return booleanStream.anyMatch(bool -> bool); // 一个为 true
} else {
return false;
}
}
}
通用工具-获取userId
public class UserThreadLocal {
private static final ThreadLocal<String> threadLocal = new ThreadLocal<>();
public static String currentUserId() {
return threadLocal.get();
}
public static void setUserId(String userId) {
threadLocal.set(userId);
}
}
Aspect
作用于目标包路径下的所有函数
* com.roles.web.*.*(..)
@Component
@Aspect
public class PermissionAspect {
@Resource
private SuperUserLayerPermissionService superUserLayerPermissionService;
@Resource
private GroupUserLayerPermissionService groupUserLayerPermissionService;
@Resource
private ProjectUserLayerPermissionService projectUserLayerPermissionService;
@Resource
private UnionUserLayerPermissionService unionUserLayerPermissionService;
@Pointcut("execution(* com.roles.web.*.*(..))")
public void permissionPointCut() {}
@Pointcut("@annotation(permission)")
public void annotationSuperUserLayerPermission(SuperUserLayerPermission permission) {}
@Pointcut("@annotation(permission)")
public void annotationGroupUserLayerPermission(GroupUserLayerPermission permission) {}
@Pointcut("@annotation(permission)")
public void annotationProjectUserLayerPermission(ProjectUserLayerPermission permission) {}
@Pointcut("@annotation(permission)")
public void annotationUnionUserLayerPermission(UnionUserLayerPermission permission) {}
@Around(value = "permissionPointCut() && annotationSuperUserLayerPermission(permission)", argNames = "joinPoint,permission")
public Object privilege(ProceedingJoinPoint joinPoint, SuperUserLayerPermission permission) throws Throwable {
String methodName = joinPoint.getSignature().getName();
String className = joinPoint.getSignature().getDeclaringType().getSimpleName();
String userId = UserThreadLocal.currentUserId();
if (superUserLayerPermissionService.checkSuperUser(permission, userId)) {
return joinPoint.proceed();
} else {
// log
String msg = String.format("%s has not permission at %s.%s", userId, className, methodName);
System.out.println(msg);
throw new RuntimeException(msg);
}
}
@Around(value = "permissionPointCut() && annotationGroupUserLayerPermission(permission)", argNames = "joinPoint,permission")
public Object privilege(ProceedingJoinPoint joinPoint, GroupUserLayerPermission permission) throws Throwable {
String methodName = joinPoint.getSignature().getName();
String className = joinPoint.getSignature().getDeclaringType().getSimpleName();
String userId = UserThreadLocal.currentUserId();
if (groupUserLayerPermissionService.checkGroupUser(permission, userId)) {
return joinPoint.proceed();
} else {
// log
String msg = String.format("%s has not permission at %s.%s", userId, className, methodName);
System.out.println(msg);
throw new RuntimeException(msg);
}
}
@Around(value = "permissionPointCut() && annotationProjectUserLayerPermission(permission)", argNames = "joinPoint,permission")
public Object privilege(ProceedingJoinPoint joinPoint, ProjectUserLayerPermission permission) throws Throwable {
String methodName = joinPoint.getSignature().getName();
String className = joinPoint.getSignature().getDeclaringType().getSimpleName();
String userId = UserThreadLocal.currentUserId();
if (projectUserLayerPermissionService.checkProjectUser(permission, userId)) {
return joinPoint.proceed();
} else {
// log
String msg = String.format("%s has not permission at %s.%s", userId, className, methodName);
System.out.println(msg);
throw new RuntimeException(msg);
}
}
@Around(value = "permissionPointCut() && annotationUnionUserLayerPermission(permission)", argNames = "joinPoint,permission")
public Object privilege(ProceedingJoinPoint joinPoint, UnionUserLayerPermission permission) throws Throwable {
String methodName = joinPoint.getSignature().getName();
String className = joinPoint.getSignature().getDeclaringType().getSimpleName();
String userId = UserThreadLocal.currentUserId();
if (unionUserLayerPermissionService.checkUnion(permission, userId)) {
return joinPoint.proceed();
} else {
// log
String msg = String.format("%s has not permission at %s.%s", userId, className, methodName);
System.out.println(msg);
throw new RuntimeException(msg);
}
}
}
使用案例
处于Aspect目标包路径下
* com.roles.web.*.*(..)
@RestController
@RequestMapping("/test")
public class TestController {
@GetMapping("/hello")
@UnionUserLayerPermission(
relation = LogicPermission.AND,
superUserLayerPermission = @SuperUserLayerPermission,
groupUserLayerPermission = @GroupUserLayerPermission(roles = {GroupUserRole.G2}),
projectUserLayerPermission = @ProjectUserLayerPermission(roles = {ProjectUserRole.P1, ProjectUserRole.P3})
)
public boolean test() {
return true;
}
@GetMapping("/world")
@SuperUserLayerPermission
public boolean hello() {
return true;
}
}