之前看很多樂觀鎖都是做防止修改同一筆資料,但最近同事之前用 RateLimit 預防前端遇到連點新增兩筆資料,這次專案我也類似用這個方法,前端程式我非能控制,這邊後端用這個方法做個補強。
心智圖
mindmap
AspNetCoreRateLimit
官方內建方法
IpRateLimit
ClientRateLimit
Token based 驗證方法
IpRateLimit
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
// needed to load configuration from appsettings.json
// 一般專案都會有,可以省略
// services.AddOptions();
// needed to store rate limit counters and ip rules
// 需要載入 MemoryCache,實際看你用哪個 Cache
services.AddMemoryCache();
//load general configuration from appsettings.json
// 載入 IpRateLimit
services.Configure<IpRateLimitOptions>(Configuration.GetSection("IpRateLimiting"));
//load ip rules from appsettings.json
// 載入 Ip RateLimit 設定
services.Configure<IpRateLimitPolicies>(Configuration.GetSection("IpRateLimitPolicies"));
// inject counter and rules stores
// 載入 Memory Rate Limit
services.AddInMemoryRateLimiting();
//services.AddDistributedRateLimiting<AsyncKeyLockProcessingStrategy>();
//services.AddDistributedRateLimiting<RedisProcessingStrategy>();
//services.AddRedisRateLimiting();
// configuration (resolvers, counter key builders)
// 使用 IpRateLimit
services.AddSingleton<IRateLimitConfiguration, RateLimitConfiguration>();
|
- UseIpRateLimiting
1
2
|
app.UseAuthorization();
app.UseIpRateLimiting();
|
- 設定檔案
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
"IpRateLimiting": {
"EnableEndpointRateLimiting": false,
"StackBlockedRequests": false,
"RealIpHeader": "X-Real-IP",
"ClientIdHeader": "X-ClientId",
"HttpStatusCode": 429,
"IpWhitelist": [ "127.0.0.1", "::1/10", "192.168.0.0/24" ],
"EndpointWhitelist": [ "get:/api/license", "*:/api/status" ],
"ClientWhitelist": [ "dev-id-1", "dev-id-2" ],
"GeneralRules": [
{
"Endpoint": "*",
"Period": "1s",
"Limit": 2
},
{
"Endpoint": "*",
"Period": "15m",
"Limit": 100
},
{
"Endpoint": "*",
"Period": "12h",
"Limit": 1000
},
{
"Endpoint": "*",
"Period": "7d",
"Limit": 10000
}
]
}
|
設定說明
EnableEndpointRateLimiting
不同設定對規則引響不太一樣,假如設為true
,所有規則 Rate Limit Count 是拆開的。false
所有規則會加總算。
If EnableEndpointRateLimiting is set to false then the limits will apply globally and only the rules that have as endpoint * will apply. For example, if you set a limit of 5 calls per second, any HTTP call to any endpoint will count towards that limit.
If EnableEndpointRateLimiting is set to true, then the limits will apply for each endpoint as in {HTTP_Verb}{PATH}. For example if you set a limit of 5 calls per second for *:/api/values a client can call GET /api/values 5 times per second but also 5 times PUT /api/values.
StackBlockedRequests
當API RateLimit出現,訪問中API要不要計算在內,通常是設定false
。
RealIpHeader
通常反向代理後會有 IP 參考 Header,這邊可以針對情況調整,沒有 Header 他會抓 IP資料。
也有針對 IP 設定也可以設定,不過我是沒設定這段。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
"IpRateLimitPolicies": {
"IpRules": [
{
"Ip": "84.247.85.224",
"Rules": [
{
"Endpoint": "*",
"Period": "1s",
"Limit": 10
},
{
"Endpoint": "*",
"Period": "15m",
"Limit": 200
}
]
},
{
"Ip": "192.168.3.22/25",
"Rules": [
{
"Endpoint": "*",
"Period": "1s",
"Limit": 5
},
{
"Endpoint": "*",
"Period": "15m",
"Limit": 150
},
{
"Endpoint": "*",
"Period": "12h",
"Limit": 500
}
]
}
]
}
|
特定 API 設定 RateLimit
我專案只需要指定新增 Post API 不要一直呼叫,可以這樣設定。付款流程理論上8秒內只會觸發一次,這邊特別設定這樣。可以阻擋前端連點或不明原因呼叫。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
"IpRateLimiting": {
"EnableEndpointRateLimiting": true,
"StackBlockedRequests": false,
"RealIpHeader": "X-Real-IP",
"ClientIdHeader": "X-ClientId",
"HttpStatusCode": 429,
"IpWhitelist": [],
"ClientWhitelist": [],
"GeneralRules": [
{
"Endpoint": "post:/api/Pay",
"Period": "8s",
"Limit": 1
},
{
"Endpoint": "post:/api/newXXX",
"Period": "1s",
"Limit": 1
}
]
}
|
ClientLimit
當初看到 ClientId
本來以為跟 OAuth 有關係,但其實沒有關係,他就是抓個Header
上面的 X-Client
。如果所有 Request 沒設定到就會把 X-Client
當作一樣 key。所以使用這個情境應該比較低,我也找不到 Reverse Proxy 設定 X-Client
,不太了解這個使用時機。
使用方法跟 IpRateLimit 差不多,這邊就不完整顯示,請參考ClientRateLimitMiddleware · stefanprodan/AspNetCoreRateLimit Wiki。
使用者驗證
其實 ClinetLimit
和 IpRateLimit
實務上需求比較少,假如在同一個網域很多Web 做 Nat 情況下,使用者 IP 匯市一樣的,IpRateLimit
會有問題。所以假如能做到身分驗證當 Key 就能解決這個問題。原生沒有這個方法,這邊找到幾個方式。
這邊整理一下找到方法。
使用 "ClientIdHeader": "Authorization"
,但這個缺點很明顯。使用這個 AccessToken 需要紀錄很長;還有第二個缺點,使用者換 Token 就不會阻擋。
參考:use jwt in rate limiting · Issue #37 · stefanprodan/AspNetCoreRateLimit
方法2: 設定 Request X-ClientId 為 User Identity
1.設定好相關Token-based 身分驗證理論上你的專案都有設定好,這邊就不說明。
- 在走
ClientRateLimmit
前設定塞 X-ClientId
。這邊可以額外拆 class,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
app.UseAuthorization();
app.Use(async(context, next)=>
{
try
{
context.Request.Headers.Add("X-ClientId", context.User.Identity!.Name);
}
catch (Exception)
{
context.Request.Headers.Add("X-ClientId", "not authenticated");
}
await next();
});
app.UseHttpLogging();
app.UseClientRateLimiting();
|
RateLimit:Rate limit after authentication · Issue #82 · stefanprodan/AspNetCoreRateLimit
方法3: RateLimit進階設定
這個寫法相對方法2比較正規寫法,自己寫相關規則去判斷再注入DI裡面去。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
public class ElmahIoRateLimitConfiguration : RateLimitConfiguration
{
public ElmahIoRateLimitConfiguration(
IOptions<IpRateLimitOptions> ipOptions,
IOptions<ClientRateLimitOptions> clientOptions)
: base(ipOptions, clientOptions)
{
}
public override void RegisterResolvers()
{
base.RegisterResolvers();
ClientResolvers.Add(new ClientQueryStringResolveContributor());
}
}
public class ClientQueryStringResolveContributor : IClientResolveContributor
{
public Task<string> ResolveClientAsync(HttpContext httpContext)
{
var queryDictionary =
Microsoft.AspNetCore.WebUtilities.QueryHelpers.ParseQuery(
httpContext.Request.QueryString.ToString());
if (queryDictionary.ContainsKey("api_key")
&& !string.IsNullOrWhiteSpace(queryDictionary["api_key"]))
{
return Task.FromResult(queryDictionary["api_key"].ToString());
}
return Task.FromResult(Guid.NewGuid().ToString());
}
}
|
參考: Built-in rate limiting in ASP.NET Core vs AspNetCoreRateLimit
我這個範例不是 Token-based ,這邊有另外一種寫法。
use jwt in rate limiting · Issue #37 · stefanprodan/AspNetCoreRateLimit
不過使用沒有很順利,這邊文章(How to implement Rate Limiting in an ASP.NET Core Web API)照網路設定順利可以跑。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
// configuration (resolvers, counter key builders)
builder.Services.AddSingleton<IRateLimitConfiguration, CustomRateLimitConfiguration>();
// 省略...
internal class CustomRateLimitConfiguration : RateLimitConfiguration
{
public CustomRateLimitConfiguration(
IOptions<IpRateLimitOptions> ipOptions,
IOptions<ClientRateLimitOptions> clientOptions) : base(ipOptions, clientOptions)
{
}
public override void RegisterResolvers()
{
ClientResolvers.Add(new ClientIdResolverContributor());
}
}
internal class ClientIdResolverContributor : IClientResolveContributor
{
public Task<string> ResolveClientAsync(HttpContext context)
{
return Task.FromResult<string>(context.User.Identity.Name);
}
}
|
彩蛋