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

时间:2016-03-29 13:09:22

标签: asp.net-web-api asp.net-mvc-5 cors asp.net-web-api2 owin

我有一个ASP.NET MVC 5 webproject(localhost:81),它使用Knockoutjs从我的WebApi 2项目(localhost:82)调用函数,以便在我启用CORS的两个项目之间进行通信。到目前为止,一切都有效,直到我尝试对WebApi实施OWIN令牌认证。

要在WebApi上使用/ token端点,我还需要在端点上启用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);
    ...
}

TokenConfig

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());
}

AuthorizationProvider

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
}

IdentityConfig

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。从webproject调用Api(localhost:82 / api / ..)也可以,因此为WebApi启用了CORS。

3 个答案:

答案 0 :(得分:40)

我知道你的问题在评论中得到了解决,但我认为了解导致问题的原因以及如何解决这一类问题非常重要。

查看您的代码,我可以看到您为Token端点多次设置Access-Control-Allow-Origin标头:

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

GrantResourceOwnerCredentials方法内:

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

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

  

如果响应包含零个或多个Access-Control-Allow-Origin标头值,则返回失败并终止此算法。

在您的方案中,框架将设置此标头两次,并了解必须如何实现CORS,这将导致在某些情况下删除标头(可能与客户端相关)。

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

因此,在调用app.UseCors之后将调用移至ConfigureOAuth允许您的CORS标头只设置一次(因为owin管道在OAuth中间件中断,并且永远不会到达Microsoft <{1}}端点的CORS中间件,使您的Ajax调用正常工作。

要获得更好的全局解决方案,您可以尝试在OAuth中间件调用之前再次Token,并删除app.UseCors内的第二个Access-Control-Allow-Origin插入。

答案 1 :(得分:8)

按照以下步骤进行操作:

  1. 从您的API中移除任何代码,例如config.EnableCors(), [EnableCors(header:"*"....)]
  2. 转到startup.cs并在下添加

    app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
    
  3. 之前

        ConfigureAuth(app);
    

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

答案 2 :(得分:0)

不使用app.UseCors()解决问题

我有同样的问题。我通过 axois 使用 Vue.Js客户端,通过跨公司访问我的REST-API。在我的Owin-Api服务器上,由于与其他第三方组件的版本冲突,我无法添加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标头,而对于OPTIONS调用,我还需要操纵StatusCode,因为axois.post()首先使用OPTIONS方法进行调用,然后再发送POST。如果OPTIONS返回StatusCode 405,则将永远不会发送POST。

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