网站建设批复意见,中国最新领导班子,域名注册官网免费,建站平台 阿里巴巴当开发者在构建网站、移动设备或物联网应用程序时#xff0c;API 网关作为微服务架构中不可或缺的控制组件#xff0c;是流量的核心进出口。通过有效的权限管控#xff0c;可以实现认证授权、监控分析等功能#xff0c;提高 API 的安全性、可用性、拓展性以及优化 API 性能…当开发者在构建网站、移动设备或物联网应用程序时API 网关作为微服务架构中不可或缺的控制组件是流量的核心进出口。通过有效的权限管控可以实现认证授权、监控分析等功能提高 API 的安全性、可用性、拓展性以及优化 API 性能。在上一篇文章 Authing 结合 APISIX 实现统一可配置 API 权限网关【快速启动版】中演示了通过 Authing 权限管理 APISIX 实现 API 的访问控制效果本文将教你如何实现上述能力的具体实践方法。
01 关于 Authing
Authing 是国内首款以开发者为中心的全场景身份云产品集成了所有主流身份认证协议为企业和开发者提供完善安全的用户认证和访问管理服务。以「API First」作为产品基石把身份领域所有常用功能都进行了模块化的封装通过全场景编程语言 SDK 将所有能力 API 化提供给开发者。同时用户可以灵活的使用 Authing 开放的 RESTful APIs 进行功能拓展满足不同企业不同业务场景下的身份和权限管理需求。
02 关于 APISIX
Apache APISIX 是一个动态、实时、高性能的 API 网关提供负载均衡、动态上游、灰度发布、服务熔断、身份认证、可观测性等丰富的流量管理功能。Apache APISIX 不仅支持插件动态变更和热插拔而且拥有众多实用的插件。Apache APISIX 的 OpenID Connect 插件支持 OpenID Connect 协议用户可以使用该插件让 Apache APISIX 对接 Authing 服务作为集中式认证网关部署于企业中。
03 业务目标
通过 Authing 权限管理 APISIX 实现 API 的访问控制
04 如何实现
本文所涉及到的代码已经上传到 Github
Python 插件 https://github.com/fehu-asia/authing-apisix-python-agent
Java Adapter: https://github.com/fehu-asia/authing-apisix-java-adapter
Java 插件 https://github.com/fehu-asia/authing-apisix-java-agent
4.1 业务架构 系统整体包含了三大部分 Authing 服务集群、Authing 插件适配服务以及 APISIX 网关本方案建立需要配置和开发的部分有四个部分Authing API 权限结构配置、APISIX 插件和路由配置、APISIX 插件开发部署以及业务适配服务开发其中业务适配服务包含了认证和授权的主要逻辑(使用单独服务承载)避免了插件的频繁更新和部署。
这里需要说明的是之所以采用 Adapter 的方式来实现是因为插件我们并不希望经常变动但需求可能是无法避免的需要经常变动所以我们将具体的鉴权逻辑放在 Adapter 插件只实现请求转发和根据 Adapter 的返回结果决定是否放行同时无状态的插件可以让我们实现更多的场景复用和能力扩展例如进行鉴权结果的缓存实现后续只需维护 Adapter 即可。
当然我们也可将具体的逻辑放在插件里。
注意本教程只用于与 APISIX 和 Authing 进行集成对于生产环境使用您需要自行开发插件并保证其安全性及可用性等本文档不承诺此插件可以用于生产环境。
APISIX 基础环境搭建
git clone https://github.com/apache/apisix-docker.git
cd apisix-docker/example
docker-compose -p docker-apisix up -d到这里可以使用 docker ps 查看 apisix docker 进程启动状态 随后访问 localhost:9000 可以进入 dashboard 界面进行路由和插件的配置。
4.2 在 Authing 对 API 进行管理
登录 Authing 官网www.authing.com 进行以下操作
4.2.1 创建应用 配置 Token 签名算法为 RS256 及校验 AccessToken 的方式为 none 。 4.2.2 创建用户
进入 Authing 控制台-用户管理-用户列表-点击创建用户后可以根据不同方式用户名、手机号、邮箱创建测试用户如下图所示 4.2.3 创建 API
进入 Authing 控制台-权限管理-创建资源可以选择创建树数据类型的资源如下图所示 4.2.4 创建策略
进入权限管理-数据资源权限-数据策略标签可以点击创建策略来新建数据访问策略如下图所示。策略包含了对应的权限空间中定义的数据以及操作创建后能够基于此策略对不同对象(用户、角色、用户组等)进行授权管理。 4.2.5 API 授权 4.3 APISIX 路由和 SOCK 配置 4.3.1 SOCK 配置
APISIX 使用 unix sock 与插件进程通信因此需要配置对应的 sock 端口
需要将宿主机上的 sock 文件挂载到容器里插件启动的时候会在宿主机上创建这个 sock 文件此处需要注意的是若 APISIX 是先于插件启动的当插件启动后则需要重启下 APISIX 容器确保插件先于 APISIX 启动。
文件位置 /apisix-docker/example/docker-compose.yml apisix部分 apisix:image: apache/apisix:latestrestart: alwaysvolumes:- ./apisix_log:/usr/local/apisix/logs- ./apisix_conf/config.yaml:/usr/local/apisix/conf/config.yaml:ro- /tmp/runner.sock:/tmp/runner.sock4.3.2 路由配置
X-API-KEY /apisix/apisix-docker/example/apisix_conf/config.yaml curl http://127.0.0.1:9180/apisix/admin/routes/1 -H X-API-KEY: edd1c9f034335f136f87ad84b625c8f1 -X PUT -d
{uri: /*,plugins: {ext-plugin-pre-req: {conf: [{name: authing_agent,value: {\url\: \{适配服务的访问地址}\,\user_pool_id\: \{用户池ID}\,\user_pool_secret\: \{用户池密钥}\}}]}},upstream: {type: roundrobin,nodes: {httpbin.org:80: 1}}
}ext-plugin-pre-req 是需要启用的插件类型, 在配置 conf 中需要确定两个变量
“name”: 插件名称 “value”: “{“url”: “适配服务的访问地址”,“user_pool_id”: “用户池ID”,“user_pool_secret”: “用户池密钥”}”
其中访问地址格式为 {{domain}}:{{port}}/{{path}}
例如 “{“url”: “http://192.168.1.123:8080/isAllow”,“user_pool_id”: “124u2353h2t24he2u349382u152”,“user_pool_secret”: “6435462313i5412njburh2u34”}”
4.4 APISIX 插件开发和部署
4.4.1 建立插件工程目录
git clone https://github.com/apache/apisix-python-plugin-runner.git 进入目录 make setup 进入目录 make install 进入目录并修改 apisix/plugins/rewrite.py 文件将请求参数传递到 Authing
4.4.2 编写 Agent (python) 插件代码
可使用其他语言实现例如 Java、Go、Lua
之所以采用 Python 的原因是因为环境初始化比较简单可以让开发者快速了解 APISIX 的插件的开发机制。
https://apisix.apache.org/docs/apisix/external-plugin/
from typing import Any
from apisix.runner.http.request import Request
from apisix.runner.http.response import Response
from apisix.runner.plugin.core import PluginBase
import json
import requests
import jsondef isAllow(request,config):return requests.request(POST, config.get(url), headers{Content-Type: application/json}, datajson.dumps({request: request,pluginConfig: config}))class Rewrite(PluginBase):def name(self) - str:return authing_agentdef config(self, conf: Any) - Any:return confdef filter(self, conf: Any, request: Request, response: Response):# 组装 Adapter 请求参数authing_request {uri: request.get_uri(),method: request.get_method(),args:request.get_args(),headers:request.get_headers(),request_id:request.get_id(),host:request.get_var(host),remote_addr: request.get_remote_addr(),configs: request.get_configs()}# 接收 Adapter 响应判断是否放行authing_response isAllow(authing_request,eval(conf))if authing_response.text ! ok:response.set_status_code(authing_response.status_code)response.set_body(authing_response.text) 4.4.3 运行 Agent 插件
nohup make dev #后台运行 agent 程序4.5 适配器开发
4.5.1 通信接口设计
启动代理 Authing 服务自行实现对应接口以 springboot 为例接口结构如下 4.5.2 部分 JAVA 文件列出如下
IsAllowController.java
package cn.authing.apisix.adapter.controller;import cn.authing.apisix.adapter.entity.APISIXRquestParams;
import cn.authing.sdk.java.client.ManagementClient;
import cn.authing.sdk.java.dto.CheckPermissionDto;
import cn.authing.sdk.java.dto.CheckPermissionRespDto;
import cn.authing.sdk.java.dto.CheckPermissionsRespDto;
import cn.authing.sdk.java.model.ManagementClientOptions;
import cn.hutool.http.HttpStatus;
import cn.hutool.http.HttpUtil;
import com.google.gson.Gson;
import com.nimbusds.jose.JOSEException;
import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jose.JWSObject;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.jwk.source.RemoteJWKSet;
import com.nimbusds.jose.proc.BadJOSEException;
import com.nimbusds.jose.proc.JWSKeySelector;
import com.nimbusds.jose.proc.JWSVerificationKeySelector;
import com.nimbusds.jose.proc.SecurityContext;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.proc.ConfigurableJWTProcessor;
import com.nimbusds.jwt.proc.DefaultJWTProcessor;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StopWatch;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;import java.net.MalformedURLException;
import java.net.URL;
import java.text.ParseException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;/*** author Gao FeiHu* version 1.0.0* date 2022.12.22* email gaofeihuauthing.cn*/
RestController
Slf4j
public class IsAllowController {/*** 用户池 ID*/public static String ACCESS_KEY_ID ;/*** 用户池密钥*/public static String ACCESS_KEY_SECRET ;/*** Authing SDK* See* https://docs.authing.cn/v3/reference/*/ManagementClient managementClient;/*** 初始化 ManagementClient** param ak 用户池 ID* param aks 用户池密钥*/public void init(String ak, String aks) {log.info(init ManagementClient ......);try {// 保存用户池 ID 和密钥ACCESS_KEY_ID ak;ACCESS_KEY_SECRET aks;// 初始化ManagementClientOptions options new ManagementClientOptions();options.setAccessKeyId(ak);options.setAccessKeySecret(aks);managementClient new ManagementClient(options);} catch (Exception e) {e.printStackTrace();System.err.println(初始化 managementClient 失败可能无法请求);}}/*** 是否放行** param apisixRquestParams 请求 body 包含了 APISIX 插件的配置以及请求上下文* param response HttpServletResponse* return 200 OK 放行* 403 forbidden 禁止访问* 500 internal server error 请求错误 可根据实际需求放行或拒绝*/PostMapping(/isAllow)public Object isAllow(RequestBody APISIXRquestParams apisixRquestParams, HttpServletResponse response) {// 请求计时器StopWatch stopWatch new StopWatch();stopWatch.start();// 请求 ID 与 APISIX 一致String requestID apisixRquestParams.getRequest().getRequest_id();log.info({} 请求入参 : {} , requestID, new Gson().toJson(apisixRquestParams));try {// 0. 若插件为多实例用于实现不同业务逻辑此处可对应修改为多实例模式if (managementClient null || !ACCESS_KEY_ID.equals(apisixRquestParams.getPluginConfig().get(user_pool_id))) {init((String) apisixRquestParams.getPluginConfig().get(user_pool_id), (String) apisixRquestParams.getPluginConfig().get(user_pool_secret));}// 1. 拿到 accessTokenString authorization (String) apisixRquestParams.getRequest().getHeaders().get(authorization);if (!StringUtils.hasLength(authorization)) {return result(response, stopWatch, requestID, HttpStatus.HTTP_UNAUTHORIZED, HTTP_UNAUTHORIZED);}String accessToken authorization;if (authorization.startsWith(Bearer)) {accessToken authorization.split( )[1].trim();}log.info({} accessToken : {} , requestID, accessToken);// 2. 解析 accessToken 拿到应用 ID 和用户 IDJWSObject parse JWSObject.parse(accessToken);MapString, Object payload parse.getPayload().toJSONObject();String aud (String) payload.get(aud);String sub (String) payload.get(sub);// 3. 校验 accessToken// 在线校验String result onlineValidatorAccessToken(accessToken, aud);log.info({} accessToken 在线结果 : {} , requestID, result);if (!result.contains({\active\:true)) {return result(response, stopWatch, requestID, HttpStatus.HTTP_UNAUTHORIZED, HTTP_UNAUTHORIZED);}// // 离线校验
// if (null offlineValidatorAccessToken(accessToken, aud)) {
// return result(response, stopWatch, requestID, HttpStatus.HTTP_UNAUTHORIZED, HTTP_UNAUTHORIZED);
// }// 4. 获取到 APISIX 中的请求方法对应 Authing 权限中的 actionString action apisixRquestParams.getRequest().getMethod();// 5. 获取到 APISIX 中的请求路径String resource apisixRquestParams.getRequest().getUri();// 6. 去 Authing 请求判断是否有权限// TODO 可在此添加 Redis 对校验结果进行缓存CheckPermissionDto reqDto new CheckPermissionDto();reqDto.setUserId(sub);reqDto.setNamespaceCode(aud);reqDto.setResources(Arrays.asList(resource.substring(1, resource.length())));reqDto.setAction(action);CheckPermissionRespDto checkPermissionRespDto managementClient.checkPermission(reqDto);log.info(new Gson().toJson(checkPermissionRespDto));// 7. 由于我们是单个 resource 校验所以只需要判断第一个元素即可ListCheckPermissionsRespDto resultList checkPermissionRespDto.getData().getCheckResultList();if (resultList.isEmpty() || resultList.get(0).getEnabled() false) {return result(response, stopWatch, requestID, HttpStatus.HTTP_FORBIDDEN, HTTP_FORBIDDEN);}return result(response, stopWatch, requestID, HttpStatus.HTTP_OK, ok);} catch (Exception e) {e.printStackTrace();log.error(请求错误, e);return result(response, stopWatch, requestID, HttpStatus.HTTP_INTERNAL_ERROR, e.getMessage());}}public String result(HttpServletResponse response, StopWatch stopWatch, String requestID, int status, String msg) {stopWatch.stop();log.info({} 请求耗时{} , 请求出参 : http_status_code{},msg{} , requestID, stopWatch.getTotalTimeMillis() ms, status, msg);response.setStatus(status);return msg;}public String onlineValidatorAccessToken(String accessToken, String aud) {HashMapString, Object paramMap new HashMap();paramMap.put(token, accessToken);paramMap.put(token_type_hint, access_token);paramMap.put(client_id, aud);return HttpUtil.post(https://api.authing.cn/ aud /oidc/token/introspection, paramMap);}public JWTClaimsSet offlineValidatorAccessToken(String accessToken, String aud) {try {ConfigurableJWTProcessorSecurityContext jwtProcessor new DefaultJWTProcessor();JWKSourceSecurityContext keySource null;keySource new RemoteJWKSet(new URL(https://api.authing.cn/ aud /oidc/.well-known/jwks.json));JWSAlgorithm expectedJWSAlg JWSAlgorithm.RS256;JWSKeySelectorSecurityContext keySelector new JWSVerificationKeySelector(expectedJWSAlg, keySource);jwtProcessor.setJWSKeySelector(keySelector);return jwtProcessor.process(accessToken, null);} catch (MalformedURLException e) {e.printStackTrace();} catch (ParseException e) {e.printStackTrace();} catch (BadJOSEException e) {e.printStackTrace();} catch (JOSEException e) {e.printStackTrace();} finally {return null;}}
}APISIXRquestParams.java
package cn.authing.apisix.adapter.entity;import lombok.Data;
import lombok.ToString;import java.util.Map;/*** APISIX 请求实体类*/
Data
ToString
public class APISIXRquestParams {/*** APISIX 请求上下文*/APISIXRequest request;/*** 插件配置*/MapString, Object pluginConfig;}APISIXRequest.java
package cn.authing.apisix.adapter.entity;import lombok.Data;
import lombok.ToString;import java.util.Map;Data
ToString
public class APISIXRequest {private String uri;private String method;private String request_id;private String host;private String remote_addr;private MapString, Object args;private MapString, Object headers;private MapString, Object configs;
}4.6 访问测试
4.6.1 未认证 4.6.2 无权限 4.6.3 认证通过并成功访问 404 是因为上游服务没有这个接口但认证和 API 鉴权已经通过
05 总结
如果您需要对 API 进行细颗粒度的管理可以通过本方案来实现我们可以在 Adapter 实现更加细粒度的 API 访问控制以及更加场景化的权限方案。