如何解决基于 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开源社区,与我们共同打造高效、灵活的微服务应用。这里有丰富的技术干货,公司架构实战、最新的项目动态,更有机会与开发者们一起交流、成长。

相关推荐
wm104310 分钟前
java web springboot
java·spring boot·后端
小扳2 小时前
微服务篇-深入了解 MinIO 文件服务器(你还在使用阿里云 0SS 对象存储图片服务?教你使用 MinIO 文件服务器:实现从部署到具体使用)
java·服务器·分布式·微服务·云原生·架构
龙少95432 小时前
【深入理解@EnableCaching】
java·后端·spring
溟洵4 小时前
Linux下学【MySQL】表中插入和查询的进阶操作(配实操图和SQL语句通俗易懂)
linux·运维·数据库·后端·sql·mysql
SomeB1oody6 小时前
【Rust自学】6.1. 定义枚举
开发语言·后端·rust
SomeB1oody6 小时前
【Rust自学】5.3. struct的方法(Method)
开发语言·后端·rust
啦啦右一8 小时前
Spring Boot | (一)Spring开发环境构建
spring boot·后端·spring
森屿Serien8 小时前
Spring Boot常用注解
java·spring boot·后端
盛派网络小助手10 小时前
微信 SDK 更新 Sample,NCF 文档和模板更新,更多更新日志,欢迎解锁
开发语言·人工智能·后端·架构·c#
∝请叫*我简单先生10 小时前
java如何使用poi-tl在word模板里渲染多张图片
java·后端·poi-tl