WebAPI 和令牌的可怕 CORS 问题

Posted

技术标签:

【中文标题】WebAPI 和令牌的可怕 CORS 问题【英文标题】:Dreaded CORS issue with WebAPI and token 【发布时间】:2016-01-20 10:35:09 【问题描述】:

我发誓这种事在我身上发生过很多次,以至于我真的讨厌 CORS。 我刚刚将我的应用程序一分为二,一个只处理 API 端的东西,另一个处理客户端的东西。 我以前做过这个,所以我知道我需要确保 CORS 已启用并允许所有,所以我在 WebApiConfig.cs

中进行了设置
public static void Register(HttpConfiguration config)


    // Enable CORS
    config.EnableCors(new EnableCorsAttribute("*", "*", "*"));

    // Web API configuration and services
    var formatters = config.Formatters;
    var jsonFormatter = formatters.JsonFormatter;
    var serializerSettings = jsonFormatter.SerializerSettings;

    // Remove XML formatting
    formatters.Remove(config.Formatters.XmlFormatter);
    jsonFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/json"));

    // Configure our JSON output
    serializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
    serializerSettings.Formatting = Formatting.Indented;
    serializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
    serializerSettings.PreserveReferencesHandling = Newtonsoft.Json.PreserveReferencesHandling.None;

    // Configure the API route
    config.MapHttpAttributeRoutes();
    config.Routes.MapHttpRoute(
        name: "DefaultApi",
        routeTemplate: "controller/id",
        defaults: new  id = RouteParameter.Optional 
    );

如您所见,我的第一行启用了 CORS,所以它应该可以工作。 如果我打开我的客户端应用程序并查询 API,它确实可以工作(没有 EnableCors,我会收到预期的 CORS 错误。 问题是我的 /token 仍然出现 CORS 错误。现在我知道 /token 端点不是 WebAPI 的一部分,所以我创建了自己的 OAuthProvider(我必须指出它在其他地方也可以使用),看起来像这样:

public class OAuthProvider<TUser> : OAuthAuthorizationServerProvider
    where TUser : class, IUser

    private readonly string publicClientId;
    private readonly UserService<TUser> userService;

    public OAuthProvider(string publicClientId, UserService<TUser> userService)
    
        if (publicClientId == null)
            throw new ArgumentNullException("publicClientId");

        if (userService == null)
            throw new ArgumentNullException("userService");

        this.publicClientId = publicClientId; 
        this.userService = userService;
    

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

        var user = await this.userService.FindByUserNameAsync(context.UserName, context.Password);

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

        var oAuthIdentity = this.userService.CreateIdentity(user, context.Options.AuthenticationType);
        var cookiesIdentity = this.userService.CreateIdentity(user, CookieAuthenticationDefaults.AuthenticationType);
        var properties = CreateProperties(user.UserName);
        var ticket = new AuthenticationTicket(oAuthIdentity, properties);

        context.Validated(ticket);
        context.Request.Context.Authentication.SignIn(cookiesIdentity);
    

    public override Task TokenEndpoint(OAuthTokenEndpointContext context)
    
        foreach (KeyValuePair<string, string> property in context.Properties.Dictionary)
            context.AdditionalResponseParameters.Add(property.Key, property.Value);

        return Task.FromResult<object>(null);
    

    public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
    
        // Resource owner password credentials does not provide a client ID.
        if (context.ClientId == null)
        
            context.Validated();
        

        return Task.FromResult<object>(null);
    

    public override Task ValidateClientRedirectUri(OAuthValidateClientRedirectUriContext context)
    
        if (context.ClientId == this.publicClientId)
        
            var redirectUri = new Uri(context.RedirectUri);
            var expectedRootUri = new Uri(context.Request.Uri, redirectUri.PathAndQuery);

            if (expectedRootUri.AbsoluteUri == redirectUri.AbsoluteUri)
                context.Validated();
        

        return Task.FromResult<object>(null);
    

    public static AuthenticationProperties CreateProperties(string userName)
    
        IDictionary<string, string> data = new Dictionary<string, string>
        
             "userName", userName 
        ;

        return new AuthenticationProperties(data);
    

如您所见,在 GrantResourceOwnerCredentials 方法中,我再次启用了对所有内容的 CORS 访问。这应该适用于对 /token 的所有请求,但它不是。 当我尝试从客户端应用程序登录时,出现 CORS 错误。 Chrome 显示:

XMLHttpRequest 无法加载 http://localhost:62605/token。对预检请求的响应未通过访问控制检查:请求的资源上不存在“Access-Control-Allow-Origin”标头。因此,Origin 'http://localhost:50098' 不允许访问。响应的 HTTP 状态代码为 400。

Firefox 显示:

跨域请求被阻止:同源策略不允许读取位于http://localhost:62605/token 的远程资源。 (原因:缺少 CORS 标头“Access-Control-Allow-Origin”)。 跨域请求被阻止:同源策略不允许读取位于http://localhost:62605/token 的远程资源。 (原因:CORS 请求失败)。

出于测试目的,我决定使用 fiddler 来查看是否可以看到其他任何可能给我关于正在发生的事情提供线索的东西。当我尝试登录时,FIddler 显示响应代码为 400,如果我查看原始响应,我可以看到错误:

"error":"unsupported_grant_type"

这很奇怪,因为我发送的数据没有改变,并且在拆分之前工作正常。 我决定在提琴手上使用 Composer 并复制我期望 POST 请求的样子。 当我执行它时,它工作正常,我得到一个 200 的响应代码。

有人知道为什么会发生这种情况吗?

更新 1

仅供参考,来自我的客户端应用程序的请求如下所示:

OPTIONS http://localhost:62605/token HTTP/1.1
Host: localhost:62605
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
Access-Control-Request-Method: POST
Origin: http://localhost:50098
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (Khtml, like Gecko) Chrome/46.0.2490.71 Safari/537.36
Access-Control-Request-Headers: accept, authorization, content-type
Accept: */*
Referer: http://localhost:50098/account/signin
Accept-Encoding: gzip, deflate, sdch
Accept-Language: en-US,en;q=0.8

来自作曲家,它看起来像这样:

POST http://localhost:62605/token HTTP/1.1
User-Agent: Fiddler
Content-Type: 'application/x-www-form-urlencoded'
Host: localhost:62605
Content-Length: 67

grant_type=password&userName=foo&password=bar

【问题讨论】:

【参考方案1】:

内部

 public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)

摆脱这个:

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

目前你正在做两次 CORS 的事情。一次使用 .EnableCors,然后再次在令牌端点中写入标头。

对于它的价值,在我的 OWIN 启动课程中,我把它放在了最上面:

app.UseCors(CorsOptions.AllowAll);

我的 WebAPI 注册方法中也没有它,因为我让 OWIN 启动处理它。

【讨论】:

我遇到了这个问题,从一个角度应用程序调用了一个 c# web api。将 app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll); 移动到 **public void Configuration(IAppBuilder app) ** 的顶部就可以了。谢谢比尔。 UseCors 扩展来自Microsoft.Owin.Cors NuGet 包。【参考方案2】:

由于 OAuthAuthorizationServer 作为 Owin 中间件运行,您必须使用适当的包 Microsoft.Owin.Cors 来启用与管道中的任何中间件一起使用的 CORS。请记住,就 owin 管道而言,WebApi 和 Mvc 本身只是中间件。

所以从您的 WebApiConfig 中删除 config.EnableCors(new EnableCorsAttribute("*", "*", "*")); 并将以下内容添加到您的启动类中。 注意app.UseCors必须在app.UseOAuthAuthorizationServer之前

app.UseCors(CorsOptions.AllowAll)

【讨论】:

是的,两者都不能,正确的位置是在startup.cs中,我的从webapiconfig.cs中取出后就开始工作了【参考方案3】:

@r3plica

我遇到了这个问题,就像比尔说的那样。

在 Configuration method() 中将“app.UseCors”行放在最顶部 (在 ConfigureOAuth(app) 之前就足够了)

例子:

public void Configuration(IAppBuilder app)
    
        app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);

        HttpConfiguration config = new HttpConfiguration();

        ConfigureWebApi(config);
        ConfigureOAuth(app);

        app.UseWebApi(config);
    

【讨论】:

欢迎来到本站!我希望你不介意一些反馈来改进你的答案。首先,您不能在问题或答案中@ 某人,只能是 cmets,所以这只是您可以删除的噪音。添加一个为另一个答案提供示例的答案很好,我建议在这种情况下明确链接到答案。 To add to [Bill's answer](http://***.com/a/33265170/1677912)...,为清楚起见。祝你好运! 这正是我遇到的问题。我想知道您是否知道为什么会发生这种情况?【参考方案4】:

我们遇到了类似的情况,最终在 web.config 的 system.webServer 节点中指定了一些 CORS 数据以通过预检检查。您的情况与我们的情况略有不同,但也许这对您也有帮助。

这是我们添加的内容:

<httpProtocol>
  <customHeaders>
    <add name="Access-Control-Allow-Origin" value="*" />
    <add name="Access-Control-Allow-Headers" value="Content-Type" />
    <add name="Access-Control-Allow-Credentials" value="true" />
    <add name="Access-Control-Allow-Methods" value="GET, POST, OPTIONS" />
  </customHeaders>
</httpProtocol>

【讨论】:

我试过了,这不是问题。最终我想通了。我将发布解决方案。正如我所料,这与 COR 无关 :( 非常感谢!这解决了我的开发环境中的问题【参考方案5】:

事实证明,CORS 根本没有问题。我有一个拦截器类错误地修改了标题。如果您在 WebConfig.cs 或您的 Startup 类甚至 web.config 中设置了 CORS,我建议将来参考,如果您在 WebConfig.cs 或您的 Startup 类中设置了 CORS,那么您需要检查是否没有任何内容在修改您的标头。如果是,请禁用它并再次测试。

【讨论】:

需要详细说明吗? 你的拦截器现在是什么样子的?

以上是关于WebAPI 和令牌的可怕 CORS 问题的主要内容,如果未能解决你的问题,请参考以下文章

IIS 中托管的 WebAPI 中的 CORS 问题

Web Api 2 Preflight CORS 请求承载令牌

c# Web Api 启用了 CORS 并且请求的资源上不存在可怕的 No 'Access-Control-Allow-Origin' 标头

CORS跨源资源共享和Json Web Token

从 keycloak 刷新访问令牌时出现 CORS 错误

Web API 中的 OWIN CORS 问题