使用IdentityServer3授权针对MVC控制器的Ajax请求中的CORS

时间:2016-01-13 13:50:01

标签: ajax cors identityserver3

我目前正在使用各种Ajax请求来保存,加载和自动填充数据的网站上工作。它是使用C#,MVC和JQuery构建的。 MVC控制器上的所有操作都需要授权用户,我们使用IdentityServer3进行身份验证。它是使用NuGet安装的,当前版本是2.3.0。

当我打开页面并按下按钮时,一切正常。某个会话到期时似乎会出现此问题。如果我暂停一段时间,并尝试使用Ajax函数,则会产生以下错误:

  

XMLHttpRequest无法加载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'标头出现在请求的资源上。起源' http://localhost:12345'因此不允许访问。

根据我对Ajax的了解,问题本身非常简单。 MVC站点已经忘记了当前会话,并且它要求客户端再次进行身份验证。我从Ajax请求得到的响应是" 302 Found",其中Location-header指向我们的IdentityServer。 IdentityServer恰好位于另一个域上,虽然这在您执行常规HTTP请求时工作正常,但它对Ajax请求不起作用。 "同源政策"直接阻止Ajax功能进行身份验证。如果我刷新页面,我将被重定向到IdentityServer并正常进行身份验证。然后事情会恢复正常几分钟。

解决方案可能是在IdentityServer的响应消息中添加一个额外的标头,明确指出此服务允许跨源请求。

我目前从IdentityServer获取此标头(在Fiddler中检查)。

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错误,指出客户端未被授权(错误的URI)。如果我部署站点(而不是运行localhost调试),我使用没有尾部斜杠的域名,我得到与调试时完全相同的行为。我注意到上面的错误消息中没有尾部斜杠,我认为这可能是问题,直到我在网站的部署版本中看到同样的事情。

我也建立了自己的政策提供者,如下:

public class MyCorsPolicyService : ICorsPolicyService
{
    public Task<bool> IsOriginAllowedAsync(string origin)
    {
        return Task.FromResult(true);
    }
}

...我将它插入IdentityServerServiceFactory,如下所示:

factory.CorsPolicyService = new Registration<ICorsPolicyService>(new MyCorsPolicyService());

这个想法是让它无论原点如何都返回真实。这也不起作用;与以前完全相同的结果。

我已经阅读了关于这一特定主题的十几个其他主题,但我无处可去。据我所知,当涉及到不同网站的设置时,我们没有做任何不寻常的事情。它几乎都是开箱即用的。有什么建议吗?

-----更新-----

问题仍然存在。我现在尝试了一些新的策略。我在某地读过cookie身份验证对于Ajax请求不好,而且我应该使用bearer令牌。我在Ajax中设置了这个:

$(function () {
    $(document).ajaxSend(function (event, request, settings) {
        console.log("Setting bearer token.");
        request.setRequestHeader("Authorization", "Bearer " + $bearerToken);
    });
});

Chrome和Fiddler中的控制台都确认该令牌确实存在并由JQuery发送。我使用的令牌来自HttpContext.GetOwinContext()声明主体对象的access_token-property。身份验证。用户。

这没什么用。我仍然收到来自服务器的302响应,并且Fiddler显示该令牌未在以下Ajax请求(这是GET请求)上发送到IdentityServer。

从那里,我读了这个帖子: Handling CORS Preflight requests to ASP.NET MVC actions 我试图将这些代码放入IdentityServer的startup.cs中,但似乎没有&#34;预检&#34;要求进去。我在Fiddler看到的就是这个(从一开始):

1 - 从客户端到MVC控制器的初始Ajax请求:

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 - 对IdentityServer的Ajax请求:

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无法加载https://identityserver.domain.com/connect/authorize?client_id=Bar&blahblahblah。 No&#39; Access-Control-Allow-Origin&#39;标头出现在请求的资源上。起源&#39; http://localhost:12345&#39;因此不允许访问。

4 个答案:

答案 0 :(得分:1)

我最近遇到过这个问题,它是由标题X-Requested-With与AJAX请求一起发送引起的。删除此标题或拦截它并使用401处理它将使您走上正确的轨道。

如果您没有此标头,则问题很可能是由触发Access-Control-Allow-Origin响应的不同标头引起的。

正如您所发现的,您在Identity Server中对CORS所做的任何事情都无法解决此问题。

答案 1 :(得分:1)

我也遇到了这个问题,UseTokenLifetime = false没有解决问题,因为你在STS上放弃了令牌有效性。

当我尝试使用授权的api方法时,即使我在Owin上有效,我仍然有401。

我找到的解决方案是将UseTokenLifetime = true保留为默认值,但是编写一个全局ajax错误处理程序(或角度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();
    }
}});

触发身份验证工作流程。

答案 2 :(得分:0)

事实证明,问题出在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按钮,它将不可避免地返回。

https://github.com/IdentityServer/IdentityServer3/issues/2424

答案 3 :(得分:0)

我使用OWIN Middleware for OpenIDConnect与另一个身份提供商有类似的问题。但是,行为发生在1小时而不是5分钟之后。解决方案是检查请求是否是AJAX请求,如果是,则强制它返回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的statusCode,并重定向到身份验证页面。