我想做什么
我有一个Azure免费计划托管的后端ASP.Net核心Web API(源代码:https://github.com/killerrin/Portfolio-Backend)。
我还有一个客户网站,我想要消费该API。客户端应用程序不会托管在Azure上,而是托管在Github Pages或我可以访问的其他Web托管服务上。因此,域名不会排队。
考虑到这一点,我需要在Web API端启用CORS,但是我现在已经尝试了好几个小时并拒绝工作。
我如何设置客户端它只是一个用React.js编写的简单客户端。我在Jquery中通过AJAX调用API。 React网站有效,所以我不知道它。 Jquery API调用正如我在Attempt 1中确认的那样工作。这是我如何进行调用
var apiUrl = "http://andrewgodfroyportfolioapi.azurewebsites.net/api/Authentication";
//alert(username + "|" + password + "|" + apiUrl);
$.ajax({
url: apiUrl,
type: "POST",
data: {
username: username,
password: password
},
contentType: "application/json; charset=utf-8",
dataType: "json",
success: function (response) {
var authenticatedUser = JSON.parse(response);
//alert("Data Loaded: " + authenticatedUser);
if (onComplete != null) {
onComplete(authenticatedUser);
}
},
error: function (xhr, status, error) {
//alert(xhr.responseText);
if (onComplete != null) {
onComplete(xhr.responseText);
}
}
});
我试过了什么
尝试1 - “正确”的方式
https://docs.microsoft.com/en-us/aspnet/core/security/cors
我已经在微软网站上关注了这个教程,尝试了在Startup.cs中全局启用它的所有3个选项,在每个控制器上设置它并在每个Action上尝试它。
遵循此方法,Cross Domain可以工作,但只能在单个控制器上的一个Action上运行(POST到AccountController)。对于其他一切,Microsoft.AspNetCore.Cors
中间件拒绝设置标头。
我通过NUGET安装了Microsoft.AspNetCore.Cors
,版本是1.1.2
以下是我在Startup.cs中设置它的方法
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// Add Cors
services.AddCors(o => o.AddPolicy("MyPolicy", builder =>
{
builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader();
}));
// Add framework services.
services.AddMvc();
services.Configure<MvcOptions>(options =>
{
options.Filters.Add(new CorsAuthorizationFilterFactory("MyPolicy"));
});
...
...
...
}
// This method gets called by the runtime. Use this method to configure
//the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env,
ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
// Enable Cors
app.UseCors("MyPolicy");
//app.UseMvcWithDefaultRoute();
app.UseMvc();
...
...
...
}
正如你所看到的,我正在做所有事情。我在MVC之前添加了Cors两次,当这不起作用时我尝试将[EnableCors("MyPolicy")]
放在每个控制器上
[Route("api/[controller]")]
[EnableCors("MyPolicy")]
public class AdminController : Controller
尝试2 - 暴力强迫它
https://andrewlock.net/adding-default-security-headers-in-asp-net-core/
在尝试上一次尝试几个小时之后,我想我会尝试通过手动设置标头来强制它,强制它们在每个响应上运行。我在本教程后就如何手动为每个响应添加标头做了这个。
这些是我添加的标题
.AddCustomHeader("Access-Control-Allow-Origin", "*")
.AddCustomHeader("Access-Control-Allow-Methods", "*")
.AddCustomHeader("Access-Control-Allow-Headers", "*")
.AddCustomHeader("Access-Control-Max-Age", "86400")
这些是我试过的其他标题失败了
.AddCustomHeader("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE")
.AddCustomHeader("Access-Control-Allow-Headers", "content-type, accept, X-PINGOTHER")
.AddCustomHeader("Access-Control-Allow-Headers", "X-PINGOTHER, Host, User-Agent, Accept, Accept: application/json, application/json, Accept-Language, Accept-Encoding, Access-Control-Request-Method, Access-Control-Request-Headers, Origin, Connection, Content-Type, Content-Type: application/json, Authorization, Connection, Origin, Referer")
使用此方法,正确应用了跨站点标头,它们显示在我的开发人员控制台和邮递员中。然而问题是,当它通过Access-Control-Allow-Origin
检查时,webbrowser抛出一个hissy fit(我相信)Access-Control-Allow-Headers
说明415 (Unsupported Media Type)
所以蛮力方法也不起作用
最后
有没有人得到这个工作,可以伸出援助之手,或者只是能指出我正确的方向?
编辑
因此,为了让API调用完成,我不得不停止使用JQuery并切换到Pure Javascript XMLHttpRequest
格式。
尝试1
我设法通过遵循MindingData的答案让Microsoft.AspNetCore.Cors
工作,除了Configure
方法将app.UseCors
放在app.UseMvc
之前。
此外,当与Javascript API解决方案混合时,options.AllowAnyOrigin()
用于通配符支持也开始工作。
尝试2
所以我设法让尝试2(暴力强迫它)工作......唯一的例外是Access-Control-Allow-Origin
的通配符不起作用,因此我必须手动设置有权访问它的域。
它显然不理想,因为我只是想让这个WebAPI向所有人开放,但它至少对我来说在一个单独的网站上工作,这意味着它是一个开始
app.UseSecurityHeadersMiddleware(new SecurityHeadersBuilder()
.AddDefaultSecurePolicy()
.AddCustomHeader("Access-Control-Allow-Origin", "http://localhost:3000")
.AddCustomHeader("Access-Control-Allow-Methods", "OPTIONS, GET, POST, PUT, PATCH, DELETE")
.AddCustomHeader("Access-Control-Allow-Headers", "X-PINGOTHER, Content-Type, Authorization"));
因为您有一个非常简单的CORS策略(允许来自XXX域的所有请求),所以您不需要使它变得如此复杂。首先尝试执行以下操作(CORS的一个非常基本的实现)。
如果您还没有,请安装CORS nuget包。
Install-Package Microsoft.AspNetCore.Cors
在startup.cs的ConfigureServices方法中,添加CORS服务。
public void ConfigureServices(IServiceCollection services)
{
services.AddCors(); // Make sure you call this previous to AddMvc
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
然后在startup.cs的Configure方法中添加以下内容:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
// Make sure you call this before calling app.UseMvc()
app.UseCors(
options => options.WithOrigins("http://example.com").AllowAnyMethod()
);
app.UseMvc();
}
现在开始吧。策略适用于您需要针对不同操作的不同策略(例如,不同的主机或不同的标头)。对于您的简单示例,您真的不需要它。从这个简单的例子开始,然后根据需要进行调整。
进一步阅读:http://dotnetcoretutorials.com/2017/01/03/enabling-cors-asp-net-core/
对我来说,它与我使用的代码无关。对于Azure,我们必须进入App Service的设置,在侧面菜单上输入“CORS”。在那里,我不得不添加我要求的域名。一旦我进入,一切都是神奇的。
我上面的MindingData的答案工作,但我不得不使用Microsoft.AspNet.Cors而不是Microsoft.AspNetCore.Cors。我在Visual Studio 2019中使用.NetCore Web应用程序API项目
services.AddCors();
BEFORE services.AddMvc();app.UseCors(builder => builder
.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials());
app.UseMvc();
要点是在app.UseCors
之前添加app.UseMvc()
。
确保在MVC之前声明CORS功能,以便在MVC管道获得控制权并终止请求之前触发中间件。
在上述方法工作之后,您可以更改它配置特定的ORIGIN以接受api调用,并避免让您的API对所有人开放
public void ConfigureServices(IServiceCollection services)
{
services.AddCors(options => options.AddPolicy("ApiCorsPolicy", builder =>
{
builder.WithOrigins("http://localhost:4200").AllowAnyMethod().AllowAnyHeader();
}));
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
在configure方法中告诉CORS使用刚刚创建的策略:
app.UseCors("ApiCorsPolicy");
app.UseMvc();
我刚刚发现这篇关于这个主题的紧凑文章 - https://dzone.com/articles/cors-in-net-core-net-core-security-part-vi
我创建了自己的中间件类,对我有用,我认为.net核心中间件类有问题
public class CorsMiddleware
{
private readonly RequestDelegate _next;
public CorsMiddleware(RequestDelegate next)
{
_next = next;
}
public Task Invoke(HttpContext httpContext)
{
httpContext.Response.Headers.Add("Access-Control-Allow-Origin", "*");
httpContext.Response.Headers.Add("Access-Control-Allow-Credentials", "true");
httpContext.Response.Headers.Add("Access-Control-Allow-Headers", "Content-Type, X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Date, X-Api-Version, X-File-Name");
httpContext.Response.Headers.Add("Access-Control-Allow-Methods", "POST,GET,PUT,PATCH,DELETE,OPTIONS");
return _next(httpContext);
}
}
// Extension method used to add the middleware to the HTTP request pipeline.
public static class CorsMiddlewareExtensions
{
public static IApplicationBuilder UseCorsMiddleware(this IApplicationBuilder builder)
{
return builder.UseMiddleware<CorsMiddleware>();
}
}
并在startup.cs中以这种方式使用它
app.UseCorsMiddleware();
在我的情况下,根据MindingData的回答,只有get
请求可以正常工作。对于其他类型的请求,您需要编写:
app.UseCors(corsPolicyBuilder =>
corsPolicyBuilder.WithOrigins("http://localhost:3000")
.AllowAnyMethod()
.AllowAnyHeader()
);
别忘了添加.AllowAnyHeader()
为了扩展user8266077的answer,我发现在我的用例中,我仍然需要在.NET Core 2.1预览版中为preflight requests提供OPTIONS响应:
// https://stackoverflow.com/a/45844400
public class CorsMiddleware
{
private readonly RequestDelegate _next;
public CorsMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
context.Response.Headers.Add("Access-Control-Allow-Origin", "*");
context.Response.Headers.Add("Access-Control-Allow-Credentials", "true");
// Added "Accept-Encoding" to this list
context.Response.Headers.Add("Access-Control-Allow-Headers", "Content-Type, X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Accept-Encoding, Content-Length, Content-MD5, Date, X-Api-Version, X-File-Name");
context.Response.Headers.Add("Access-Control-Allow-Methods", "POST,GET,PUT,PATCH,DELETE,OPTIONS");
// New Code Starts here
if (context.Request.Method == "OPTIONS")
{
context.Response.StatusCode = (int)HttpStatusCode.OK;
await context.Response.WriteAsync(string.Empty);
}
// New Code Ends here
await _next(context);
}
}
然后在Startup.cs中启用类似的中间件
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseMiddleware(typeof(CorsMiddleware));
// ... other middleware inclusion such as ErrorHandling, Caching, etc
app.UseMvc();
}
上述程序都没有帮助,然后我阅读article解决了这个问题。
下面是代码。
public void ConfigureServices(IServiceCollection services)
{
// Add service and create Policy with options
services.AddCors(options =>
{
options.AddPolicy("CorsPolicy",
builder => builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials() );
});
services.AddMvc();
}
和
public void Configure(IApplicationBuilder app)
{
// ...
// global policy - assign here or on each controller
app.UseCors("CorsPolicy");
并在我的行动方法的顶部
[EnableCors("CorsPolicy")]
尝试在Ajax调用之前添加jQuery.support.cors = true;
也可能是您发送给API的数据不稳定,
尝试添加以下JSON函数
var JSON = JSON || {};
// implement JSON.stringify serialization
JSON.stringify = JSON.stringify || function (obj) {
var t = typeof (obj);
if (t != "object" || obj === null) {
// simple data type
if (t == "string") obj = '"' + obj + '"';
return String(obj);
}
else {
// recurse array or object
var n, v, json = [], arr = (obj && obj.constructor == Array);
for (n in obj) {
v = obj[n]; t = typeof (v);
if (t == "string") v = '"' + v + '"';
else if (t == "object" && v !== null) v = JSON.stringify(v);
json.push((arr ? "" : '"' + n + '":') + String(v));
}
return (arr ? "[" : "{") + String(json) + (arr ? "]" : "}");
}
};
// implement JSON.parse de-serialization
JSON.parse = JSON.parse || function (str) {
if (str === "") str = '""';
eval("var p=" + str + ";");
return p;
};
然后在你的数据中:对象将其更改为
data: JSON.stringify({
username: username,
password: password
}),
根据您在MindingData的答案中的评论,它与您的CORS无关,它工作正常。
您的Controller操作返回错误的数据。 HttpCode 415表示“不支持的媒体类型”。当您将错误的格式传递给控制器(即XML到只接受json的控制器)或者返回错误的类型(在声明仅返回xml的控制器中返回Xml)时会发生这种情况。
稍后检查你的行动是否存在[Produces("...")]
attribute
我想如果你使用自己的CORS中间件,你需要通过检查origin头来确保它真的是CORS请求。
public class CorsMiddleware
{
private readonly RequestDelegate _next;
private readonly IMemoryCache _cache;
private readonly ILogger<CorsMiddleware> _logger;
public CorsMiddleware(RequestDelegate next, IMemoryCache cache, ILogger<CorsMiddleware> logger)
{
_next = next;
_cache = cache;
_logger = logger;
}
public async Task InvokeAsync(HttpContext context, IAdministrationApi adminApi)
{
if (context.Request.Headers.ContainsKey(CorsConstants.Origin) || context.Request.Headers.ContainsKey("origin"))
{
if (!context.Request.Headers.TryGetValue(CorsConstants.Origin, out var origin))
{
context.Request.Headers.TryGetValue("origin", out origin);
}
bool isAllowed;
// Getting origin from DB to check with one from request and save it in cache
var result = _cache.GetOrCreateAsync(origin, async cacheEntry => await adminApi.DoesExistAsync(origin));
isAllowed = result.Result.Result;
if (isAllowed)
{
context.Response.Headers.Add(CorsConstants.AccessControlAllowOrigin, origin);
context.Response.Headers.Add(
CorsConstants.AccessControlAllowHeaders,
$"{HeaderNames.Authorization}, {HeaderNames.ContentType}, {HeaderNames.AcceptLanguage}, {HeaderNames.Accept}");
context.Response.Headers.Add(CorsConstants.AccessControlAllowMethods, "POST, GET, PUT, PATCH, DELETE, OPTIONS");
if (context.Request.Method == "OPTIONS")
{
_logger.LogInformation("CORS with origin {Origin} was handled successfully", origin);
context.Response.StatusCode = (int)HttpStatusCode.NoContent;
return;
}
await _next(context);
}
else
{
if (context.Request.Method == "OPTIONS")
{
_logger.LogInformation("Preflight CORS request with origin {Origin} was declined", origin);
context.Response.StatusCode = (int)HttpStatusCode.NoContent;
return;
}
_logger.LogInformation("Simple CORS request with origin {Origin} was declined", origin);
context.Response.StatusCode = (int)HttpStatusCode.Forbidden;
return;
}
}
await _next(context);
}