我在 ASP.NET Core Web 应用程序中正确设置了 CORS。我正在使用以下软件包...
"Microsoft.AspNet.Cors": "6.0.0-rc1-final"
这是startup.cs片段...
public virtual IServiceProvider ConfigureServices(IServiceCollection services)
{
services.AddCors
(
options =>
{
options.AddPolicy
(
CORSDefaults.PolicyName,
builder =>
{
//From config...
var allowedDomains = new []{"http://aaa.somewhere.com","https://aaa.somewhere.com","http://bbb.somewhere.com","https://bbb.somewhere.com"};
//Load it
builder
.WithOrigins(allowedDomains)
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials();
}
);
}
);
}
这非常有效,只是允许的子域列表增长得很快,而且我想允许“somewhere.com”的所有子域。类似于“*.somewhere.com”。我似乎找不到任何有关如何在新的 ASP.NET Core(MVC6、ASP.NET5、VNext)中执行此操作的文档。我发现的所有演示如何执行此操作的文档/示例均适用于早期版本的 MVC 或 WebApi。我如何在新堆栈中实现这一目标?
此功能现已在 2.0.0 版本中实现。在您的
ConfigureServices
中使用以下内容:
options.AddPolicy("MyCorsPolicy",
builder => builder
.SetIsOriginAllowedToAllowWildcardSubdomains()
.WithOrigins("https://*.mydomain.com")
.AllowAnyMethod()
.AllowCredentials()
.AllowAnyHeader()
.Build()
);
另外,也不要忘记在您的
Configure
调用中调用 UseCors:
app.UseCors("MyCorsPolicy");
我向 ASP.NET 团队提交了一个拉取请求并进行了此更改,因此希望它能够进入 nuget 包中。在那之前,我使用这个解决方法。
下面您可以照常注册 cors,但必须在 di 容器中注册 WildCardCorsService 类。
public virtual IServiceProvider ConfigureServices(IServiceCollection services)
{
services.TryAdd(ServiceDescriptor.Transient<ICorsService, WildCardCorsService>());
services.AddCors
(
options =>
{
options.AddPolicy
(
CORSDefaults.PolicyName,
builder =>
{
builder
.WithOrigins("http://*.withwildcardsubdomain.com", "http://nowildcard.com")
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials();
}
);
}
);
}
将该类保存在本地解决方案中。它是 Microsoft.AspNet.Cors.CorsService.cs 类的副本和编辑,以允许其处理通配符子域。如果它找到通配符“*”,它将检查根域是否与允许的来源和实际来源匹配。它不支持部分通配符匹配。
namespace Microsoft.AspNet.Cors.Infrastructure
{
/// <summary>
/// This ICorsService should be used in place of the official default CorsService to support origins
/// like http://*.example.comwhich will allow any subdomain for example.com
/// </summary>
public class WildCardCorsService : ICorsService
{
private readonly CorsOptions _options;
/// <summary>
/// Creates a new instance of the <see cref="CorsService"/>.
/// </summary>
/// <param name="options">The option model representing <see cref="CorsOptions"/>.</param>
public WildCardCorsService(IOptions<CorsOptions> options)
{
if (options == null)
{
throw new ArgumentNullException(nameof(options));
}
_options = options.Value;
}
/// <summary>
/// Looks up a policy using the <paramref name="policyName"/> and then evaluates the policy using the passed in
/// <paramref name="context"/>.
/// </summary>
/// <param name="requestContext"></param>
/// <param name="policyName"></param>
/// <returns>A <see cref="CorsResult"/> which contains the result of policy evaluation and can be
/// used by the caller to set appropriate response headers.</returns>
public CorsResult EvaluatePolicy(HttpContext context, string policyName)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
var policy = _options.GetPolicy(policyName);
return EvaluatePolicy(context, policy);
}
/// <inheritdoc />
public CorsResult EvaluatePolicy(HttpContext context, CorsPolicy policy)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (policy == null)
{
throw new ArgumentNullException(nameof(policy));
}
var corsResult = new CorsResult();
var accessControlRequestMethod = context.Request.Headers[Microsoft.AspNet.Cors.Infrastructure.CorsConstants.AccessControlRequestMethod];
if (string.Equals(context.Request.Method, Microsoft.AspNet.Cors.Infrastructure.CorsConstants.PreflightHttpMethod, StringComparison.Ordinal) &&
!StringValues.IsNullOrEmpty(accessControlRequestMethod))
{
EvaluatePreflightRequest(context, policy, corsResult);
}
else
{
EvaluateRequest(context, policy, corsResult);
}
return corsResult;
}
public virtual void EvaluateRequest(HttpContext context, CorsPolicy policy, CorsResult result)
{
var origin = context.Request.Headers[Microsoft.AspNet.Cors.Infrastructure.CorsConstants.Origin];
if (!OriginIsAllowed(origin, policy))
{
return;
}
AddOriginToResult(origin, policy, result);
result.SupportsCredentials = policy.SupportsCredentials;
AddHeaderValues(result.AllowedExposedHeaders, policy.ExposedHeaders);
}
public virtual void EvaluatePreflightRequest(HttpContext context, CorsPolicy policy, CorsResult result)
{
var origin = context.Request.Headers[Microsoft.AspNet.Cors.Infrastructure.CorsConstants.Origin];
if (!OriginIsAllowed(origin, policy))
{
return;
}
var accessControlRequestMethod = context.Request.Headers[Microsoft.AspNet.Cors.Infrastructure.CorsConstants.AccessControlRequestMethod];
if (StringValues.IsNullOrEmpty(accessControlRequestMethod))
{
return;
}
var requestHeaders =
context.Request.Headers.GetCommaSeparatedValues(Microsoft.AspNet.Cors.Infrastructure.CorsConstants.AccessControlRequestHeaders);
if (!policy.AllowAnyMethod && !policy.Methods.Contains(accessControlRequestMethod))
{
return;
}
if (!policy.AllowAnyHeader &&
requestHeaders != null &&
!requestHeaders.All(header => Microsoft.AspNet.Cors.Infrastructure.CorsConstants.SimpleRequestHeaders.Contains(header, StringComparer.OrdinalIgnoreCase) ||
policy.Headers.Contains(header, StringComparer.OrdinalIgnoreCase)))
{
return;
}
AddOriginToResult(origin, policy, result);
result.SupportsCredentials = policy.SupportsCredentials;
result.PreflightMaxAge = policy.PreflightMaxAge;
result.AllowedMethods.Add(accessControlRequestMethod);
AddHeaderValues(result.AllowedHeaders, requestHeaders);
}
/// <inheritdoc />
public virtual void ApplyResult(CorsResult result, HttpResponse response)
{
if (result == null)
{
throw new ArgumentNullException(nameof(result));
}
if (response == null)
{
throw new ArgumentNullException(nameof(response));
}
var headers = response.Headers;
if (result.AllowedOrigin != null)
{
headers[Microsoft.AspNet.Cors.Infrastructure.CorsConstants.AccessControlAllowOrigin] = result.AllowedOrigin;
}
if (result.VaryByOrigin)
{
headers["Vary"] = "Origin";
}
if (result.SupportsCredentials)
{
headers[Microsoft.AspNet.Cors.Infrastructure.CorsConstants.AccessControlAllowCredentials] = "true";
}
if (result.AllowedMethods.Count > 0)
{
// Filter out simple methods
var nonSimpleAllowMethods = result.AllowedMethods
.Where(m =>
!Microsoft.AspNet.Cors.Infrastructure.CorsConstants.SimpleMethods.Contains(m, StringComparer.OrdinalIgnoreCase))
.ToArray();
if (nonSimpleAllowMethods.Length > 0)
{
headers.SetCommaSeparatedValues(
Microsoft.AspNet.Cors.Infrastructure.CorsConstants.AccessControlAllowMethods,
nonSimpleAllowMethods);
}
}
if (result.AllowedHeaders.Count > 0)
{
// Filter out simple request headers
var nonSimpleAllowRequestHeaders = result.AllowedHeaders
.Where(header =>
!Microsoft.AspNet.Cors.Infrastructure.CorsConstants.SimpleRequestHeaders.Contains(header, StringComparer.OrdinalIgnoreCase))
.ToArray();
if (nonSimpleAllowRequestHeaders.Length > 0)
{
headers.SetCommaSeparatedValues(
Microsoft.AspNet.Cors.Infrastructure.CorsConstants.AccessControlAllowHeaders,
nonSimpleAllowRequestHeaders);
}
}
if (result.AllowedExposedHeaders.Count > 0)
{
// Filter out simple response headers
var nonSimpleAllowResponseHeaders = result.AllowedExposedHeaders
.Where(header =>
!Microsoft.AspNet.Cors.Infrastructure.CorsConstants.SimpleResponseHeaders.Contains(header, StringComparer.OrdinalIgnoreCase))
.ToArray();
if (nonSimpleAllowResponseHeaders.Length > 0)
{
headers.SetCommaSeparatedValues(
Microsoft.AspNet.Cors.Infrastructure.CorsConstants.AccessControlExposeHeaders,
nonSimpleAllowResponseHeaders);
}
}
if (result.PreflightMaxAge.HasValue)
{
headers[Microsoft.AspNet.Cors.Infrastructure.CorsConstants.AccessControlMaxAge]
= result.PreflightMaxAge.Value.TotalSeconds.ToString(CultureInfo.InvariantCulture);
}
}
protected virtual bool OriginIsAllowed(string origin, CorsPolicy policy)
{
if (!string.IsNullOrWhiteSpace(origin) &&
(policy.AllowAnyOrigin ||
policy.Origins.Contains(origin) ||
IsWildCardSubdomainMatch(origin, policy)))
return true;
return false;
}
private void AddOriginToResult(string origin, CorsPolicy policy, CorsResult result)
{
if (policy.AllowAnyOrigin)
{
if (policy.SupportsCredentials)
{
result.AllowedOrigin = origin;
result.VaryByOrigin = true;
}
else
{
result.AllowedOrigin = Microsoft.AspNet.Cors.Infrastructure.CorsConstants.AnyOrigin;
}
}
else
{
result.AllowedOrigin = origin;
}
}
private static void AddHeaderValues(IList<string> target, IEnumerable<string> headerValues)
{
if (headerValues == null)
{
return;
}
foreach (var current in headerValues)
{
target.Add(current);
}
}
private bool IsWildCardSubdomainMatch(string origin, CorsPolicy policy)
{
var actualOriginUri = new Uri(origin);
var actualOriginRootDomain = GetRootDomain(actualOriginUri);
foreach (var o in policy.Origins)
{
if (!o.Contains("*"))
continue;
// 1) CANNOT USE System.Text.RegularExpression since it does not exist in .net platform 5.4 (which the Microsoft.AspNet.Cors project.json targets)
// 2) '*' char is not valid for creation of a URI object so we replace it just for this comparison
var allowedOriginUri = new Uri(o.Replace("*", "SOMELETTERS"));
if (allowedOriginUri.Scheme == actualOriginUri.Scheme &&
actualOriginRootDomain == GetRootDomain(allowedOriginUri))
return true;
}
return false;
}
private string GetRootDomain(Uri uri)
{
//Got this snippet here http://stackoverflow.com/questions/16473838/get-domain-name-of-a-url-in-c-sharp-net
var host = uri.Host;
int index = host.LastIndexOf('.'), last = 3;
while (index > 0 && index >= last - 3)
{
last = index;
index = host.LastIndexOf('.', last - 1);
}
return host.Substring(index + 1);
}
}
/// <summary>
/// Needed to copy these in since some of them are internal to the Microsoft.AspNet.Cors project
/// </summary>
public static class CorsConstants
{
/// <summary>The HTTP method for the CORS preflight request.</summary>
public static readonly string PreflightHttpMethod = "OPTIONS";
/// <summary>The Origin request header.</summary>
public static readonly string Origin = "Origin";
/// <summary>
/// The value for the Access-Control-Allow-Origin response header to allow all origins.
/// </summary>
public static readonly string AnyOrigin = "*";
/// <summary>The Access-Control-Request-Method request header.</summary>
public static readonly string AccessControlRequestMethod = "Access-Control-Request-Method";
/// <summary>The Access-Control-Request-Headers request header.</summary>
public static readonly string AccessControlRequestHeaders = "Access-Control-Request-Headers";
/// <summary>The Access-Control-Allow-Origin response header.</summary>
public static readonly string AccessControlAllowOrigin = "Access-Control-Allow-Origin";
/// <summary>The Access-Control-Allow-Headers response header.</summary>
public static readonly string AccessControlAllowHeaders = "Access-Control-Allow-Headers";
/// <summary>The Access-Control-Expose-Headers response header.</summary>
public static readonly string AccessControlExposeHeaders = "Access-Control-Expose-Headers";
/// <summary>The Access-Control-Allow-Methods response header.</summary>
public static readonly string AccessControlAllowMethods = "Access-Control-Allow-Methods";
/// <summary>The Access-Control-Allow-Credentials response header.</summary>
public static readonly string AccessControlAllowCredentials = "Access-Control-Allow-Credentials";
/// <summary>The Access-Control-Max-Age response header.</summary>
public static readonly string AccessControlMaxAge = "Access-Control-Max-Age";
internal static readonly string[] SimpleRequestHeaders = new string[4]
{
"Origin",
"Accept",
"Accept-Language",
"Content-Language"
};
internal static readonly string[] SimpleResponseHeaders = new string[6]
{
"Cache-Control",
"Content-Language",
"Content-Type",
"Expires",
"Last-Modified",
"Pragma"
};
internal static readonly string[] SimpleMethods = new string[3]
{
"GET",
"HEAD",
"POST"
};
}
}
享受吧!
CorsService
使用 policy.Origins.Contains(origin)
来评估请求。因此,看起来没有一种简单的方法可以满足您的要求,因为 List
必须包含原点。您可以实现自己的 ICorsService
,继承开箱即用的 CorsService
已经提供的内容,并调整方法来处理 *.mydomain.com
通配符。
编辑 这是我使用
yo aspnet
生成 1.0.0-rc1-update2
Web Api 项目所完成的工作。有用。在 Startup.cs 中注册您的服务(有关详细信息,请参阅 CorsServiceCollectionExtensions
。)
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddOptions();
services.TryAdd(
ServiceDescriptor.Transient<ICorsService, MyCorsService>());
services.TryAdd(
ServiceDescriptor.Transient<ICorsPolicyProvider, DefaultCorsPolicyProvider>());
}
public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(minLevel: LogLevel.Verbose);
app.UseCors(corsPolictyBuilder =>
{
corsPolictyBuilder.WithOrigins("*.mydomain.com");
});
app.Run(async context =>
{
await context.Response.WriteAsync(
$"Is Cors? {context.Request.Headers.ContainsKey(CorsConstants.Origin)}");
});
}
}
CorsService
。
public class MyCorsService : CorsService, ICorsService
{
private ILogger _logger;
public MyCorsService(IOptions<CorsOptions> options, ILogger<MyCorsService> logger)
: base(options)
{
_logger = logger;
_logger.LogInformation("MyCorsService");
}
public override void ApplyResult(
CorsResult result, HttpResponse response)
{
_logger.LogInformation("ApplyResult");
base.ApplyResult(result, response);
}
public override void EvaluateRequest(
HttpContext context, CorsPolicy policy, CorsResult result)
{
_logger.LogInformation("EvaluateRequest");
base.EvaluateRequest(context, policy, result);
}
public override void EvaluatePreflightRequest(
HttpContext context, CorsPolicy policy, CorsResult result)
{
_logger.LogInformation("EvaluatePreflightRequest");
base.EvaluatePreflightRequest(context, policy, result);
}
}
当在子域的第一部分指定通配符时,SetIsOriginAllowedToAllowWildcardSubdomains 函数可以很好地工作,例如
https:\\*.modules.features.releaseversion.companyname.com
但是当在子域的任何其他部分指定通配符时,例如,相同的函数不会给出所需的结果。 https:\\environment.modules.*.releaseversion.companyname.com
或 https:\\*.modules.*.releaseversion.companyname.com
或 https:\\environment.*.*.releaseversion.companyname.com
下面的代码灵感来自 @Shaun Luttin 和 @sjdirect 代码片段
我们只是想扩展 Microsoft.AspNetCore.Cors.Infrastruct.CorsService 类的行为,以启用在 URL 中任意位置指定的通配符字符
下面的类执行 CORS 检查以允许通配符子域。将此类本地复制到所需的项目中。它是 Microsoft.AspNetCore.Cors.Infrastruct.CorsService 的扩展版本,可为子域启用通配符支持。
using Microsoft.AspNetCore.Cors.Infrastructure;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Primitives;
using System;
using System.Text.RegularExpressions;
namespace Microsoft.AspNetCore.Cors.Infrastructure
{
public class CORSServiceWildCardSupport : CorsService, ICorsService
{
private readonly CorsOptions _options;
private readonly ILogger _logger;
public CORSServiceWildCardSupport(IOptions<CorsOptions> options, ILoggerFactory loggerFactory) : base(options, loggerFactory)
{
_options = options.Value;
_logger = loggerFactory.CreateLogger<CorsService>();
}
public new virtual CorsResult EvaluatePolicy(HttpContext context, CorsPolicy policy)
{
if (context == null)
{
throw new ArgumentNullException("context");
}
if (policy == null)
{
throw new ArgumentNullException("policy");
}
if (policy.AllowAnyOrigin && policy.SupportsCredentials)
{
throw new ArgumentException(Resource.InsecureConfiguration, "policy");
}
IHeaderDictionary headers = context.Request.Headers;
StringValues origin = headers[CorsConstants.Origin];
bool num = string.Equals(context.Request.Method, CorsConstants.PreflightHttpMethod, StringComparison.OrdinalIgnoreCase);
bool flag = num && headers.ContainsKey(CorsConstants.AccessControlRequestMethod);
CorsResult result = new CorsResult
{
IsPreflightRequest = flag,
IsOriginAllowed = IsWildCardSubdomainMatch(origin, policy)
};
if (flag)
{
EvaluatePreflightRequest(context, policy, result);
}
else
{
EvaluateRequest(context, policy, result);
}
return result;
}
protected virtual IsWildCardSubdomainMatch(string origin, CorsPolicy policy)
{
var actualOrigin = new Uri(origin);
foreach (var o in policy.Origins)
{
if (IsWildcardMatch(actualOrigin, o))
{
return true;
}
}
return false;
}
private bool IsWildcardMatch(Uri actualOrigin, string wildcardUri)
{
if (!wildcardUri.StartsWith(actualOrigin.Scheme))
{
return false;
}
var wildcardUriMinusScheme = wildcardUri.Replace(actualOrigin.Scheme + "://", "");
var regexFirstStage = wildcardUriMinusScheme.Replace(".", "\\.");
var regexAllowedHosts = "^" + regexFirstStage.Replace("*", ".*") + "$";
var actualOriginMinusScheme = actualOrigin.OriginalString.Replace(actualOrigin.Scheme + "://", "");
var isMatch = Regex.IsMatch(actualOriginMinusScheme, regexAllowedHosts);
return isMatch;
}
}
}
上面的类函数IsWildCardSubdomainMatch或IsWildcardMatch可以根据需求进行扩展,对于我们的需求我们只需要进行字符串比较即可。
使用以下扩展类将 CORSServiceWildCardSupport 类注册到依赖项容器中。将类本地复制到所需的项目中
using Microsoft.AspNetCore.Cors.Infrastructure;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using System;
using System.Collections.Generic;
namespace Microsoft.Extensions.DependencyInjection
{
public static class CORSServiceCollectionExtensions
{
public static IServiceCollection AddCORSWithWildCardSupport(this IServiceCollection services)
{
if (services == null)
{
throw new ArgumentNullException("services");
}
services.AddOptions();
services.TryAdd(ServiceDescriptor.Transient<ICorsService, CORSServiceWildCardSupport>());
services.TryAdd(ServiceDescriptor.Transient<ICorsPolicyProvider, DefaultCorsPolicyProvider>());
return services;
}
public static IServiceCollection AddCORSWithWildCardSupport(this IServiceCollection services, Action<CorsOptions> setupAction)
{
if (services == null)
{
throw new ArgumentNullException("services");
}
if (setupAction == null)
{
throw new ArgumentNullException("setupAction");
}
services.AddCORSWithWildCardSupport();
services.Configure(setupAction);
return services;
}
}
}
从 Startup 类注册 CORS
public void ConfigureServices(IServiceCollection services)
{
try
{
string[] whitelist = {"https:\\environment.modules.*.releaseversion.companyname.com","https:\\*.modules.*.releaseversion.companyname.com","https:\\environment.*.*.releaseversion.companyname.com"};
services.AddCORSWithWildCardSupport(o => o.AddPolicy(Resource.CorsPolicyName, builder =>
{
builder.WithOrigins(whitelist)
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials();
}));
services.AddControllers();
services.AddMvc(option => option.EnableEndpointRouting = false)
.SetCompatibilityVersion(CompatibilityVersion.Version_3_0);
services.AddAuthentication("Windows");
}
catch(Exception ex)
{
Logger.Error($"Failed to start due to {ex.Message}.");
}
}
services.AddControllers 方法还将 ICORSService 注册到依赖项容器中,因此始终在 AddControllers 之前使用 AddCORS
享受:)