c# - 仍登录 MVC 站点,但无法调用 Web API
问题描述
我有一个 ASP.NET MVC 站点、IdentityServer4 主机和一个 Web API。
当我使用外部提供商 (Facebook) 登录 MVC 站点时,我已正常登录。从 MVC 站点,我也可以正确使用 Web API。
但是,第二天,我仍然登录到 MVC 站点,但是当我尝试访问 Web API 时,我得到一个“未授权异常”。
因此,尽管我仍然在 MVC 站点中登录,但我不再通过身份验证来从 MVC 站点中调用 Web API。
我想知道如何处理这种情况,以及应如何配置 IdentityServer4。
- 为什么一天后我仍然登录 MVC 站点?如何配置?
- 如果我仍然登录 MVC 站点,为什么我仍然不能调用 Web API?
- 我可以同步到期时间吗?或者我应该如何处理?
MVC 应用程序的配置如下:
services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookies")
.AddOpenIdConnect("oidc", options =>
{
options.SignInScheme = "Cookies";
options.Authority = mgpIdSvrSettings.Authority;
options.RequireHttpsMetadata = false;
options.ClientId = mgpIdSvrSettings.ClientId;
options.ClientSecret = mgpIdSvrSettings.ClientSecret; // Should match the secret at IdentityServer
options.ResponseType = "code id_token"; // Use hybrid flow
options.SaveTokens = true;
options.GetClaimsFromUserInfoEndpoint = true;
options.Scope.Add("mgpApi");
options.Scope.Add("offline_access");
});
所以它使用混合流。
在 IdentityServer 中,MVC 客户端的配置如下:
new Client
{
EnableLocalLogin = false,
ClientId = "mgpPortal",
ClientName = "MGP Portal Site",
AllowedGrantTypes = GrantTypes.Hybrid,
// where to redirect to after login
RedirectUris = mgpPortalSite.RedirectUris,
// where to redirect to after logout
PostLogoutRedirectUris = mgpPortalSite.PostLogoutRedirectUris,
// secret for authentication
ClientSecrets = mgpPortalSite.ClientSecrets.Select(cs => new Secret(cs.Sha256())).ToList(),
AllowedScopes = new List<string>
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
"mgpApi"
},
AllowOfflineAccess = true,
RequireConsent = false,
},
最后是 Web API:
services.AddAuthentication("Bearer")
.AddIdentityServerAuthentication(options =>
{
options.Authority = mgpIdSvrSettings.Authority;
options.RequireHttpsMetadata = false;
options.ApiName = mgpIdSvrSettings.ApiName;
options.EnableCaching = true;
options.CacheDuration = TimeSpan.FromMinutes(10);
});
解决方案
有两种类型的身份验证,cookie 和承载。
在 cookie 让您登录的地方,不记名令牌不能。因为不记名令牌被设置为在某个时候过期,而不允许您更改生命周期。
访问令牌过期后访问资源 (api) 的唯一方法是让用户再次登录或使用刷新令牌请求新的访问令牌,而无需用户交互。
您已经配置了它:
options.Scope.Add("offline_access");
在每次登录时,请求将至少包含一个刷新令牌。将其存放在安全的地方,并在需要时使用。默认情况下,它设置为仅使用一次。
您可以使用类似此代码的代码来更新令牌(因为您实际上并没有刷新它,而是替换它)。您需要包含“IdentityModel”NuGet 包,如 IdentityServer 的示例中所示。
private async Task<TokenResponse> RenewTokensAsync()
{
// Initialize the token endpoint:
var client = _httpClientFactory.CreateClient();
var disco = await client.GetDiscoveryDocumentAsync("http://localhost:5000");
if (disco.IsError) throw new Exception(disco.Error);
// Read the stored refresh token:
var rt = await HttpContext.GetTokenAsync("refresh_token");
var tokenClient = _httpClientFactory.CreateClient();
// Request a new access token:
var tokenResult = await tokenClient.RequestRefreshTokenAsync(new RefreshTokenRequest
{
Address = disco.TokenEndpoint,
ClientId = "mvc",
ClientSecret = "secret",
RefreshToken = rt
});
if (!tokenResult.IsError)
{
var old_id_token = await HttpContext.GetTokenAsync("id_token");
var new_access_token = tokenResult.AccessToken;
var new_refresh_token = tokenResult.RefreshToken;
var expiresAt = DateTime.UtcNow + TimeSpan.FromSeconds(tokenResult.ExpiresIn);
// Save the information in the cookie
var info = await HttpContext.AuthenticateAsync("Cookies");
info.Properties.UpdateTokenValue("refresh_token", new_refresh_token);
info.Properties.UpdateTokenValue("access_token", new_access_token);
info.Properties.UpdateTokenValue("expires_at", expiresAt.ToString("o", CultureInfo.InvariantCulture));
await HttpContext.SignInAsync("Cookies", info.Principal, info.Properties);
return tokenResult;
}
return null;
}
默认情况下,刷新令牌使用配置为一次性使用。请注意,当存储新刷新令牌失败并且您应该丢失它时,请求新刷新令牌的唯一方法是强制用户再次登录。
另请注意,刷新令牌可能会过期。
退后一步,当访问令牌过期或即将过期时,您需要使用它:
var accessToken = await HttpContext.GetTokenAsync("access_token");
var tokenHandler = new JwtSecurityTokenHandler();
var jwtSecurityToken = tokenHandler.ReadJwtToken(accessToken);
// Depending on the lifetime of the access token.
// This is just an example. An access token may be valid
// for less than one minute.
if (jwtSecurityToken.ValidTo < DateTime.UtcNow.AddMinutes(5))
{
var responseToken = await RenewTokensAsync();
if (responseToken == null)
{
throw new Exception("Error");
}
accessToken = responseToken.AccessToken;
}
// Proceed, accessToken contains a valid token.
推荐阅读
- r - 根据来自另一个 rhandon 表单元格的值更新 rhandson 表单元格
- java - 如何在 maven pom xml 中将共享文件夹设置为 outputDirectory
- flutter - Renderflex 对象在布局期间被赋予无限大小 - 颤动错误
- android - 每当创建 Exception 实例时,suspendCoroutine 都会使应用程序崩溃
- html - 我怎样才能让卡片在彼此的右侧和左侧堆叠?
- python-3.x - 仅在 Python 中使用 if-else 将整数(从 0 到 999)转换为英文单词时遇到问题
- node.js - 猫鼬没有将我的新文档保存在数组中
- swift - 从所选国家代码返回模板字符串
- azure - 使用 get 和 body 参数调用嵌套的 Azure 逻辑应用
- ansible - 处理 Ansible 的 import_tasks/import_role 和“通知”的最佳方法是什么?