静默刷新在 OPTIONS 预检上进行身份验证,但不在 GET 到 UserInfo 端点上进行身份验证

Posted

技术标签:

【中文标题】静默刷新在 OPTIONS 预检上进行身份验证,但不在 GET 到 UserInfo 端点上进行身份验证【英文标题】:Silent refresh authenticates on OPTIONS preflight but not on GET to UserInfo endpoint 【发布时间】:2019-07-13 12:11:16 【问题描述】:

环境:

IdentityServer4 支持隐式流的实例 Angular 7 客户端应用程序使用 oidc-client-js ASP.NET Framework Web API 资源使用IdentityServer3 Katana Access Token Validation Middleware

基本令牌发行和验证在我们的环境中运行良好。我现在正在尝试启用静默刷新技术(如documented here)。启用automaticSilentRenew 和简短的AccessTokenLifetime 后,我可以看到无提示请求在我的浏览器控制台中触发,正如我所期望的那样。

我可以看到对 IS4 的 UserInfo 端点的两个后续调用(见下面的屏幕截图)。第一个是 CORS 预检 OPTIONS 请求。在IProfileService.IsActiveAsync() 的自定义实现中的断点处,我可以看到此请求成功通过了身份验证(通过检查httpContext)。

public class ProfileService : IProfileService

    private readonly HttpContext _httpContext;

    public ProfileService(IHttpContextAccessor httpContextAccessor)
    
        _httpContext = httpContextAccessor.HttpContext;
    

    ...

    public async Task IsActiveAsync(IsActiveContext context)
    
        var temp = _httpContext.User; // breakpoint here
        // call external API with _httpContext.User info to get isActive
    

但是,对 UserInfo 端点的第二个请求 (GET) 未进行身份验证。我在IProfileService.IsActiveAsync() 中的断点显示没有经过身份验证的用户,因此我用于验证用户是否处于活动状态(调用另一个 API)的例程返回 false,它被转换为 401。我可以在失败的GET 请求中看到此标头@ 987654344@.

我已尝试指定小于每个 this 的 AccessTokenLifetimeIdentityTokenLifetime,但没有成功。

这是两个请求的日志:

Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request starting HTTP/1.1 OPTIONS http://localhost:5000/connect/userinfo  
Microsoft.AspNetCore.Cors.Infrastructure.CorsService:Information: CORS policy execution successful.
Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request finished in 8.5635ms 204 
Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request starting HTTP/1.1 GET http://localhost:5000/connect/userinfo  
Microsoft.AspNetCore.Cors.Infrastructure.CorsService:Information: CORS policy execution successful.
Microsoft.AspNetCore.Cors.Infrastructure.CorsService:Information: CORS policy execution successful.
IdentityServer4.Hosting.IdentityServerMiddleware:Information: Invoking IdentityServer endpoint: IdentityServer4.Endpoints.UserInfoEndpoint for /connect/userinfo
IdentityServer4.Validation.TokenValidator:Error: User marked as not active: f84db3aa-57b8-48e4-9b59-6deee3d288ad
Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request finished in 94.7189ms 401

问题:

如何在静默刷新期间向 UserInfo 端点发送 GET 请求以验证到 HttpContext

更新:

添加两个请求的所有标头和生成的浏览器 cookie 的屏幕截图,以调查 @Anders 的答案。

【问题讨论】:

您使用什么库来发出请求?您确定正在发送任何与 cookie 相关的选项吗?选项请求通常由浏览器发出,没有来自 API 调用的任何帮助(或影响),因此即使选项请求中有标头并不意味着后续请求中会有标头 我们使用oidc-client-js 发送请求。对于你的第二个问题,我真的不确定,但会调查。谢谢。 【参考方案1】:

好吧,我想我知道发生了什么。您发出静默刷新令牌请求(从我可以从重定向到 oidc-silent-refresh.html 中看到的成功)并调用第一个 ProfileService 的“IsActiveAsync”(因为它需要通过配置文件服务生成令牌静默刷新)。您可以看到“HttpContext.User”,因为打开了一个 iframe,允许发送所有必要的 cookie。

用户信息预检请求甚至没有进入 ProfileService,正如您从日志中看到的那样,仅显示了一次“调用 IdentityServer 端点:IdentityServer4.Endpoints.UserInfoEndpoint for /connect/userinfo”。此外,UserInfoEndpoint 会阻止任何选项请求通过(您可以在“ProcessAsync”方法here 的开头验证它是否为真)。

现在,当您发出实际的用户信息请求时,您会尝试从 HttpContext(通常使用 cookie 中的信息填充)获取信息,但由于请求中没有发送 cookie,因此无法访问。请求是在 'getJson' 方法(来自 oidc-client-js 库)中发出的here,您可以看到请求中没有发送任何 cookie(请求中的 'withCredentials' 属性将设置为 true如果是的话)。

那么我们如何获得有关用户的必要信息呢?要获取此信息,我们可以参考传递给 ProfileService 的“IsActiveAsync”的“context”,其中包含由访问令牌声明填充的主体(此过程可以在“ValidateAccessTokenAsync”方法here 中看到)。

【讨论】:

就是这样。谢谢!【参考方案2】:

/connect/userinfo 的请求由 IdentityServer 域/路径中的会话身份验证 cookie 进行身份验证。

我的猜测是 cookie 正确包含在 OPTIONS 请求中,但未包含在后续 GET 请求中。您可以通过查看请求在浏览器开发工具中验证这一点。

如果我的猜测是正确的,原因可能是相同的站点属性。默认情况下,ASP.NET Core(IdentityServer4 使用)中的所有身份验证 cookie 都具有相同的站点属性,以防止跨站点请求伪造攻击。

根据information I can find,AJAX Get 请求中不允许使用带有 samesite=lax 的 cookie。但是,如果 OPTIONS 请求中允许,我找不到任何东西。您可以验证(使用浏览器开发工具)在对/connect/authorize 的第一个请求的响应中,cookie 标头中是否存在相同站点设置。

设置本身位于AddCookie() 调用中cookie 选项的Cookie 部分。 MS 文档说它默认为lax

另一方面,Idsrv4 存储库中有一个 GitHub thread,表示他们已将 Idsrv4 会话 cookie 的默认设置更改为“无”。

我显然在这里猜测了一下,但验证我的假设应该相当简单,如上所述。

【讨论】:

太棒了。仍在剖析您的建议。刚刚将两个请求的屏幕截图添加到问题的末尾。 嗯,使用 Fiddler 我看到“这个请求没有发送任何 cookie 数据。”对于对 UserInfo 端点的两个请求。我在idsrv.session cookie 中也没有看到SameSite 的价值(请参阅问题中cookie 的最后一个屏幕截图)。还有其他想法吗?谢谢。 我看到没有相同的站点值。但是cookie应该作为标头包含在两个请求中......所以我在这里迷路了。

以上是关于静默刷新在 OPTIONS 预检上进行身份验证,但不在 GET 到 UserInfo 端点上进行身份验证的主要内容,如果未能解决你的问题,请参考以下文章

为啥经过身份验证的 CORS 请求的预检 OPTIONS 请求在 Chrome 中有效,但在 Firefox 中无效?

将 Spring Security(双向 SSL)配置为仅对 CORS 预检 OPTIONS 请求进行身份验证

ASP.NET Core:带有 OPTIONS 异常的 Windows 身份验证(CORS 预检)

使用 CORS 避免预检 OPTIONS 请求

Safari不会在Apache上的Vue.js应用程序中加载预检CORS身份验证请求(XHR)

OPTIONS 请求身份验证