使用Azure API Management, 想对一些固定的IP地址进行访问次数的限制,如被限制的IP地址一分钟可以访问10次,而不被限制的IP地址则可以无限访问?
最近ChatGPT爆火,所以也把这个问题让ChatGPT来解答,然后人工验证它的回答正确与否?
根据对APIM Policy的文档参考, choose 和 rate-limit 策略组合理论上的确可以实现要求, 接下来就让我们实际验证:
1)在API的Inbound 策略中添加 choose策略
(策略具体内容,见文末)
2) 测试验证,连续对该API访问10次以上,得到429 Too Many Requests错误
3)以上证明,ChatGPT针对这个问题的解答是正确的!
在参考ChatGPT给出的 choose + rate limit 组合后,我们也发现另一个选项。使用 rate-limit-by-key 策略实现对特定IP的速率限制。
429 Too Many Requests
响应状态代码。在官方文档中给出的示例中,是针对所有的IP(context.Request.IpAddress)都进行了10次/60秒请求的限制,而本示例中则特指“某些固定IP”限制。那么如何来完成这个需求呢?
答案 就在“rate-limit-by-key 策略”的说明中,”可以添加可选增量条件,指定在决定是否到达限制值时应该进行计数的请求”, 所以,只要可选增量条件(increment-condition) 的值根据输入的IP地址动态赋值True/False, 就能完美匹配以上要求。
理论推断,只需要实现如下逻辑,即可以实现终极需求“想对一些固定的IP地址进行访问次数的限制,如被限制的IP地址一分钟可以访问10次,而不被限制的IP地址则可以无限访问?”
只需两步:
1)通过设置一个变量(set-variable) 值,用C#代码来计算变量值,在赋值语句中,预先定义一个IP限制列表,通过 contains 检查当前请求IP是否在列表中,返回True or False 。True表示当前请求的IP需要速率限制, 否则,不需要。
2) 然后,在rate-limit-by-key 的 increment-condition条件中使用上一步参数值,进行判断是否计入限制
1)在API的 Inbound 策略中添加 rate-limit-by-key策略
(策略具体内容,见文末)
2)验证在30秒,访问5次以上后,同样得到429 Too Many Requests错误
3) 当在请求Headers中添加Ocp-Apim-Trace: true 和 Ocp-Apim-Subscription-Key: {订阅Key}后,可以查看请求在APIM中执行的日志跟踪。可以查看rate-limit-by-key 策略的执行情况.
想实现固定IP地址访问次数的限制,至少有如下两种解决方案。
<!-- IMPORTANT: - Policy elements can appear only within the <inbound>, <outbound>, <backend> section elements. - To apply a policy to the incoming request (before it is forwarded to the backend service), place a corresponding policy element within the <inbound> section element. - To apply a policy to the outgoing response (before it is sent back to the caller), place a corresponding policy element within the <outbound> section element. - To add a policy, place the cursor at the desired insertion point and select a policy from the sidebar. - To remove a policy, delete the corresponding policy statement from the policy document. - Position the <base> element within a section element to inherit all policies from the corresponding section element in the enclosing scope. - Remove the <base> element to prevent inheriting policies from the corresponding section element in the enclosing scope. - Policies are applied in the order of their appearance, from the top down. - Comments within policy elements are not supported and may disappear. Place your comments between policy elements or at a higher level scope. --> <policies> <inbound> <base /> <set-variable name="IsCountIpLimit" value="@{ string ipAddress =context.Request.IpAddress; List<string> cidrList = new List<string>(){ "167.xxx. xxx.135", "167.xxx. xxx.136", "167.xxx. xxx.137" }; return cidrList.Contains(ipAddress); }" /> <choose> <when condition="@((bool)context.Variables["IsCountIpLimit"])"> <rate-limit calls="10" renewal-period="60" /> </when> </choose> </inbound> <backend> <base /> </backend> <outbound> <base /> </outbound> <on-error> <base /> </on-error> </policies>
<!-- IMPORTANT: - Policy elements can appear only within the <inbound>, <outbound>, <backend> section elements. - To apply a policy to the incoming request (before it is forwarded to the backend service), place a corresponding policy element within the <inbound> section element. - To apply a policy to the outgoing response (before it is sent back to the caller), place a corresponding policy element within the <outbound> section element. - To add a policy, place the cursor at the desired insertion point and select a policy from the sidebar. - To remove a policy, delete the corresponding policy statement from the policy document. - Position the <base> element within a section element to inherit all policies from the corresponding section element in the enclosing scope. - Remove the <base> element to prevent inheriting policies from the corresponding section element in the enclosing scope. - Policies are applied in the order of their appearance, from the top down. - Comments within policy elements are not supported and may disappear. Place your comments between policy elements or at a higher level scope. --> <policies> <inbound> <base /> <set-variable name="IsCountIpLimit" value="@{ string ipAddress =context.Request.IpAddress; List<string> limitIPs = new List<string>(){ "167.xxx. xxx.135", "167.xxx. xxx.136", "167.xxx. xxx.137" }; return limitIPs.Contains(ipAddress); }" /> <rate-limit-by-key calls="5" renewal-period="30" counter-key="@(context.Request.IpAddress)" increment-condition="@(context.Response.StatusCode >= 200 && context.Response.StatusCode < 300 && (bool)context.Variables["IsCountIpLimit"])" /> </inbound> <backend> <base /> </backend> <outbound> <base /> </outbound> <on-error> <base /> </on-error> </policies>
choose策略:https://docs.azure.cn/zh-cn/api-management/api-management-advanced-policies#choose
rate-limit策略:https://docs.azure.cn/zh-cn/api-management/api-management-access-restriction-policies#LimitCallRate ,
rate-limit-by-key 策略:https://docs.azure.cn/zh-cn/api-management/api-management-access-restriction-policies#LimitCallRateByKey