静默刷新在 OPTIONS 预检上进行身份验证,但不在 GET 到 UserInfo 端点上进行身份验证
Silent refresh authenticates on OPTIONS preflight but not on GET to UserInfo endpoint
环境:
- 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)returns false 转换为 401。我可以看到此 header 失败 GET
请求 WWW-Authenticate: error="invalid_token"
.
我尝试指定一个小于 this 的 AccessTokenLifetime
的 IdentityTokenLifetime
但没有成功。
这是两个请求的日志:
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
?
更新:
添加两个请求的所有 header 的屏幕截图和生成的浏览器 cookie 以调查@Anders 的答案。
对 /connect/userinfo
的请求由 IdentityServer domain/path 中的 session 身份验证 cookie 进行身份验证。
我的猜测是 cookie 正确包含在 OPTIONS 请求中,但没有包含在后续的 GET 请求中。您可以通过查看请求在浏览器开发工具中验证这一点。
如果我的猜测是正确的,原因可能是 samesite 属性。 ASP.NET Core(IdentityServer4 使用)中的所有身份验证 cookie 默认情况下都具有 samesite 属性,以防止跨站点请求伪造攻击。
根据 information I can find AJAX Get 请求不允许使用 samesite=lax 的 cookie。但是,如果在 OPTIONS 请求中允许,我找不到任何东西。您可以验证(使用浏览器开发工具)在第一个请求 /connect/authorize
.
的响应中,cookie header 中是否存在 samesite 设置
设置本身位于 AddCookie()
调用中 cookie 选项的 Cookie
部分。 MS 文档说它默认为 lax
.
另一方面,Idsrv4 存储库中有一个 GitHub thread,表示他们已将 Idsrv4 session cookie 的默认值更改为 "none"。
我显然在这里有点猜测,但验证我上面概述的假设应该相当简单。
好的,我想我知道发生了什么。您发出静默刷新令牌请求(从我从重定向到 oidc-silent-refresh.html 中看到的成功)并且调用第一个 ProfileService 的 'IsActiveAsync'(因为它需要通过配置文件服务为静默刷新生成令牌)。您能够看到 'HttpContext.User' 因为打开了一个 iframe,它允许发送所有必要的 cookie。
用户信息预检请求甚至没有到达配置文件服务,正如您从日志中看到的那样,只显示了一次 'Invoking IdentityServer endpoint: IdentityServer4.Endpoints.UserInfoEndpoint for /connect/userinfo'。此外,UserInfoEndpoint 会阻止任何选项请求通过(您可以在 'ProcessAsync' 方法 here 的开头验证它是否为真)。
现在,当您发出实际的用户信息请求时,您正试图从 HttpContext 获取信息(通常使用来自 cookie 的信息填充),但它是不可访问的,因为请求中没有发送 cookie。在'getJson'方法(来自oidc-client-js库)中发出请求here,可以看到请求中没有发送cookie('withCredentials' 属性 将根据请求设置为真)。
那么我们如何获取用户的必要信息呢?要获取此信息,我们可以参考 'context' passed into the ProfileService's 'IsActiveAsync' that contains a principal populated by the access token claims (this process can be seen in the 'ValidateAccessTokenAsync' method here).
环境:
- 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)returns false 转换为 401。我可以看到此 header 失败 GET
请求 WWW-Authenticate: error="invalid_token"
.
我尝试指定一个小于 this 的 AccessTokenLifetime
的 IdentityTokenLifetime
但没有成功。
这是两个请求的日志:
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
?
更新:
添加两个请求的所有 header 的屏幕截图和生成的浏览器 cookie 以调查@Anders 的答案。
对 /connect/userinfo
的请求由 IdentityServer domain/path 中的 session 身份验证 cookie 进行身份验证。
我的猜测是 cookie 正确包含在 OPTIONS 请求中,但没有包含在后续的 GET 请求中。您可以通过查看请求在浏览器开发工具中验证这一点。
如果我的猜测是正确的,原因可能是 samesite 属性。 ASP.NET Core(IdentityServer4 使用)中的所有身份验证 cookie 默认情况下都具有 samesite 属性,以防止跨站点请求伪造攻击。
根据 information I can find AJAX Get 请求不允许使用 samesite=lax 的 cookie。但是,如果在 OPTIONS 请求中允许,我找不到任何东西。您可以验证(使用浏览器开发工具)在第一个请求 /connect/authorize
.
设置本身位于 AddCookie()
调用中 cookie 选项的 Cookie
部分。 MS 文档说它默认为 lax
.
另一方面,Idsrv4 存储库中有一个 GitHub thread,表示他们已将 Idsrv4 session cookie 的默认值更改为 "none"。
我显然在这里有点猜测,但验证我上面概述的假设应该相当简单。
好的,我想我知道发生了什么。您发出静默刷新令牌请求(从我从重定向到 oidc-silent-refresh.html 中看到的成功)并且调用第一个 ProfileService 的 'IsActiveAsync'(因为它需要通过配置文件服务为静默刷新生成令牌)。您能够看到 'HttpContext.User' 因为打开了一个 iframe,它允许发送所有必要的 cookie。
用户信息预检请求甚至没有到达配置文件服务,正如您从日志中看到的那样,只显示了一次 'Invoking IdentityServer endpoint: IdentityServer4.Endpoints.UserInfoEndpoint for /connect/userinfo'。此外,UserInfoEndpoint 会阻止任何选项请求通过(您可以在 'ProcessAsync' 方法 here 的开头验证它是否为真)。
现在,当您发出实际的用户信息请求时,您正试图从 HttpContext 获取信息(通常使用来自 cookie 的信息填充),但它是不可访问的,因为请求中没有发送 cookie。在'getJson'方法(来自oidc-client-js库)中发出请求here,可以看到请求中没有发送cookie('withCredentials' 属性 将根据请求设置为真)。
那么我们如何获取用户的必要信息呢?要获取此信息,我们可以参考 'context' passed into the ProfileService's 'IsActiveAsync' that contains a principal populated by the access token claims (this process can be seen in the 'ValidateAccessTokenAsync' method here).