2016-04-26 15 views
8

Ho impostato correttamente Cors in un'applicazione web ASP.NET Core. Im utilizzando il seguente pacchetto ...Configurare cors per consentire tutti i sottodomini utilizzando ASP.NET Core (Asp.net 5, MVC6, VNext)

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

e qui è la 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(); 
       } 
      ); 
     } 
    ); 
} 

Questa grande opera, tranne che l'elenco dei sottodomini per permettere è in rapida crescita e voglio per consentire tutti i sottodomini di "somewhere.com". Qualcosa come "* .somewhere.com". Non riesco a trovare alcuna documentazione su come farlo nel nuovo ASP.NET Core (MVC6, ASP.NET5, VNext). Tutti i documenti/esempi che sto riscontrando dimostrano come farlo sono le versioni precedenti di MVC o WebApi. Come posso ottenere questo nel nuovo stack?

risposta

9

ho submitted a pull request alla squadra ASP.NET con questo cambiamento quindi speriamo che renderà nel pacchetto NuGet. Fino ad allora, io uso questa soluzione alternativa.

Di seguito si registra cors come al solito con l'eccezione di dover registrare la classe WildCardCorsService nel contenitore.

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

Salva questa classe localmente nella soluzione. È una copia e modifica della classe Microsoft.AspNet.Cors.CorsService.cs per consentirgli di gestire sottodomini con caratteri jolly. Se trova un carattere jolly '*' controllerà se il dominio radice corrisponde alle origini consentite e all'origine effettiva. NON supporta la corrispondenza dei caratteri jolly parziale.

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

Divertiti!

+0

Non dovrebbe essere registrato come un singleton, piuttosto che registrare un transitorio che creerà una nuova istanza per ogni richiesta? – michaelmsm89

+0

Dove lo aggiungo? – Si8

+0

Questa è una bella chiavetta – Nexxas

1

L'originale CorsService utilizza policy.Origins.Contains(origin) per valutare una richiesta. Quindi, non sembra che ci sia un modo banale per fare quello che ti serve, perché lo List deve contenere l'origine. È possibile implementare il proprio ICorsService, ereditare ciò che già fornisce la versione CorsService e modificare i metodi per gestire il carattere jolly *.mydomain.com.

Modifica Ecco cosa ho realizzato utilizzando yo aspnet per generare un progetto Api Web 1.0.0-rc1-update2. Funziona. Pubblica il tuo servizio nel Startup.cs (vedi CorsServiceCollectionExtensions per maggiori dettagli.)

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

Ecco il servizio, in attesa dell'implementazione. Puoi copiare/incollare o ereditare da 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

Speravo che ci fosse qualcosa che non era così pratico. Se dovessi seguire l'approccio di cui sopra, quale sarebbe il modo migliore per iniettare/utilizzare quell'implementazione anziché quella attuale? Sto ancora cercando di comprendere da vicino tutti i possibili approcci di DI che Asp.net Core offre. Grazie! – sjdirect

+0

@sjdirect Sei disposto a utilizzare RC2? –

+1

Shaun, in questo momento non sarei entusiasta dell'aggiornamento, ma mi piacerebbe sentire che sollievo RC2 porta in relazione a questo problema. – sjdirect

Problemi correlati