操作场景
如果 API 网关提供的认证鉴权方式不能满足您的需求,您可以使用自定义认证插件,通过您自定义的代码进行认证鉴权。
自定义认证插件作用在请求过程中,客户端请求 API 网关后,API 网关会将请求内容转发到认证函数中。您可以将认证函数部署在云函数上,公网、或内网 VPC 上,认证通过后请求才会被转发给业务后端,否则将拒绝请求。
前提条件
对于部署在公网/内网的服务,需要按照最下方的代码模板搭建对应的认证服务。
操作步骤
步骤1:创建认证函数
对于部署在公网或内网 VPC 的认证函数,可省略该步骤。
1. 登录 云函数控制台。
2. 在左侧导航栏,单击函数服务,进入函数列表页。
3. 单击页面左上角的新建,选择模板创建,可搜索认证类函数模板,如图模糊搜索自定义认证、选择标签为 Python2.7 的版本。
4. 模板默认提供了三种参数认证的示例:Header、Query、Body。实际业务中,您可以根据需要选择不同的认证方式,也可以结合多个参数进行认证。
步骤2:创建自定义认证插件
1. 登录 API 网关控制台。
2. 在左侧导航栏,单击插件 > 自定义插件,进入自定义插件列表页。
3. 单击页面左上角的新建,新建一个自定义认证插件。
对于部署在云函数的认证服务,创建自定义认证插件时需要填写的数据如下:
参数 | 是否必填 | 说明 |
选择函数 | 必填 | 选择认证函数所在的命名空间、名称和版本。 |
后端超时 | 必填 | 设置 API 网关转发到认证函数的后端超时时间,超时时间的最大限制为30分钟。在 API 网关调用认证函数,未在超时时间内获得响应时,API 网关将终止此次调用,并返回相应的错误信息。 |
是否发送 Body | 必填 | 当此选项的值为“是”时,会把客户端请求的 Header、Body、Query 都会发送给云函数。 当此选项的值为“否”时,不会发送请求 Body。 |
认证参数 | 选填 | 选填,但如果设置了参数则请求数据中为必填。当“缓存时间”不为0时,必须设置此参数。使用缓存时,此参数将作为搜索条件来查询认证结果。 |
缓存时间 | 必填 | 设置认证结果缓存的时间。值为0时代表不开启缓存,缓存时间最大支持3600秒。 |
对于部署在公网的认证服务,创建自定义认证插件时需要填写的数据如下:
参数 | 是否必填 | 说明 |
请求方法 | 必填 | 请求自定义认证函数的方法,支持 GET、POST、PUT、DELETE、HEAD、ANY。 |
公网服务 | 必填 | 自定义认证服务访问地址,支持 HTTP 和 HTTPS 协议。 |
路径匹配模式 | 必填 | 支持后端路径匹配和全路径匹配两种方式。 后端路径匹配:直接使用配置的路径请求服务。 全路径匹配:使用去除请求路径的路径请求服务,如 API 路径配置为 /a/,请求路径为 /a/b,开启全路径匹配后,传输给服务的为 /b。 |
请求数据格式 | 必填 | APIGW会把客户端发送的请求按照不同的格式转发给公网/内网认证服务。 原始格式:按照请求原始的内容和格式转发给认证服务。 JSON:将原始请求中的 Header Query Body Method PATH 参数转换成新的格式作为 Body 转发给公网/内网认证服务。 |
超时时间 | 必填 | 请求后端超时时间。默认15s。 |
是否发送body | 必填 | 如果后端认证服务不需要通过 Body 做额外验证,可以不需要发送 Body,减少转发的性能损耗。 |
缓存时间 | 必填 | 相同的认证参数在缓存时间内只会请求一次后端。默认为0,表示不走缓存,每次都请求后端认证服务。 |
认证参数 | 选填 | 选填,但如果设置了参数则请求数据中为必填。当“缓存时间”不为0时,必须设置此参数。使用缓存时,此参数将作为搜索条件来查询认证结果。 |
对于部署在内网 VPC 的认证服务,创建自定义认证插件时需要填写的数据如下:
参数 | 是否必填 | 说明 |
选择 VPC | 必填 | 选择认证服务所属的 VPC。 |
请求方法 | 必填 | 请求自定义认证函数的方法,支持 GET、POST、PUT、DELETE、HEAD、ANY。 |
后端地址 | 必填 | 自定义认证服务访问地址,支持 HTTP 和 HTTPS 协议。 |
步骤3:绑定 API
1. 在列表中选中刚刚创建好的插件,单击操作列的绑定 API。
2. 在绑定 API 弹窗中选择服务和环境,并选择需要绑定插件的 API。
3. 单击确定,即可将插件绑定到 API,此时插件的配置已经对 API 生效。
插件及请求响应示例
请在下方示例区切换页签,查看插件数据 pluginData、请求示例、响应示例。
{"cache_time":10, // 认证结果缓存时间,单位秒,合法值:0 ~ 3600 秒"endpoint_timeout":15, // 后端超时时间,单位秒,合法值:0 ~ 60 秒"func_name":"test_name", // 自定义函数名称"func_namespace":"test_namespace", // 自定义函数命名空间"func_qualifier":"$LATEST", // 自定义函数版本"is_send_body":true, // 是否将请求Body发送到函数"header_auth_parameters":[ // Header位置的认证参数,插件根据参数值来缓存认证结果"Header1"],"query_auth_parameters":[ // Query位置的认证参数,插件根据参数值来缓存认证结果"Query1"],"user_id":1253970226, // appid"version":"2021-12-26 17:17:49"// 插件版本,格式:yyyy-MM-dd HH:mm:ss,编辑插件时,传入新值会使得插件下的缓存结果失效}
{ "requestContext": { // 请求的信息 "path": "\\/test", "httpMethod": "POST", "sourceIp": "113.108.77.64", "stage": "release", "serviceId": "service-i76yjnfu", "identity": {} }, "path": "\\/test", // 请求路径 "httpMethod": "POST", // 请求方法 "body": "{\\"data\\":1}", // 请求Body "queryStringParameters": { // 请求的Query "query": "1"}, "headerParameters": { // 请求Header "accept": "*\\/*", "host": "service-ixxxxx-xxxxx.gz.tencentapigw.cn", "header-auth": "apigw", "content-length": "10", "content-type": "application\\/x-www-form-urlencoded", "user-agent": "curl\\/8.1.2" }
{ "api-auth": true, // 认证结果 true:认证通过 false:认证失败 "api-succ-headers": {"key":"value"}, // 认证成功后需要添加到转发到后端的header "api-response": "auth fail" // 认证失败后返回自定义内容 }
注意事项
当用户开启缓存并配置了认证参数时,API 网关会进行参数校验。如果请求不传递该认证参数,API 网关将会报错“缺少 xxx 参数”。API 网关做参数校验和命中缓存时,都是大小写不敏感的。
每次将自定义插件绑定到一个网关 API 时,相当于为认证函数创建了一个该网关 API 的触发器。在 SCF 侧删除触发器,相当于把插件和 API 解绑。
自定义认证插件目前仅支持事件函数,不支持 Web 函数。
自定义认证插件可与 API 网关提供的认证方式共存,API 网关提供的认证方式优先级高于自定义认证,建议您将自定义认证插件绑定的 API 网关 API 设置为“免认证”。
自定义服务类型规则
示例代码:自定义认证服务
以下均以 Python2.7 为例,提供2种内网或公网的数据请求格式的示例。
from http.server import BaseHTTPRequestHandler, HTTPServer from urllib.parse import urlparse, parse_qs import json PORT_NUMBER = 8088 class MyHandler(BaseHTTPRequestHandler): def do_POST(self): response = {'api-auth': True} # Header认证 header_auth_key = self.headers.get("header-auth") if header_auth_key == "apigw": response['api-auth'] = False # Query认证 # parsed_url = urlparse(self.path) # query_params = parse_qs(parsed_url.query) # query_auth_key = query_params.get("query-auth", [None])[0] #if query_auth_key == "apigw": # response['api-auth'] = False self.send_response(200) self.send_header('Content-type', 'application/json') self.end_headers() self.wfile.write(json.dumps(response).encode('utf-8')) try: server = HTTPServer(('', PORT_NUMBER), MyHandler) print(f'Started HTTP server on port {PORT_NUMBER}') # Wait forever for incoming HTTP requests server.serve_forever() except KeyboardInterrupt: print('^C received, shutting down the web server') server.socket.close()
from http.server import BaseHTTPRequestHandler, HTTPServer import json PORT_NUMBER = 8088 class MyHandler(BaseHTTPRequestHandler): def do_POST(self): response = {'api-auth': True} content_length = int(self.headers.get('Content-Length', 0)) request_body = self.rfile.read(content_length).decode('utf-8') request_body = json.loads(request_body) # Header认证 header_parameters = request_body.get('headerParameters', {}) if header_parameters.get('header-auth') == "apigw": response['api-auth'] = False # Query认证 # query_parameters = request_body.get('queryStringParameters', {}) # if query_parameters.get('query-auth') == "apigw": # response['api-auth'] = False self.send_response(200) self.send_header('Content-type', 'application/json') self.end_headers() self.wfile.write(json.dumps(response).encode('utf-8')) try: server = HTTPServer(('', PORT_NUMBER), MyHandler) print(f'Started HTTP server on port {PORT_NUMBER}') # Wait forever for incoming HTTP requests server.serve_forever() except KeyboardInterrupt: print('^C received, shutting down the web server') server.socket.close()
常见错误信息
错误信息 | 含义 |
Custom authentication failed: authentication information, incorrect | 认证失败,默认返回内容。 |
Custom authentication failed: custom service request, request failed | 请求后端服务失败,需排查后端服务状态是否正常。 |
Custom authentication failed: custom service response, invalid content | 认证服务返回的数据格式错误或者认证服务返回的数据做了 Gzip 压缩,当前不支持对认证服务响应做 Gzip 数据解析。 |