如何在MVC Web应用中调用授权Web API并动态获取存储Token
解决方法
实现逻辑说明
你当前的Web API已经配置了/token作为令牌签发端点,采用资源所有者密码授权模式,所以MVC应用只需在请求业务接口前,先向该端点发起请求获取动态Token即可,还可选择缓存Token减少重复请求开销。
前置准备
首先在MVC项目中定义Token响应接收模型,可放在Models文件夹下:
public class TokenResponse { [JsonProperty("access_token")] public string AccessToken { get; set; } [JsonProperty("expires_in")] public int ExpiresIn { get; set; } [JsonProperty("token_type")] public string TokenType { get; set; } [JsonProperty("error")] public string Error { get; set; } [JsonProperty("error_description")] public string ErrorDescription { get; set; } }
另外先确认Web API的Startup.cs中已添加OAuth中间件启用代码(你贴出的代码只定义了配置,漏了启用逻辑的话功能不生效):
// 追加到OAuth配置定义之后 app.UseOAuthAuthorizationServer(options); app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
修改MVC控制器代码
替换你写死Token的逻辑为动态获取逻辑:
if (opc.username == "test" && opc.password == "test") { // ========== 动态获取Token逻辑开始 ========== string token = string.Empty; // 优先从缓存中取未过期的Token if (Session["AccessToken"] != null && (DateTime)Session["TokenExpireTime"] > DateTime.Now) { token = Session["AccessToken"].ToString(); } else { using (var tokenClient = new HttpClient()) { tokenClient.BaseAddress = new Uri("https://localhost:44304/"); // 构造密码授权模式的请求参数 var tokenRequestParams = new Dictionary<string, string> { {"grant_type", "password"}, {"username", opc.username}, {"password", opc.password} }; var requestContent = new FormUrlEncodedContent(tokenRequestParams); // 请求Token端点 var tokenResponse = await tokenClient.PostAsync("token", requestContent); if (tokenResponse.IsSuccessStatusCode) { var tokenResult = await tokenResponse.Content.ReadAsAsync<TokenResponse>(); token = tokenResult.AccessToken; // 缓存Token,提前1分钟过期避免边界问题 Session["AccessToken"] = token; Session["TokenExpireTime"] = DateTime.Now.AddSeconds(tokenResult.ExpiresIn - 60); } else { var errorResult = await tokenResponse.Content.ReadAsAsync<TokenResponse>(); ViewBag.Error = $"获取Token失败:{errorResult.ErrorDescription}"; return View("Error"); } } } // ========== 动态获取Token逻辑结束 ========== IEnumerable<OperatorClass> OperatorObject = null; HttpClient client = new HttpClient(); client.BaseAddress = new Uri("https://localhost:44304/api/"); client.DefaultRequestHeaders.Add("Authorization", $"Bearer {token}"); var resultDisplay = await client.GetAsync("data"); if (resultDisplay.IsSuccessStatusCode) { // 原代码用Wait()会同步阻塞,直接用await即可 var readTable = await resultDisplay.Content.ReadAsAsync<IList<OperatorClass>>(); OperatorObject = readTable; return View(OperatorObject); } else { OperatorObject = Enumerable.Empty<OperatorClass>(); ModelState.AddModelError(String.Empty, "No records found"); ViewBag.Error = "Token错误,可能已过期或无效,请检查Token服务"; return View("Error"); } } else { ViewBag.Error = "用户名或密码错误!"; return View("Login"); }
注意事项
- 生产环境不要开启
AllowInsecureHttp = true,必须使用HTTPS传输Token避免被窃取 - 单实例部署可以用
IMemoryCache替代Session存储Token,分布式部署可存入分布式缓存 - 生产环境不要每次请求都new HttpClient,建议通过
IHttpClientFactory注入复用实例,避免套接字耗尽问题 - 不要硬编码接口地址、账号密码等配置,建议统一放在配置文件中读取
内容的提问来源于stack exchange,提问作者user14979715
相关产品推荐
相关产品推荐

