仍登录 MVC 站点,但无法调用 Web API

Posted

技术标签:

【中文标题】仍登录 MVC 站点,但无法调用 Web API【英文标题】:Still logged in MVC site, but can't call web API 【发布时间】:2019-06-27 03:35:23 【问题描述】:

我有一个 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,
,

最后是网络 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);                    
            );

【问题讨论】:

【参考方案1】:

有两种认证方式,cookie和bearer。

cookie 让您保持登录状态,而不记名令牌则不能。因为不记名令牌设置为在某个时间过期,不允许您更改生命周期。

在访问令牌过期后访问资源 (api) 的唯一方法是让用户再次登录或使用 refresh token 请求新的访问令牌,而无需用户交互。

你已经配置好了:

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;

默认情况下,刷新令牌的使用是 configured 一次性使用。请注意,当存储新刷新令牌失败并且您应该丢失它时,请求新刷新令牌的唯一方法是强制用户重新登录。

另请注意,刷新令牌可能会过期。


退一步,当访问令牌过期或即将过期时,您需要使用它:

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.

【讨论】:

感谢您为我指明正确的方向。所以 somshow 我需要请求一个刷新令牌。我将进一步调查这里的最佳做法以及如何去做。 感谢您的帮助!非常感谢:) 我猜如果调用“var responseToken = await RenewTokensAsync();”返回null,然后我必须以编程方式注销用户? 是的,如果结果为空(不管为什么),那么您将无法访问该资源,因此您需要一个新的刷新令牌。唯一的办法是强制用户重新登录。 @RuardvanElburg tokenHandler.ReadJwtToken(accessToken); 在上下文中存储普通的 expires_at 值时看起来过度

以上是关于仍登录 MVC 站点,但无法调用 Web API的主要内容,如果未能解决你的问题,请参考以下文章

是否可以将自定义错误页面与 MVC 站点一起使用,但不能在 Web API 中使用?

在 Web API 中启用了 Asp.Net MVC CORS,但不再发送标头

ASP.NET MVC Web API 身份验证令牌安全问题

如何生成 Bearer 令牌以调用远程 Web API

如何从另一个应用程序调用我的基于角色的授权 Web API?

有没有办法让 Asp.net 零公共站点(Asp.net MVC)中的实时登录用户?