如何解决基于 Redis 的网关鉴权导致的 RESTful API 拦截问题?

前言

在上一遍文章《基于Redis的网关鉴权方案与性能优化》我们设计了结合Redis缓存,通过网关鉴权的方式,很大程度上提高了开发的效率,也是企业中最常见的鉴权架构方案。但是遇到RESTful风格的api,不能精细化的控制,就比如我们公司的老项目,统一采用POST请求方式,就规避了RESTful API的问题,但是新项目可不这么玩了,实际开发中哪怕我不遵循RESTful API,但是我接口是可以随便定义的,不限制任何的请求方式,那么我们的网关该如何鉴权呢?

一、设计思路

1.1 RESTful API示例

首先关于RESTful的概念,它是一种软件设计风格,而不是标准,其余概念不多赘述,我们来看一下RESTful API的接口示例截图:

同一个请求路径表示不同的请求方式,所以在网关鉴权的时候,我们应该怎么才能精准拦截呢?我们需要加入一个请求方式,根据不同的请求方式来判断请求的是哪个类型的API接口,如下设计图所示:

💥💥💥 通过 【角色+请求方式+接口路径】 三重元素组合设计,能给唯一标识具体的API接口,以此来鉴别我们的用户是否拥有请求权限。

1.2 新增菜单权限(前端)

有了上述的一个设计思路之后,我们在新增菜单的时候,需要加入一个字段 【请求方式】 ,这样我们才能按照 【角色+请求方式+接口路径】 的方式存储到Redis,从而在网关鉴权的时候去做一个路径匹配。

为了节省输入API的路径和请求方式的时间,提高开发效率,我们将菜单的新增编辑页面进行了优化,通过点击输入框,手动选择具体接口和请求方式,调整如下:

💥💥💥 接口由来?获取方式很多,就不一一举例了,这里我折中了一下,选择了通过请求Swagger的开放接口【/v3/api-docs】,将返回的json字符串做了解析处理,就能够动态的返回系统中的所有接口信息了,这样我们前端界面只需要手动选择具体的接口即可。

💥💥💥核心代码(白嫖+收藏):

java 复制代码
public static List<ApiEndpointVO> parseApiDocs(String apiDocsJson) throws Exception {
       List<ApiEndpointVO> apiEndpoints = new ArrayList<>();
       ObjectMapper objectMapper = new ObjectMapper();
       JsonNode rootNode = objectMapper.readTree(apiDocsJson);

       // 解析 paths 节点
       JsonNode pathsNode = rootNode.get("paths");
       if (pathsNode != null) {
           Iterator<String> pathIterator = pathsNode.fieldNames();
           while (pathIterator.hasNext()) {
               String path = pathIterator.next();
               JsonNode methodsNode = pathsNode.get(path);

               // 遍历每个方法(GET, POST 等)
               Iterator<String> methodIterator = methodsNode.fieldNames();
               while (methodIterator.hasNext()) {
                   String method = methodIterator.next();
                   JsonNode methodDetailsNode = methodsNode.get(method);

                   String summary = methodDetailsNode.has("summary") ? methodDetailsNode.get("summary").asText() : "No summary provided";

                   // 创建 ApiEndpoint 对象
                   ApiEndpointVO apiEndpoint = new ApiEndpointVO();
                   apiEndpoint.setPath(path);
                   apiEndpoint.setMethod(method.toUpperCase());
                   apiEndpoint.setSummary(summary);

                   apiEndpoints.add(apiEndpoint);
               }
           }
       }
       return apiEndpoints;
   }
java 复制代码
// 调用接口获取 JSON
String service = environment.getProperty("spring.application.name");
String apiDocsJson = restTemplate.getForObject("http://" + service + "/v3/api-docs", String.class);
List<ApiEndpointVO> list = SwaggerEndpointUtils.parseApiDocs(apiDocsJson);

1.3 角色授权(后端)

后端角色授权的时候,我们根据角色查询出其拥有的菜单权限,根据【请求方式】分组,将其api接口路径,一并存入到Redis中:

权限KEY value
micro-admin:system:role:method:WEB:GET /micro-system/menu/list
micro-admin:system:role:method:WEB:GET /micro-system/menu/getById/{id}
micro-admin:system:role:method:WEB:POST /micro-system/menu/add

KEY的组成: micro-admin:system:role:method:【角色编码】:【请求方式】

VALUE: 接口API路径

java 复制代码
@Override
public void updateRolePermsCache(String roleCode) {
    redisUtils.deleteObject(ROLE_METHOD_KEY + roleCode);

    List<SysRolePermsVO> rolePermsList = this.baseMapper.getRolePermsList(roleCode);
    Map<String, List<SysRolePermsVO>> collect =
            rolePermsList.stream().collect(Collectors.groupingBy(SysRolePermsVO::getRequestMethod));
    for (Map.Entry<String, List<SysRolePermsVO>> m : collect.entrySet()) {
        String method = m.getKey();
        Set<String> paths = m.getValue().stream().map(SysRolePermsVO::getMenuUrl).filter(Objects::nonNull).collect(Collectors.toSet());
        redisUtils.setCacheSet(ROLE_METHOD_KEY + roleCode + ":" + method, paths);
    }
}

1.4 网关鉴权(后端)

在网关层面根据用户【角色+请求方式】获取接口列表,与当前请求路径做正则匹配,即可判断出用户是否拥有访问权限。

1)通过 【角色+请求方式】 的组合,可以解决同一接口在不同请求方式下的权限控制问题。 2)通过 【接口正则表达式】,可以灵活处理包含 {} 占位符的接口路径问题。

💥💥💥 重点:/micro-system/menu/getById/{id} 】这样的接口,我们需要用正则表达式来匹配,将 {} 路径替换为正则表达式组,以此来匹配检查。

💥💥💥核心代码(白嫖+收藏):

java 复制代码
public static void main(String[] args) {
    // 根据用户【角色+请求方式】获取接口列表
    List<String> paths = new ArrayList<>();
    paths.add("/micro-system/operator/feign/getUserInfoByOpenid/{openid}");
    paths.add("/micro-system/operator/feign/updateStatus/{id}/{status}");
    paths.add("/micro-system/operator/feign/getUserInfoByUsername/{username}");

    // 当前请求路径
    String requestPath  = "/micro-system/operator/feign/updateStatus/123456/1";

    // 调用方法判断路径是否匹配
    boolean matched = isPathMatched(paths, requestPath);
    System.out.println("是否匹配: " + matched);
}

// 检查 requestPath 是否匹配 paths 中的任意一个模板
public static boolean isPathMatched(List<String> paths, String requestPath) {
    for (String path : paths) {
        String regex = convertTemplateToRegex(path);
        System.out.println(regex);
        if (Pattern.matches(regex, requestPath)) {
            return true; // 如果匹配,则返回 true
        }
    }
    return false; // 如果没有匹配的,返回 false
}

public static String convertTemplateToRegex(String template) {
    // 替换路径模板中的 {xxx} 替换为正则表达式捕获组 (.+) ,匹配一个或多个字符
    return template.replaceAll("\\{[^/]+\\}", "([^/]+)");
}

总结

MicroAdmin 是一款基于Spring Cloud Alibaba的微服务架构,前端基于Vue3 + Element-Plus,宗旨:高效、灵活、可扩展。通过封装基础能力,大幅提升开发效率,助您快速构建企业级应用。

想了解更多的微服务架构实践吗? 关注公众号【Java星探】,加入MicroAdmin开源社区,与我们共同打造高效、灵活的微服务应用。这里有丰富的技术干货,公司架构实战、最新的项目动态,更有机会与开发者们一起交流、成长。

相关推荐
6<7几秒前
【go】静态类型与动态类型
开发语言·后端·golang
热爱运维的小七2 分钟前
从数据透视到AI分析,用四层架构解决运维难题
运维·人工智能·架构
lamdaxu2 分钟前
Arthas基础
后端
技术liul6 分钟前
解决Spring Boot Configuration Annotation Processor not configured
java·spring boot·后端
小华同学ai12 分钟前
1K star!这个开源项目让短信集成简单到离谱,开发效率直接翻倍!
后端·程序员·github
HelloDam13 分钟前
基于元素小组的归并排序算法
后端·算法·排序算法
Net分享14 分钟前
在 ASP.NET Core 中使用 Confluent.Kafka 实现 Kafka 生产者和消费者
后端
HelloDam15 分钟前
单元格法近似求解多边形最大内接矩形问题【思路讲解+java实现】
后端
Winwoo16 分钟前
服务端推送 SSE
后端
桂月二二35 分钟前
实时事件流处理架构的容错设计
架构·wpf