WebAPI 和令牌的可怕 CORS 问题
Dreaded CORS issue with WebAPI and token
我发誓这在我身上发生了很多次,以至于我实际上 讨厌 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 cannot load http://localhost:62605/token. Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:50098' is therefore not allowed access. The response had HTTP status code 400.
Firefox 显示如下:
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://localhost:62605/token. (Reason: CORS header 'Access-Control-Allow-Origin' missing).
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://localhost:62605/token. (Reason: CORS request failed).
出于测试目的,我决定使用 fiddler 来查看是否可以看到任何其他信息,这些信息可能会为我提供有关正在发生的事情的线索。当我尝试登录时,FIddler 将响应代码显示为 400,如果我查看原始响应,我会看到错误:
{"error":"unsupported_grant_type"}
这很奇怪,因为我发送的数据没有改变并且在拆分之前工作正常。
我决定在 fiddler 上使用 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
我们 运行 遇到了类似的情况,最终在 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>
由于 OAuthAuthorizationServer
作为 Owin 中间件运行,您必须使用适当的包 Microsoft.Owin.Cors
来启用与管道中的任何中间件一起工作的 CORS。请记住,就 owin 管道而言,WebApi 和 Mvc 本身只是中间件。
因此,从您的 WebApiConfig 中删除 config.EnableCors(new EnableCorsAttribute("*", "*", "*"));
,并将以下内容添加到您的启动 class。
注意 app.UseCors
它必须在 app.UseOAuthAuthorizationServer
之前
app.UseCors(CorsOptions.AllowAll)
里面
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
摆脱这个:
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
目前您正在执行两次 CORS 操作。一次使用 .EnableCors 并再次在您的令牌端点中写入 header。
为了它的价值,在我的 OWIN 启动中 class 我在最上面有这个:
app.UseCors(CorsOptions.AllowAll);
我的 WebAPI 注册方法中也没有它,因为我让 OWIN 启动处理它。
事实证明,CORS 完全没有问题。我有一个拦截器 class 错误地修改了 headers。我建议将来参考,任何其他有这些问题的人,如果你在 WebConfig.cs 或你的 Startup class 甚至 web.config 中设置了你的 CORS,那么你需要检查没有什么是修改你的 headers。如果是,请禁用它并再次测试。
@r3plica
我遇到了这个问题,就像比尔说的那样。
将行 "app.UseCors" 放在 Configuration method() 的最顶部
(在 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);
}
我发誓这在我身上发生了很多次,以至于我实际上 讨厌 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 cannot load http://localhost:62605/token. Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:50098' is therefore not allowed access. The response had HTTP status code 400.
Firefox 显示如下:
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://localhost:62605/token. (Reason: CORS header 'Access-Control-Allow-Origin' missing). Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://localhost:62605/token. (Reason: CORS request failed).
出于测试目的,我决定使用 fiddler 来查看是否可以看到任何其他信息,这些信息可能会为我提供有关正在发生的事情的线索。当我尝试登录时,FIddler 将响应代码显示为 400,如果我查看原始响应,我会看到错误:
{"error":"unsupported_grant_type"}
这很奇怪,因为我发送的数据没有改变并且在拆分之前工作正常。 我决定在 fiddler 上使用 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
我们 运行 遇到了类似的情况,最终在 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>
由于 OAuthAuthorizationServer
作为 Owin 中间件运行,您必须使用适当的包 Microsoft.Owin.Cors
来启用与管道中的任何中间件一起工作的 CORS。请记住,就 owin 管道而言,WebApi 和 Mvc 本身只是中间件。
因此,从您的 WebApiConfig 中删除 config.EnableCors(new EnableCorsAttribute("*", "*", "*"));
,并将以下内容添加到您的启动 class。
注意 app.UseCors
它必须在 app.UseOAuthAuthorizationServer
app.UseCors(CorsOptions.AllowAll)
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
摆脱这个:
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
目前您正在执行两次 CORS 操作。一次使用 .EnableCors 并再次在您的令牌端点中写入 header。
为了它的价值,在我的 OWIN 启动中 class 我在最上面有这个:
app.UseCors(CorsOptions.AllowAll);
我的 WebAPI 注册方法中也没有它,因为我让 OWIN 启动处理它。
事实证明,CORS 完全没有问题。我有一个拦截器 class 错误地修改了 headers。我建议将来参考,任何其他有这些问题的人,如果你在 WebConfig.cs 或你的 Startup class 甚至 web.config 中设置了你的 CORS,那么你需要检查没有什么是修改你的 headers。如果是,请禁用它并再次测试。
@r3plica
我遇到了这个问题,就像比尔说的那样。
将行 "app.UseCors" 放在 Configuration method() 的最顶部 (在 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);
}