簡體   English   中英

為 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 令牌身份驗證。

要在 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);
    ...
}

令牌配置

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.從webproject調用Api(localhost:82/api/..)也可以,所以為WebApi啟用了CORS。

我知道您的問題已在評論中解決,但我相信了解導致問題的原因以及如何解決這一整類問題很重要。

查看您的代碼,我可以看到您不止一次為 Token 端點設置Access-Control-Allow-Origin標頭:

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

GrantResourceOwnerCredentials方法內部:

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

看看CORS 規范,這本身就是一個問題,因為:

如果響應包含零個或多個 Access-Control-Allow-Origin 標頭值,則返回失敗並終止此算法。

在您的場景中,框架兩次設置此標頭,並了解必須如何實現 CORS,這將導致在某些情況下(可能與客戶端相關)刪除標頭。

以下問題答案也證實了這一點: Duplicate Access-Control-Allow-Origin:*導致COR錯誤?

出於這個原因,移動呼叫app.UseCors調用后ConfigureOAuth讓您的CORS頭一次只能設置(因為owin管道在OAuth的中間件中斷,並且永遠不會達到對微軟CORS中間件Token端點)和品牌您的 Ajax 調用正常工作。

為了更好的和全球性的解決方案,你可以嘗試再次把app.UseCors的OAuth的中間件調用之前,取出第二個Access-Control-Allow-Origin內側插入GrantResourceOwnerCredentials

按照以下步驟操作,您的 API 將正常工作:

  1. 從您的 API 中刪除任何代碼,如config.EnableCors(), [EnableCors(header:"*"....)]
  2. 轉到 startup.cs 並添加以下行

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

之前

    ConfigureAuth(app);

您還需要安裝 Microsoft.owin.cors 包才能使用此功能

不使用app.UseCors()解決問題

我有同樣的問題。 我用了一個Vue.Js客戶端axois跨團訪問我的REST的API。 在我的 Owin-Api-Server 上,由於與其他 3rd 方組件的版本沖突,我無法添加 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() 在發送 POST 之前首先使用 OPTIONS-method 調用。 如果 OPTIONS 返回 StatusCode 405,則永遠不會發送 POST。

這解決了我的問題。 也許這也可以幫助某人。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM