Ajax-requests 中的 CORS 针对具有 IdentityServer3 授权的 MVC 控制器
CORS in Ajax-requests against an MVC controller with IdentityServer3-authorization
我目前在使用各种 Ajax-requests 来保存、加载和自动完成数据的网站上工作。它是使用 C#、MVC 和 JQuery 构建的。 MVC 控制器上的所有操作都需要用户获得授权,我们使用 IdentityServer3 进行身份验证。使用NuGet安装,当前版本为2.3.0.
当我打开页面并按下按钮时,一切正常。问题似乎发生在某个 session 过期时。如果我闲置一段时间,并尝试使用 Ajax-function,它会生成以下错误:
XMLHttpRequest cannot load https://identityserver.domain.com/connect/authorize?client_id=Bar&redirect_uri=http%3a%2f%2flocalhost%3a12345&response_mode=form_post&response_type=id_token+token&scope=openid+profile+email+phone+roles [...]. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:12345' is therefore not allowed access.
据我所知Ajax,问题本身很简单。 MVC 站点已失去对当前 session 的跟踪,它正在要求客户端再次进行身份验证。我从 Ajax-request 得到的响应是“302 Found”,其中 Location-header 指向我们的 IdentityServer。 IdentityServer 恰好在另一个域上,虽然在执行常规 HTTP-requests 时它工作正常,但它在 Ajax-requests 时效果不是特别好。 "Same Origin Policy" 直接阻止 Ajax-function 进行身份验证。如果我刷新页面,我将被重定向到 IdentityServer 并正常进行身份验证。几分钟后一切都会恢复正常。
解决方案可能是在来自 IdentityServer 的响应消息中添加一个额外的 header,明确声明允许此服务 cross-origin 请求。
我目前没有从 IdentityServer(在 Fiddler 中检查)得到这个header。
According to the docs,应该默认开启。我已经检查过我们确实以这种方式启用了 CORS:
factory.CorsPolicyService = new Registration<ICorsPolicyService>(new DefaultCorsPolicyService { AllowAll = true });
这是我的一位客户:
new Client
{
Enabled = true,
ClientName = "Foo",
ClientId = "Bar",
ClientSecrets = new List<Secret>
{
new Secret("Cosmic")
},
Flow = Flows.Implicit,
RequireConsent = false,
AllowRememberConsent = true,
AccessTokenType = AccessTokenType.Jwt,
PostLogoutRedirectUris = new List<string>
{
"http://localhost:12345/",
"https://my.domain.com"
},
RedirectUris = new List<string>
{
"http://localhost:12345/",
"https://my.domain.com"
},
AllowAccessToAllScopes = true
}
这些设置不起作用。我注意到我在此处的 URI 中有一个额外的正斜杠,但如果我删除它们,我会得到默认的 IdentityServer-error,表明客户端未被授权(错误的 URI)。如果我部署站点(而不是 运行 本地主机调试),我使用没有尾部斜杠的域名,并且我得到与调试中完全相同的行为。我确实注意到上面的错误消息中没有尾部斜杠,我认为这可能是问题所在,直到我在站点的部署版本中看到同样的东西。
我也做了自己的policy provider,像这样:
public class MyCorsPolicyService : ICorsPolicyService
{
public Task<bool> IsOriginAllowedAsync(string origin)
{
return Task.FromResult(true);
}
}
...然后我像这样将它插入到 IdentityServerServiceFactory 中:
factory.CorsPolicyService = new Registration<ICorsPolicyService>(new MyCorsPolicyService());
想法是 return 无论来源如何。这也不起作用;与之前完全相同的结果。
我已经阅读了关于这个特定主题的十几个其他线程,但我一无所获。据我所知,在不同站点的设置方面,我们没有做任何不寻常的事情。都差不多out-of-the-box了。有什么建议吗?
-----更新-----
问题依然存在。我现在尝试了一些新的策略。我在某处读到 cookie 身份验证对 Ajax-requests 不利,我应该改用不记名令牌。我在 Ajax 中这样设置:
$(function () {
$(document).ajaxSend(function (event, request, settings) {
console.log("Setting bearer token.");
request.setRequestHeader("Authorization", "Bearer " + $bearerToken);
});
});
Chrome 中的控制台和 Fiddler 均确认令牌确实存在并由 JQuery 发送。我使用的令牌来自 access_token-property 声明主体 object 来自 HttpContext.GetOwinContext().Authentication.User.
这没什么用。我仍然从服务器收到 302 响应,并且 Fiddler 显示令牌未在以下 Ajax-request(这是一个 GET-request)上发送到 IdentityServer。
从那里,我读到了这个帖子:
Handling CORS Preflight requests to ASP.NET MVC actions
我试图将此代码放入 IdentityServer 的 startup.cs,但似乎没有 "preflight" 请求进入。我在 Fiddler 中看到的是(从一开始):
1 - 从客户端到 MVC 控制器的初始 Ajax-request:
POST http://localhost:12345/my/url HTTP/1.1
Host: localhost:12345
Connection: keep-alive
Content-Length: pretty long
Authorization: Bearer <insert long token here>
Origin: http://localhost:12345
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.106 Safari/537.36
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Accept: application/json, text/javascript, */*; q=0.01
X-Requested-With: XMLHttpRequest
Referer: http://localhost:12345/my/url
Accept-Encoding: gzip, deflate
Accept-Language: nb-NO,nb;q=0.8,no;q=0.6,nn;q=0.4,en-US;q=0.2,en;q=0.2
Cookie: OpenIdConnect.nonce.<insert 30 000 lbs of hashed text here>
param=fish&morestuff=salmon&crossDomain=true
2 - 来自 MVC 控制器的重定向响应:
HTTP/1.1 302 Found
Cache-Control: private
Location: https://identityserver.domain.com/connect/authorize?client_id=Bar&redirect_uri=http%3a%2f%2flocalhost%3a12345%2f&response_mode=form_post&response_type=id_token+token&scope=openid+profile+email [...]
Server: Microsoft-IIS/10.0
X-AspNetMvc-Version: 5.2
X-AspNet-Version: 4.0.30319
Set-Cookie: OpenIdConnect.nonce.<lots of hashed text>
X-SourceFiles: <more hashed text>
X-Powered-By: ASP.NET
Date: Fri, 15 Jan 2016 12:23:08 GMT
Content-Length: 0
3 - Ajax-request 到 IdentityServer:
GET https://identityserver.domain.com/connect/authorize?client_id=Bar&redirect_uri=http%3a%2f%2flocalhost%3a12345%2f&response_mode=form_post&response_type=id_token+token&scope=openid+profile+email [...]
Host: identityserver.domain.com
Connection: keep-alive
Accept: application/json, text/javascript, */*; q=0.01
Origin: http://localhost:12345
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.106 Safari/537.36
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Referer: http://localhost:12345/my/url
Accept-Encoding: gzip, deflate, sdch
Accept-Language: nb-NO,nb;q=0.8,no;q=0.6,nn;q=0.4,en-US;q=0.2,en;q=0.2
4 - 来自 IdentityServer3 的响应
HTTP/1.1 302 Found
Content-Length: 0
Location: https://identityserver.domain.com/login?signin=<some hexadecimal id>
Server: Microsoft-IIS/8.5
Set-Cookie: SignInMessage.<many, many, many hashed bytes>; path=/; secure; HttpOnly
X-Powered-By: ASP.NET
Date: Fri, 15 Jan 2016 12:23:11 GMT
5 - Chrome
的崩溃
XMLHttpRequest cannot load https://identityserver.domain.com/connect/authorize?client_id=Bar&blahblahblah. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:12345' is therefore not allowed access.
我最近遇到了这个问题,它是由 header X-Requested-With 与 AJAX 请求一起发送引起的。删除此 header 或拦截它并使用 401 处理它将使您走上正轨。
如果您没有这个 header,问题很可能是由另一个 header 触发 Access-Control-Allow-Origin 响应引起的。
如您所见,您在 Identity Server 中对 CORS 所做的任何操作都无法解决此问题。
事实证明,问题出在 MVC 中的客户端配置上。我缺少 UseTokenLifetime 属性,它应该设置为 false。
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = "Bar",
Scope = "openid profile email phone roles",
UseTokenLifetime = false,
SignInAsAuthenticationType = "Cookies"
[...]
出于某种原因,IdentityServer 将所有这些 cookie 设置为在分发后 5 分钟内过期。此特定设置将覆盖 IdentityServer 的微小过期时间,而是使用 aprox。 10 小时,或您的客户端应用程序中的任何默认值。
可以说这足以解决问题了。然而,如果用户决定在网站上闲置 10 个小时,除了 Ajax-buttons.
,什么都不点击,这将不可避免地 return
https://github.com/IdentityServer/IdentityServer3/issues/2424
我也遇到了这个问题,UseTokenLifetime = false
没有解决这个问题,因为你失去了 STS 上的令牌有效性。
当我尝试访问授权的 api 方法时,即使我在 Owin 上有效,我仍然得到 401。
我找到的解决方案是将 UseTokenLifetime = true
保留为默认值,但要编写一个全局 ajax 错误处理程序(或 angular http 拦截器),如下所示:
$.ajaxSetup({
global: true,
error: function(xhr, status, err) {
if (xhr.status == -1) {
alert("You were idle too long, redirecting to STS") //or something like that
window.location.reload();
}
}});
触发身份验证工作流程。
我在使用 OWIN Middleware for OpenIDConnect 和不同的身份提供者时遇到了类似的问题。但是,该行为是在 1 小时而不是 5 分钟后发生的。解决方案是检查请求是否是 AJAX 请求,如果是,则将其强制为 return 401 而不是 302。这是执行此操作的代码:
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
ClientId = oktaOAuthClientId,
Authority = oidcAuthority,
RedirectUri = oidcRedirectUri,
ResponseType = oidcResponseType,
Scope = oauthScopes,
SignInAsAuthenticationType = "Cookies",
UseTokenLifetime = true,
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthorizationCodeReceived = async n =>
{
//...
},
RedirectToIdentityProvider = n => //token expired!
{
if (IsAjaxRequest(n.Request))
{
n.Response.StatusCode = 401;//for web api only!
n.Response.Headers.Remove("Set-Cookie");
n.State = NotificationResultState.HandledResponse;
}
return Task.CompletedTask;
},
}
});
然后,我使用Angular拦截器检测到状态码为401,并重定向到认证页面。
我目前在使用各种 Ajax-requests 来保存、加载和自动完成数据的网站上工作。它是使用 C#、MVC 和 JQuery 构建的。 MVC 控制器上的所有操作都需要用户获得授权,我们使用 IdentityServer3 进行身份验证。使用NuGet安装,当前版本为2.3.0.
当我打开页面并按下按钮时,一切正常。问题似乎发生在某个 session 过期时。如果我闲置一段时间,并尝试使用 Ajax-function,它会生成以下错误:
XMLHttpRequest cannot load https://identityserver.domain.com/connect/authorize?client_id=Bar&redirect_uri=http%3a%2f%2flocalhost%3a12345&response_mode=form_post&response_type=id_token+token&scope=openid+profile+email+phone+roles [...]. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:12345' is therefore not allowed access.
据我所知Ajax,问题本身很简单。 MVC 站点已失去对当前 session 的跟踪,它正在要求客户端再次进行身份验证。我从 Ajax-request 得到的响应是“302 Found”,其中 Location-header 指向我们的 IdentityServer。 IdentityServer 恰好在另一个域上,虽然在执行常规 HTTP-requests 时它工作正常,但它在 Ajax-requests 时效果不是特别好。 "Same Origin Policy" 直接阻止 Ajax-function 进行身份验证。如果我刷新页面,我将被重定向到 IdentityServer 并正常进行身份验证。几分钟后一切都会恢复正常。
解决方案可能是在来自 IdentityServer 的响应消息中添加一个额外的 header,明确声明允许此服务 cross-origin 请求。
我目前没有从 IdentityServer(在 Fiddler 中检查)得到这个header。
According to the docs,应该默认开启。我已经检查过我们确实以这种方式启用了 CORS:
factory.CorsPolicyService = new Registration<ICorsPolicyService>(new DefaultCorsPolicyService { AllowAll = true });
这是我的一位客户:
new Client
{
Enabled = true,
ClientName = "Foo",
ClientId = "Bar",
ClientSecrets = new List<Secret>
{
new Secret("Cosmic")
},
Flow = Flows.Implicit,
RequireConsent = false,
AllowRememberConsent = true,
AccessTokenType = AccessTokenType.Jwt,
PostLogoutRedirectUris = new List<string>
{
"http://localhost:12345/",
"https://my.domain.com"
},
RedirectUris = new List<string>
{
"http://localhost:12345/",
"https://my.domain.com"
},
AllowAccessToAllScopes = true
}
这些设置不起作用。我注意到我在此处的 URI 中有一个额外的正斜杠,但如果我删除它们,我会得到默认的 IdentityServer-error,表明客户端未被授权(错误的 URI)。如果我部署站点(而不是 运行 本地主机调试),我使用没有尾部斜杠的域名,并且我得到与调试中完全相同的行为。我确实注意到上面的错误消息中没有尾部斜杠,我认为这可能是问题所在,直到我在站点的部署版本中看到同样的东西。
我也做了自己的policy provider,像这样:
public class MyCorsPolicyService : ICorsPolicyService
{
public Task<bool> IsOriginAllowedAsync(string origin)
{
return Task.FromResult(true);
}
}
...然后我像这样将它插入到 IdentityServerServiceFactory 中:
factory.CorsPolicyService = new Registration<ICorsPolicyService>(new MyCorsPolicyService());
想法是 return 无论来源如何。这也不起作用;与之前完全相同的结果。
我已经阅读了关于这个特定主题的十几个其他线程,但我一无所获。据我所知,在不同站点的设置方面,我们没有做任何不寻常的事情。都差不多out-of-the-box了。有什么建议吗?
-----更新-----
问题依然存在。我现在尝试了一些新的策略。我在某处读到 cookie 身份验证对 Ajax-requests 不利,我应该改用不记名令牌。我在 Ajax 中这样设置:
$(function () {
$(document).ajaxSend(function (event, request, settings) {
console.log("Setting bearer token.");
request.setRequestHeader("Authorization", "Bearer " + $bearerToken);
});
});
Chrome 中的控制台和 Fiddler 均确认令牌确实存在并由 JQuery 发送。我使用的令牌来自 access_token-property 声明主体 object 来自 HttpContext.GetOwinContext().Authentication.User.
这没什么用。我仍然从服务器收到 302 响应,并且 Fiddler 显示令牌未在以下 Ajax-request(这是一个 GET-request)上发送到 IdentityServer。
从那里,我读到了这个帖子: Handling CORS Preflight requests to ASP.NET MVC actions 我试图将此代码放入 IdentityServer 的 startup.cs,但似乎没有 "preflight" 请求进入。我在 Fiddler 中看到的是(从一开始):
1 - 从客户端到 MVC 控制器的初始 Ajax-request:
POST http://localhost:12345/my/url HTTP/1.1
Host: localhost:12345
Connection: keep-alive
Content-Length: pretty long
Authorization: Bearer <insert long token here>
Origin: http://localhost:12345
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.106 Safari/537.36
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Accept: application/json, text/javascript, */*; q=0.01
X-Requested-With: XMLHttpRequest
Referer: http://localhost:12345/my/url
Accept-Encoding: gzip, deflate
Accept-Language: nb-NO,nb;q=0.8,no;q=0.6,nn;q=0.4,en-US;q=0.2,en;q=0.2
Cookie: OpenIdConnect.nonce.<insert 30 000 lbs of hashed text here>
param=fish&morestuff=salmon&crossDomain=true
2 - 来自 MVC 控制器的重定向响应:
HTTP/1.1 302 Found
Cache-Control: private
Location: https://identityserver.domain.com/connect/authorize?client_id=Bar&redirect_uri=http%3a%2f%2flocalhost%3a12345%2f&response_mode=form_post&response_type=id_token+token&scope=openid+profile+email [...]
Server: Microsoft-IIS/10.0
X-AspNetMvc-Version: 5.2
X-AspNet-Version: 4.0.30319
Set-Cookie: OpenIdConnect.nonce.<lots of hashed text>
X-SourceFiles: <more hashed text>
X-Powered-By: ASP.NET
Date: Fri, 15 Jan 2016 12:23:08 GMT
Content-Length: 0
3 - Ajax-request 到 IdentityServer:
GET https://identityserver.domain.com/connect/authorize?client_id=Bar&redirect_uri=http%3a%2f%2flocalhost%3a12345%2f&response_mode=form_post&response_type=id_token+token&scope=openid+profile+email [...]
Host: identityserver.domain.com
Connection: keep-alive
Accept: application/json, text/javascript, */*; q=0.01
Origin: http://localhost:12345
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.106 Safari/537.36
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Referer: http://localhost:12345/my/url
Accept-Encoding: gzip, deflate, sdch
Accept-Language: nb-NO,nb;q=0.8,no;q=0.6,nn;q=0.4,en-US;q=0.2,en;q=0.2
4 - 来自 IdentityServer3 的响应
HTTP/1.1 302 Found
Content-Length: 0
Location: https://identityserver.domain.com/login?signin=<some hexadecimal id>
Server: Microsoft-IIS/8.5
Set-Cookie: SignInMessage.<many, many, many hashed bytes>; path=/; secure; HttpOnly
X-Powered-By: ASP.NET
Date: Fri, 15 Jan 2016 12:23:11 GMT
5 - Chrome
的崩溃XMLHttpRequest cannot load https://identityserver.domain.com/connect/authorize?client_id=Bar&blahblahblah. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:12345' is therefore not allowed access.
我最近遇到了这个问题,它是由 header X-Requested-With 与 AJAX 请求一起发送引起的。删除此 header 或拦截它并使用 401 处理它将使您走上正轨。
如果您没有这个 header,问题很可能是由另一个 header 触发 Access-Control-Allow-Origin 响应引起的。
如您所见,您在 Identity Server 中对 CORS 所做的任何操作都无法解决此问题。
事实证明,问题出在 MVC 中的客户端配置上。我缺少 UseTokenLifetime 属性,它应该设置为 false。
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = "Bar",
Scope = "openid profile email phone roles",
UseTokenLifetime = false,
SignInAsAuthenticationType = "Cookies"
[...]
出于某种原因,IdentityServer 将所有这些 cookie 设置为在分发后 5 分钟内过期。此特定设置将覆盖 IdentityServer 的微小过期时间,而是使用 aprox。 10 小时,或您的客户端应用程序中的任何默认值。
可以说这足以解决问题了。然而,如果用户决定在网站上闲置 10 个小时,除了 Ajax-buttons.
,什么都不点击,这将不可避免地 returnhttps://github.com/IdentityServer/IdentityServer3/issues/2424
我也遇到了这个问题,UseTokenLifetime = false
没有解决这个问题,因为你失去了 STS 上的令牌有效性。
当我尝试访问授权的 api 方法时,即使我在 Owin 上有效,我仍然得到 401。
我找到的解决方案是将 UseTokenLifetime = true
保留为默认值,但要编写一个全局 ajax 错误处理程序(或 angular http 拦截器),如下所示:
$.ajaxSetup({
global: true,
error: function(xhr, status, err) {
if (xhr.status == -1) {
alert("You were idle too long, redirecting to STS") //or something like that
window.location.reload();
}
}});
触发身份验证工作流程。
我在使用 OWIN Middleware for OpenIDConnect 和不同的身份提供者时遇到了类似的问题。但是,该行为是在 1 小时而不是 5 分钟后发生的。解决方案是检查请求是否是 AJAX 请求,如果是,则将其强制为 return 401 而不是 302。这是执行此操作的代码:
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
ClientId = oktaOAuthClientId,
Authority = oidcAuthority,
RedirectUri = oidcRedirectUri,
ResponseType = oidcResponseType,
Scope = oauthScopes,
SignInAsAuthenticationType = "Cookies",
UseTokenLifetime = true,
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthorizationCodeReceived = async n =>
{
//...
},
RedirectToIdentityProvider = n => //token expired!
{
if (IsAjaxRequest(n.Request))
{
n.Response.StatusCode = 401;//for web api only!
n.Response.Headers.Remove("Set-Cookie");
n.State = NotificationResultState.HandledResponse;
}
return Task.CompletedTask;
},
}
});
然后,我使用Angular拦截器检测到状态码为401,并重定向到认证页面。