如何解决基于 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 小时前
SpringBoot篇 单元测试 理论篇
spring boot·后端·单元测试
AI航海家(Ethan)6 小时前
PostgreSQL数据库的运行机制和架构体系
数据库·postgresql·架构
架构文摘JGWZ6 小时前
FastJson很快,有什么用?
后端·学习
BinaryBardC6 小时前
Swift语言的网络编程
开发语言·后端·golang
邓熙榆7 小时前
Haskell语言的正则表达式
开发语言·后端·golang
贾贾20237 小时前
配电自动化系统“三区四层”数字化架构
运维·科技·架构·自动化·能源·制造·智能硬件
专职9 小时前
spring boot中实现手动分页
java·spring boot·后端
Ciderw9 小时前
Go中的三种锁
开发语言·c++·后端·golang·互斥锁·
m0_7482463510 小时前
SpringBoot返回文件让前端下载的几种方式
前端·spring boot·后端
m0_7482304410 小时前
创建一个Spring Boot项目
java·spring boot·后端