为 Web Api 2 和 OWIN 令牌身份验证启用 CORS

Enable CORS for Web Api 2 and OWIN token authentication

我有一个 ASP.NET MVC 5 web 项目 (localhost:81),它使用 Knockoutjs 从我的 WebApi 2 项目 (localhost:82) 调用函数,以便在两个项目之间进行通信我启用 CORS。到目前为止一切正常,直到我尝试对 WebApi.

实施 OWIN 令牌身份验证

要在 Web 上使用 /token 端点Api,我还需要在端点上启用 CORS,但经过数小时的尝试和搜索解决方案后,它现在仍在工作并且 api/token仍然导致:

XMLHttpRequest cannot load http://localhost:82/token. No 'Access-Control-Allow-Origin' header is present on the requested resource. 
public void Configuration(IAppBuilder app)
{
    app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
    TokenConfig.ConfigureOAuth(app);
    ...
}

令牌配置

public static void ConfigureOAuth(IAppBuilder app)
{
    app.CreatePerOwinContext(ApplicationDbContext.Create);
    app.CreatePerOwinContext<AppUserManager>(AppUserManager.Create);

    OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions()
    {
        AllowInsecureHttp = true,
        TokenEndpointPath = new PathString("/token"),
        AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
        Provider = new SimpleAuthorizationServerProvider()
    };

    app.UseOAuthAuthorizationServer(OAuthServerOptions);
    app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
}

授权提供者

public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
    context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });

    var appUserManager = context.OwinContext.GetUserManager<AppUserManager>();
    IdentityUser user = await appUserManager.FindAsync(context.UserName, context.Password);

    if (user == null)
    {
        context.SetError("invalid_grant", "The user name or password is incorrect.");
        return;
    }
    ... claims
}

身份配置

public static AppUserManager Create(IdentityFactoryOptions<AppUserManager> options, IOwinContext context)
{
    // Tried to enable it again without success. 
    //context.Response.Headers.Add("Access-Control-Allow-Origin", new[] {"*"});
    
    var manager = new AppUserManager(new UserStore<AppUser>(context.Get<ApplicationDbContect>()));
        
    ...

    var dataProtectionProvider = options.DataProtectionProvider;
    if (dataProtectionProvider != null)
    {
        manager.UserTokenProvider =
                new DataProtectorTokenProvider<AppUser>(dataProtectionProvider.Create("ASP.NET Identity"));
    }
    return manager;
}

编辑:

1.重要说明是直接打开端点 (localhost:82/token) 有效。

2。从 web 项目调用 Api (localhost:82/api/..) 也可以,因此为 WebApi.

启用了 CORS

我知道您的问题已在评论中解决,但我认为了解导致问题的原因以及如何解决整个 class 问题很重要。

查看您的代码,我可以看到您不止一次为令牌端点设置 Access-Control-Allow-Origin header:

app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);

GrantResourceOwnerCredentials 方法内部:

context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" }); 

CORS specifications 来看,这本身就是一个问题,因为:

If the response includes zero or more than one Access-Control-Allow-Origin header values, return fail and terminate this algorithm.

在您的场景中,框架将此 header 设置两次,并且了解 CORS 必须如何实施,这将导致在某些情况下删除 header(可能 client-related).

以下问题的答案也证实了这一点:Duplicate Access-Control-Allow-Origin: * causing COR error?

出于这个原因,在调用 ConfigureOAuth 之后将调用移动到 app.UseCors 允许您的 CORS header 仅设置一次(因为 owin 管道在 OAuth 中间件处被中断,并且永远不会到达 Token 端点的 Microsoft CORS 中间件)并使您的 Ajax 调用正常工作。

为了更好的全局解决方案,您可以尝试在 OAuth 中间件调用之前再次放置 app.UseCors,并删除 GrantResourceOwnerCredentials.

中的第二个 Access-Control-Allow-Origin 插入

按照以下步骤操作,您的 API 将正常工作:

  1. 从您的 API.
  2. 中删除 任何代码,如 config.EnableCors(), [EnableCors(header:"*"....)]
  3. 转到 startup.cs 并

    行下方添加
    app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
    

之前

    ConfigureAuth(app);

您还需要安装 Microsoft.owin.cors 包才能使用此功能

不使用app.UseCors()

解决问题

我遇到了同样的问题。我使用 Vue.Js 客户端 axois 来访问我的 REST-API 和 cross-corps 。在我的 Owin-Api-Server 上,由于 与其他第 3 方组件的版本冲突 ,我 无法添加 Microsoft.Owin.Cors nuget。所以我无法使用app.UseCors()方法,但我通过使用中间件管道解决了它。

private IDisposable _webServer = null;

public void Start(ClientCredentials credentials)
{
    ...
    _webServer = WebApp.Start(BaseAddress, (x) => Configuration(x));
    ...
}

public void Configuration(IAppBuilder app)
{
    ...
    // added middleware insted of app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
    app.Use<MyOwinMiddleware>();
    app.UseWebApi(config);
    ...
}

public class MyOwinMiddleware : OwinMiddleware
{
    public MyOwinMiddleware(OwinMiddleware next) :
        base(next)
    { }

    public override async Task Invoke(IOwinContext context)
    {
        var request = context.Request;
        var response = context.Response;

        response.OnSendingHeaders(state =>
        {
            var resp = (IOwinResponse)state;

            // without this headers -> client apps will be blocked to consume data from this api
            if (!resp.Headers.ContainsKey("Access-Control-Allow-Origin"))
                resp.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
            if (!resp.Headers.ContainsKey("Access-Control-Allow-Headers"))
                resp.Headers.Add("Access-Control-Allow-Headers", new[] { "*" });
            if (!resp.Headers.ContainsKey("Access-Control-Allow-Methods"))
                resp.Headers.Add("Access-Control-Allow-Methods", new[] { "*" });

            // by default owin is blocking options not from same origin with MethodNotAllowed
            if (resp.StatusCode == (int)HttpStatusCode.MethodNotAllowed &&
                HttpMethod.Options == new HttpMethod(request.Method))
            {
                resp.StatusCode = (int)HttpStatusCode.OK;
                resp.ReasonPhrase = HttpStatusCode.OK.ToString();
            }

        }, response);

        await Next.Invoke(context);
    }
}

所以我创建了自己的中间件并操纵了响应。 GET 调用只需要 Access-Control-Allow headers 而对于 OPTIONS 调用我还需要操作 StatusCode 因为 axois.post() 在发送 [=42= 之前先用 OPTIONS-method 调用].如果 OPTIONS return StatusCode 405,则永远不会发送 POST。

这解决了我的问题。也许这也可以帮助别人。