2016-04-26 16 views
8

Mam poprawnie ustawione korki w aplikacji internetowej ASP.NET Core. Im stosując następujący pakiet ...Skonfiguruj cors tak, aby zezwalać na wszystkie poddomeny za pomocą ASP.NET Core (Asp.net 5, MVC6, VNext).

"Microsoft.AspNet.Cors": "6.0.0-rc1-final" 

A oto startup.cs snippet ...

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(); 
       } 
      ); 
     } 
    ); 
} 

Działa to doskonale z wyjątkiem, że lista subdomen, aby umożliwić szybko rośnie i chcę aby zezwolić na wszystkie subdomeny "somewhere.com". Coś jak "* .somewhere.com". Nie mogę znaleźć żadnej dokumentacji, jak to zrobić w nowym środowisku ASP.NET Core (MVC6, ASP.NET5, VNext). Wszystkie dokumenty/przykłady, które znajduję, które demonstrują, jak to zrobić, dotyczą wcześniejszych wersji MVC lub WebApi. Jak mogę to osiągnąć w nowym stosie?

Odpowiedz

9

I submitted a pull request do zespołu ASP.NET z tej zmiany, więc miejmy nadzieję pozwoli on na opakowaniu Nuget. Do tego czasu używam tego obejścia.

Poniżej rejestruje się cors jak zwykle, z wyjątkiem konieczności rejestracji klasy WildCardCorsService w pojemniku di.

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(); 
       } 
      ); 
     } 
    ); 
} 

Zapisz tę klasę lokalnie w swoim rozwiązaniu. Jest to kopia i edycja klasy Microsoft.AspNet.Cors.CorsService.cs, aby umożliwić jej obsługę subdomen z symbolami wieloznacznymi. Jeśli znajdzie symbol wieloznaczny "*", sprawdzi, czy domena główna pasuje do dozwolonych początków i rzeczywistego pochodzenia. NIE obsługuje częściowego dopasowywania symboli wieloznacznych.

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" 
     }; 
    } 
} 

Ciesz się!

+0

Czy nie należy rejestrować jako singleton zamiast rejestrować przejściowy, który utworzy nowe wystąpienie dla każdego żądania? – michaelmsm89

+0

Gdzie mogę to dodać? – Si8

+0

To całkiem zgrabne – Nexxas

1

Po wyjęciu z pudełka CorsService używa policy.Origins.Contains(origin) do oceny żądania. Wygląda na to, że nie ma trywialnego sposobu zrobienia tego, czego potrzebujesz, ponieważ List musi zawierać pochodzenie. Możesz zaimplementować swój własny ICorsService, dziedziczyć to, co dostarcza już gotowy do użycia, i dostosować metody obsługi wieloznacznika *.mydomain.com.

Edit Oto co osiągnąłem stosując yo aspnet wygenerować projekt Api 1.0.0-rc1-update2 internetową. To działa. Zarejestruj swoją usługę w Startup.cs (patrz CorsServiceCollectionExtensions szczegóły.)

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)}"); 
     }); 
    } 
} 

Oto serwis, w oczekiwaniu na swoją realizację. Możesz skopiować/wkleić lub dziedziczyć po 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); 
    } 
} 
+1

Miałem nadzieję, że było coś, co nie było tak silne. Gdybym miał przyjąć powyższe podejście, jaki byłby najlepszy sposób wprowadzenia/użycia tej implementacji zamiast obecnej? Wciąż próbuję objąć wszystkie możliwe podejścia DI, które zapewnia Asp.net Core. Dzięki! – sjdirect

+0

@ sjdirect Czy chcesz używać RC2? –

+1

Shaun, w tej chwili nie byłbym podekscytowany modernizacją, ale chciałbym usłyszeć, co przynosi ulga RC2 w związku z tym problemem. – sjdirect

Powiązane problemy