springboot配合自定义注解实现轻量级权限验证

案例一:
  1. 定义权限注解:
/**
 * 角色的权限验证注解
 *
 * 被注解的方法必须满足指定的权限才能通过验证,如果有多个权限取 & 关系
 * @author ruiclear
 * @date 2020-08-12 18:19
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RoleAuthVerification {

    /**
     * 权限
     * @return
     */
    AuthPermissionEnum[] value();

}

  1. 定义权限枚举:
@AllArgsConstructor
@Getter
public enum  AuthPermissionEnum {
    ADMIN(1, "权限管理"),
    BURY(2, "埋点管理"),
    PARAMTER(3, "参数编辑");

    private Integer id;     //权限id
    private String name;    //权限
}

  1. 定义权限拦截器
/**
 * 角色的权限验证拦截器
 * @author ruiclear
 * @date 2020-08-12 18:43
 */
@Aspect
@Component
public class RoleAuthenticationInterceptor extends BaseInterceptor {

    @Autowired
    private AuthService authService;

    @Pointcut("@annotation(com.fenbi.pipe.princi.server.annotation.RoleAuthVerification)")
    public void verification(){}

    @Around("verification()")
    public Object authVerification(ProceedingJoinPoint joinPoint){
        try {
            Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();

            RoleAuthVerification annotation = method.getAnnotation(RoleAuthVerification.class);
            if (annotation != null) {
                String ldap = getUsername();
                AuthPermissionEnum[] authPermissionEnums = annotation.value();
                if(StringUtils.isNotBlank(ldap) && handleRoleAuthentication(authPermissionEnums, ldap)){
                    return joinPoint.proceed();
                }
                LOG.info("Ldap:{} no access", ldap);
                unauthorizedHandle();
            }
        }catch (Throwable throwable){
            LOG.error("Authentication verification failed",throwable);
            //为了能让GlobalExceptionHandler捕获到异常
            throw new RuntimeException("error");
        }
        return null;
    }

    /**
     * 验证当前用户是否有权限,多个权限取 & 关系
     * @param authPermissionEnums
     * @param ldap
     * @return true为有权限
     */
    private boolean handleRoleAuthentication(AuthPermissionEnum[] authPermissionEnums, String ldap){
        //根据用户名ldap通过authService服务接口查询用户的所有权限
        List<Integer> permissions = authService.getPermissionsByLdap(ldap);
        boolean flag =true;
        for(AuthPermissionEnum authPermissionEnum: authPermissionEnums){
            flag &= permissions.contains(authPermissionEnum.getId());
        }
        return flag;
    }

    /**
     * 无权限时返回403
     */
    private void unauthorizedHandle() throws IOException {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletResponse response = attributes.getResponse();
        response.setStatus(HttpStatus.FORBIDDEN.value());
        OutputStream os = response.getOutputStream();
        os.write("无权限访问".getBytes());
        os.flush();
        os.close();
    }
}
  1. 定义权限表结构
  • 用户表
DROP TABLE IF EXISTS `t_auth_user`;
create table t_auth_user
(
        id            int(11) auto_increment COMMENT '自增主键',
        name     varchar(50)             not null COMMENT '用户名',
        createTime   timestamp        NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
        updateTime   timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
        PRIMARY KEY (`id`),
        UNIQUE KEY `index_name` (`name`)
)ENGINE=InnoDB AUTO_INCREMENT=9156054 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='系统用户组表';
  • 角色表
DROP TABLE IF EXISTS `t_auth_group`;
create table t_auth_group
(
        id            int(11) auto_increment COMMENT '自增主键',
        name     varchar(50)             not null COMMENT '组名',
        createTime   timestamp        NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
        updateTime   timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
        PRIMARY KEY (`id`),
        UNIQUE KEY `index_name` (`name`)
)ENGINE=InnoDB AUTO_INCREMENT=9156054 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='系统用户组表';
  • 用户和角色关系表
DROP TABLE IF EXISTS `t_auth_group_ldap`;
create table t_auth_group_ldap
(
        id            int(11) auto_increment COMMENT '自增主键',
        userId     varchar(50)             not null COMMENT '用户id',
        groupId     int(11)             not null COMMENT '用户组id',
        createTime   timestamp        NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
        updateTime   timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
        PRIMARY KEY (`id`),
        UNIQUE KEY `index_group_ldap` (`groupId`,`ldap`)
)ENGINE=InnoDB AUTO_INCREMENT=9156054 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='系统用户和用户组多对多关系维护表';
  • 权限表
DROP TABLE IF EXISTS `t_auth_permission`;
create table t_auth_permission
(
        id            int(11) auto_increment COMMENT '自增主键',
        name     varchar(50)             not null COMMENT '权限名',
        createTime   timestamp        NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
        updateTime   timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
        PRIMARY KEY (`id`),
        UNIQUE KEY `index_name` (`name`)
)ENGINE=InnoDB AUTO_INCREMENT=9156054 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='系统用户组表';
  • 权限和角色关系表
DROP TABLE IF EXISTS `t_auth_group_permission`;
create table t_auth_group_permission
(
        id            int(11) auto_increment COMMENT '自增主键',
        groupId     int(11)             not null COMMENT '用户组id',
        permissionId     int(11)             not null COMMENT '权限,1:权限管理,2:埋点管理,3:参数编辑',
        createTime   timestamp        NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
        updateTime   timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
        PRIMARY KEY (`id`),
        UNIQUE KEY `index_group_permission` (`groupId`,`permissionId`)
)ENGINE=InnoDB AUTO_INCREMENT=9156054 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='系统用户组和功能权限多对多关系维护表';
  1. 在需要进行权限控制的colltroller接口上加上自定义注解,并指明权限
//表明只有登录用户同时拥有管理员和参数管理权限才有权限访问
 @RoleAuthVerification(AuthPermissionEnum.ADMIN, AuthPermissionEnum.PARAMTER)
  1. 总结:这种注解配合枚举配合aop切面的设计,会使得之后需要增加,删除,更改接口权限时变得异常方便,只需要在对应的接口上加上对应权限的注解即可。
案例二:

同样的思路,不光是可以进行权限控制,还可以控制任何任何基于数据库存储的状态权限控制(例如:如果希望对系统中的任务在不同的角色不同的任务进度进行访问控制,也可以用该思路,在拦截器中获取方法参数中的任务id(最好统一任务id在参数中的位置)查询任务的进度和登录用户在该任务中的角色然后跟colltroller接口上加上自定义注解值进行对比,一致则可以通过权限验证,否则返回403)

  1. 定义任务权限注解:
/**
 * 任务进度的验证注解
 *
 * 被注解的方法必须同时满足指定的role和progress才能通过验证
 *
 * 注意:为了更方便统一的获取任务id,约定被该注解注解的controller接口方法的第一个参数必须是任务id,否则IssueProgressVerificationInterceptor权限验证拦截器无效
 * @author ruiclear
 * @date 2020-08-12 18:19
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface IssueProgressVerification {

    /**
     * 角色
     * @return
     */
    TaskIssueRoleEnum role() default TaskIssueRoleEnum.DEFAULT;

    /**
     * 进度
     * @return
     */
    TaskIssueProgressEnum progress() default TaskIssueProgressEnum.DEFAULT;

}
  1. 定义任务权限枚举:
/**
 * 任务角色
 * @author ruiclear
 * @date 2020-07-17 18:38
 */
@AllArgsConstructor
@Data
public enum TaskIssueRoleEnum {

    DESIGNER(0, "分析师"),         //分析师
    DEVELOPER(1, "研发工程师"),     //研发工程师
    PRODUCER(2,"业务产品"),        //业务产品
    DEFAULT(-1, "NONE");          //默认

    private int role;
    private String name;
}


/**
 * 任务进度
 * @author ruiclear
 * @date 2020-07-17 18:38
 */
@AllArgsConstructor
@Data
public enum TaskIssueProgressEnum {

    DESIGN("设计",0),  //设计
    DEVELOP("开发",1), //开发
    TEST("测试",2),    //测试
    ONLINE("上线",3),  //上线
    DEFAULT("NONE",-1);//默认

    private int progress;
    private String progressName;

}
  1. 定义处理拦截器:
/**
 * 任务进度的验证拦截器
 * @author ruiclear
 * @date 2020-08-14 13:49
 */
@Aspect
@Component
public class IssueProgressVerificationInterceptor extends BaseInterceptor{

    //用来查询数据库任务状态
    @Autowired
    private TaskIssueService taskIssueService;
    //用来查询数据库任务关联的人员
    @Autowired
    private TaskPersonService taskPersonService;

    @Pointcut("@annotation(com.fenbi.pipe.princi.server.annotation.IssueProgressVerification)")
    public void verification(){}

    @Around("verification()")
    public Object issueProgressVerification(ProceedingJoinPoint joinPoint){
        try{

            String ldap = getUsername();
            if (StringUtils.isBlank(ldap)){
                unauthorizedHandle();
                return null;
            }

            Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
            IssueProgressVerification annotation = method.getAnnotation(IssueProgressVerification.class);
            if(annotation != null){
                TaskIssueProgressEnum progressEnum = annotation.progress();
                TaskIssueRoleEnum roleEnum = annotation.role();
                Integer issueId = parseIssueId(joinPoint);
                if(handleVerification(ldap, issueId, progressEnum, roleEnum)){
                      return joinPoint.proceed();
                  }
                unauthorizedHandle();
            }

        }catch (Throwable throwable){
            LOG.error("Authentication verification failed",throwable);
            //为了能让GlobalExceptionHandler捕获到异常
            throw new RuntimeException("error");
        }
        return null;
    }

    /**
     * 从方法参数中解析出issueId
     * @param joinPoint
     * @return
     */
    private Integer parseIssueId(ProceedingJoinPoint joinPoint){
        Object[] args = joinPoint.getArgs();
        //为了更方便统一的获取任务id,约定被@IssueProgressVerification注解的controller接口方法的第一个参数必须是任务id,否则该权限验证拦截器无效
        Integer issueId = Integer.parseInt(args[0].toString());

        return issueId;
    }

    /**
     * 校验是否有权限访问
     * @param ldap
     * @param issueId
     * @param progressEnum
     * @param roleEnum
     * @return
     */
    private boolean handleVerification(String ldap, Integer issueId, TaskIssueProgressEnum progressEnum, TaskIssueRoleEnum roleEnum){
        Optional<TaskIssue> taskIssue = taskIssueService.getTaskIssueById(issueId);
        if(!taskIssue.isPresent()){
            LOG.info("任务:[{}]不存在", issueId);
            return false;
        }
        Integer progress = taskIssue.get().getProgress();
        Integer role = taskPersonService.getRoleByIssueIdAndLdap(issueId, ldap);
        if(progressEnum.getValue() == progress && roleEnum.getValue() == role){
            LOG.info("Ldap:[{}] of role:[{}] operation issueId:[{}] on progress:[{}] have access!", ldap, TaskIssueRoleEnum.getEnumByRole(role).getName(), issueId, TaskIssueProgressEnum.getEnumByRole(role).getValueName());
            return true;
        }
        LOG.info("Ldap:[{}] of role:[{}] operation issueId:[{}] on progress:[{}] not have access!", ldap, TaskIssueRoleEnum.getEnumByRole(role).getName(), issueId, TaskIssueProgressEnum.getEnumByRole(role).getValueName());
        return false;
    }

    /**
    
    /**
     * 无权限时返回403
     */
    private void unauthorizedHandle() throws IOException {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletResponse response = attributes.getResponse();
        response.setStatus(HttpStatus.FORBIDDEN.value());
        OutputStream os = response.getOutputStream();
        os.write("无权限访问".getBytes());
        os.flush();
        os.close();
    }

}

  1. 在需要进行权限控制的colltroller接口上加上自定义注解,并指明权限
//表示该方法只是能登录用户在该任务的角色是设计师且任务进度是设计阶段时才有权限访问
@IssueProgressVerification(role = TaskIssueRoleEnum.DESIGNER, progress = TaskIssueProgressEnum.DESIGN)
//为了方便拦截器统一通用处理,强制约定需要进行任务权限控制得接口第一个参数必须是任务id
public Response addFrogTask(@PathVariable(value = "issueId") int issueId,
                                @RequestBody FrogTask frogTask){

        LOG.info("任务:[{}]新增forg", issueId);
        String ldap = getUsername();
        return buryLogic.addFrogTask(ldap, frogTask) ? Response.ok() : Response.warn("新增失败");
    }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值