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