-
Notifications
You must be signed in to change notification settings - Fork 449
Expand file tree
/
Copy pathTokenProvider.cs
More file actions
206 lines (184 loc) · 7.2 KB
/
TokenProvider.cs
File metadata and controls
206 lines (184 loc) · 7.2 KB
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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using System;
using System.Threading.Tasks;
using WebApiClientCore.Extensions.OAuths.Exceptions;
namespace WebApiClientCore.Extensions.OAuths.TokenProviders
{
/// <summary>
/// 表示 token 提供者抽象类
/// </summary>
public abstract class TokenProvider : ITokenProvider
{
/// <summary>
/// 最近请求到的 token
/// </summary>
private TokenResult? token;
/// <summary>
/// 服务提供者
/// </summary>
private readonly IServiceProvider services;
/// <summary>
/// 异步锁
/// </summary>
private readonly AsyncRoot asyncRoot = new();
/// <summary>
/// 获取或设置名称
/// </summary>
public string Name { get; set; } = string.Empty;
/// <summary>
/// token 提供者抽象类
/// </summary>
/// <param name="services"></param>
public TokenProvider(IServiceProvider services)
{
this.services = services;
}
/// <summary>
/// 获取选项值
/// Options 名称为本类型的 Name 属性
/// </summary>
/// <typeparam name="TOptions"></typeparam>
/// <returns></returns>
public TOptions GetOptionsValue<TOptions>()
{
return this.services.GetRequiredService<IOptionsMonitor<TOptions>>().Get(this.Name);
}
/// <summary>
/// 强制清除 token 以支持下次获取到新的 token
/// </summary>
public void ClearToken()
{
using (this.asyncRoot.Lock())
{
this.token = null;
}
}
/// <summary>
/// 获取 token 信息
/// </summary>
/// <returns></returns>
public async Task<TokenResult> GetTokenAsync()
{
using (await this.asyncRoot.LockAsync().ConfigureAwait(false))
{
if (this.token == null)
{
using var scope = this.services.CreateScope();
this.token = await this.RequestTokenAsync(scope.ServiceProvider).ConfigureAwait(false);
}
else if (this.ShouldRefreshToken(this.token))
{
using var scope = this.services.CreateScope();
this.token = this.token.CanRefresh() == false
? await this.RequestTokenAsync(scope.ServiceProvider).ConfigureAwait(false)
: await this.RefreshTokenAsync(scope.ServiceProvider, this.token.Refresh_token ?? string.Empty).ConfigureAwait(false);
}
return this.token == null ? throw new TokenNullException() : this.token.EnsureSuccess();
}
}
/// <summary>
/// 判断是否应该刷新Token
/// 混合方案:优先从独立配置读取,其次从 HttpApiOptions.Properties 读取,最后使用默认行为
/// </summary>
/// <param name="token">token结果</param>
/// <returns></returns>
private bool ShouldRefreshToken(TokenResult token)
{
// 获取配置
var tokenOptions = this.GetTokenRefreshOptions();
if (!tokenOptions.UseTokenRefreshWindow)
{
// 如果禁用刷新窗口,使用原有行为
return token.IsExpired();
}
// 计算刷新窗口
var refreshWindow = this.CalculateRefreshWindow(token, tokenOptions);
return token.IsExpired(refreshWindow);
}
/// <summary>
/// 获取Token刷新配置
/// 优先级:独立配置(IOptionsMonitor<OAuthTokenOptions>) > HttpApiOptions.Properties > 默认配置
/// </summary>
/// <returns>Token刷新配置</returns>
private OAuthTokenOptions GetTokenRefreshOptions()
{
// 优先从独立配置读取 (方案2:独立配置类)
var oauthOptionsMonitor = this.services.GetService<IOptionsMonitor<OAuthTokenOptions>>();
if (oauthOptionsMonitor != null)
{
try
{
var options = oauthOptionsMonitor.Get(this.Name);
// 检查是否为默认配置,如果不是则使用
if (options != null)
{
return options;
}
}
catch
{
// 如果获取失败,继续尝试下一种方式
}
}
// 其次从 HttpApiOptions.Properties 读取 (方案1:Properties 字典)
var httpApiOptionsMonitor = this.services.GetService<IOptionsMonitor<HttpApiOptions>>();
if (httpApiOptionsMonitor != null)
{
try
{
var httpApiOptions = httpApiOptionsMonitor.Get(this.Name);
if (httpApiOptions != null)
{
return httpApiOptions.GetOAuthTokenOptions();
}
}
catch
{
// 如果获取失败,使用默认配置
}
}
// 最后使用默认配置
return new OAuthTokenOptions();
}
/// <summary>
/// 根据配置策略计算刷新窗口
/// </summary>
/// <param name="token">Token结果</param>
/// <param name="options">配置选项</param>
/// <returns>刷新窗口时间</returns>
private TimeSpan CalculateRefreshWindow(TokenResult token, OAuthTokenOptions options)
{
var fixedWindow = TimeSpan.FromSeconds(options.RefreshWindowSeconds);
var percentageWindow = TimeSpan.FromSeconds(token.Expires_in * options.RefreshWindowPercentage);
return options.RefreshWindowStrategy switch
{
RefreshWindowStrategy.FixedSeconds => fixedWindow,
RefreshWindowStrategy.Percentage => percentageWindow,
RefreshWindowStrategy.Auto => fixedWindow < percentageWindow ? fixedWindow : percentageWindow,
_ => fixedWindow < percentageWindow ? fixedWindow : percentageWindow // 默认使用Auto
};
}
/// <summary>
/// 请求获取 token
/// </summary>
/// <param name="serviceProvider">服务提供者</param>
/// <returns></returns>
protected abstract Task<TokenResult?> RequestTokenAsync(IServiceProvider serviceProvider);
/// <summary>
/// 刷新 token
/// </summary>
/// <param name="serviceProvider">服务提供者</param>
/// <param name="refresh_token">刷新 token</param>
/// <returns></returns>
protected abstract Task<TokenResult?> RefreshTokenAsync(IServiceProvider serviceProvider, string refresh_token);
/// <summary>
/// 转换为 string
/// </summary>
/// <returns></returns>
public override string ToString()
{
return this.Name;
}
}
}