Access-control-allow-origin con múltiples dominios

98

En mi web.config me gustaría especificar más de un dominio para la access-control-allow-origindirectiva. No quiero usar *. Probé esta sintaxis:

<add name="Access-Control-Allow-Origin" value="http://localhost:1506, http://localhost:1502" />

éste

<add name="Access-Control-Allow-Origin" value="http://localhost:1506 http://localhost:1502" />

éste

<add name="Access-Control-Allow-Origin" value="http://localhost:1506; http://localhost:1502" />

y éste

<add name="Access-Control-Allow-Origin" value="http://localhost:1506" />
<add name="Access-Control-Allow-Origin" value="http://localhost:1502" />

pero ninguno de ellos funciona. Cual es la sintaxis correcta ?

Sam
fuente

Respuestas:

78

Solo puede haber un Access-Control-Allow-Originencabezado de respuesta y ese encabezado solo puede tener un valor de origen. Por lo tanto, para que esto funcione, necesita tener un código que:

  1. Toma el Originencabezado de la solicitud.
  2. Comprueba si el valor de origen es uno de los valores de la lista blanca.
  3. Si es válido, establece el Access-Control-Allow-Originencabezado con ese valor.

No creo que haya ninguna forma de hacer esto únicamente a través de web.config.

if (ValidateRequest()) {
    Response.Headers.Remove("Access-Control-Allow-Origin");
    Response.AddHeader("Access-Control-Allow-Origin", Request.UrlReferrer.GetLeftPart(UriPartial.Authority));

    Response.Headers.Remove("Access-Control-Allow-Credentials");
    Response.AddHeader("Access-Control-Allow-Credentials", "true");

    Response.Headers.Remove("Access-Control-Allow-Methods");
    Response.AddHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
}
monsur
fuente
2
Eso responde a mi pregunta. Sin embargo, no estoy seguro de por qué Microsoft no permite especificar múltiples orígenes en web.config ...
Sam
17
¿Dónde puedo agregar este código? Tengo archivos de texto sin formato generados por el servidor y los leo a través de AJAX, sin ningún código. ¿Dónde puedo poner el código para restringir el acceso a archivos de texto en mi directorio?
Harry
3
@Simon_Weaver existe un *valor que permite que cualquier origen acceda al recurso. Sin embargo, la pregunta original era sobre la inclusión de un conjunto de dominios en la lista blanca.
monsur
2
como soy nuevo en asp .net, ¿puedo preguntar dónde puedo poner este código en mi proyecto de api web asp .net?
Amrit
93

Para IIS 7.5+ y Rewrite 2.0 puede usar:

<system.webServer>
   <httpProtocol>
     <customHeaders>
         <add name="Access-Control-Allow-Headers" value="Origin, X-Requested-With, Content-Type, Accept" />
         <add name="Access-Control-Allow-Methods" value="POST,GET,OPTIONS,PUT,DELETE" />
     </customHeaders>
   </httpProtocol>
        <rewrite>            
            <outboundRules>
                <clear />                
                <rule name="AddCrossDomainHeader">
                    <match serverVariable="RESPONSE_Access_Control_Allow_Origin" pattern=".*" />
                    <conditions logicalGrouping="MatchAll" trackAllCaptures="true">
                        <add input="{HTTP_ORIGIN}" pattern="(http(s)?://((.+\.)?domain1\.com|(.+\.)?domain2\.com|(.+\.)?domain3\.com))" />
                    </conditions>
                    <action type="Rewrite" value="{C:0}" />
                </rule>           
            </outboundRules>
        </rewrite>
 </system.webServer>

Explicando la RESPONSE_Access_Control_Allow_Originparte de la variable del servidor :
en Rewrite puede usar cualquier cadena después RESPONSE_y creará el Encabezado de respuesta usando el resto de la palabra como el nombre del encabezado (en este caso, Access-Control-Allow-Origin). La reescritura usa guiones bajos "_" en lugar de guiones "-" (la reescritura los convierte en guiones)

Explicación de la variable del servidor HTTP_ORIGIN: de
manera similar, en Rewrite puede tomar cualquier Encabezado de solicitud usando HTTP_como prefijo. Las mismas reglas con los guiones (use guiones bajos "_" en lugar de guiones "-").

Paco Zarate
fuente
¿Puede pensar en alguna razón por la que esto no funcionaría con IIS 7.5?
Phil Ricketts
Creo que debería funcionar. Especifiqué la versión IIS 8.5 porque es donde la probé.
Paco Zarate
4
@PacoZarate Buen consejo, buen consejo. Para simplificar la expresión regular y hacerla más genérica, puede usar - (http(s)?:\/\/((.+\.)?(domain1|domain2)\.(com|org|net))). De esa manera, puede agregar otros dominios con bastante facilidad y admitir varios dominios de nivel superior (por ejemplo, com, org, net, etc.).
Merlín
4
Intenté esto en IIS 7.5. Parece estar funcionando bien.
Profético
2
¿Tiene problemas con el almacenamiento en caché? Después de ajustar web.config, el primer sitio web al que voy coincide bien, pero el segundo devuelve el mismo encabezado que el primero. Por lo tanto, los dominios no coinciden demasiado.
Airn5475
20

En Web.API, este atributo se puede agregar usando Microsoft.AspNet.WebApi.Corscomo se detalla en http://www.asp.net/web-api/overview/security/enabling-cross-origin-requests-in-web-api

En MVC , puede crear un atributo de filtro para hacer este trabajo por usted:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method,
                AllowMultiple = true, Inherited = true)]
public class EnableCorsAttribute : FilterAttribute, IActionFilter {
    private const string IncomingOriginHeader = "Origin";
    private const string OutgoingOriginHeader = "Access-Control-Allow-Origin";
    private const string OutgoingMethodsHeader = "Access-Control-Allow-Methods";
    private const string OutgoingAgeHeader = "Access-Control-Max-Age";

    public void OnActionExecuted(ActionExecutedContext filterContext) {
        // Do nothing
    }

    public void OnActionExecuting(ActionExecutingContext filterContext)
    {
        var isLocal = filterContext.HttpContext.Request.IsLocal;
        var originHeader = 
             filterContext.HttpContext.Request.Headers.Get(IncomingOriginHeader);
        var response = filterContext.HttpContext.Response;

        if (!String.IsNullOrWhiteSpace(originHeader) &&
            (isLocal || IsAllowedOrigin(originHeader))) {
            response.AddHeader(OutgoingOriginHeader, originHeader);
            response.AddHeader(OutgoingMethodsHeader, "GET,POST,OPTIONS");
            response.AddHeader(OutgoingAgeHeader, "3600");
        }
    }

    protected bool IsAllowedOrigin(string origin) {
        // ** replace with your own logic to check the origin header
        return true;
    }
}

Luego, habilítelo para acciones / controladores específicos:

[EnableCors]
public class SecurityController : Controller {
    // *snip*
    [EnableCors]
    public ActionResult SignIn(Guid key, string email, string password) {

O agréguelo para todos los controladores en Global.asax.cs

protected void Application_Start() {
    // *Snip* any existing code

    // Register global filter
    GlobalFilters.Filters.Add(new EnableCorsAttribute());
    RegisterGlobalFilters(GlobalFilters.Filters);

    // *snip* existing code
}
Iglesia de Rob
fuente
¿Sabes para qué versiones de .Net / MVC funciona esto?
Keab42
Estoy usando esto con éxito en .net 4 / MVC 3; hasta donde yo sé, debería funcionar en versiones superiores, pero puede haber una forma preferida de registrar el filtro global en versiones posteriores de MVC.
Rob Church
solo tenga en cuenta su solución WEB API 2 únicamente. no para WEB API 1.
Samih A
5

Después de leer todas las respuestas y probarlas, ninguna me ayudó. Lo que encontré mientras buscaba en otro lugar es que puede crear un atributo personalizado que luego puede agregar a su controlador. Sobrescribe los de EnableCors y agrega los dominios incluidos en la lista blanca.

Esta solución está funcionando bien porque le permite tener los dominios en la lista blanca en el webconfig (appsettings) en lugar de codificarlos en el atributo EnableCors en su controlador.

 [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)]
public class EnableCorsByAppSettingAttribute : Attribute, ICorsPolicyProvider
{
    const string defaultKey = "whiteListDomainCors";
    private readonly string rawOrigins;
    private CorsPolicy corsPolicy;

    /// <summary>
    /// By default uses "cors:AllowedOrigins" AppSetting key
    /// </summary>
    public EnableCorsByAppSettingAttribute()
        : this(defaultKey) // Use default AppSetting key
    {
    }

    /// <summary>
    /// Enables Cross Origin
    /// </summary>
    /// <param name="appSettingKey">AppSetting key that defines valid origins</param>
    public EnableCorsByAppSettingAttribute(string appSettingKey)
    {
        // Collect comma separated origins
        this.rawOrigins = AppSettings.whiteListDomainCors;
        this.BuildCorsPolicy();
    }

    /// <summary>
    /// Build Cors policy
    /// </summary>
    private void BuildCorsPolicy()
    {
        bool allowAnyHeader = String.IsNullOrEmpty(this.Headers) || this.Headers == "*";
        bool allowAnyMethod = String.IsNullOrEmpty(this.Methods) || this.Methods == "*";

        this.corsPolicy = new CorsPolicy
        {
            AllowAnyHeader = allowAnyHeader,
            AllowAnyMethod = allowAnyMethod,
        };

        // Add origins from app setting value
        this.corsPolicy.Origins.AddCommaSeperatedValues(this.rawOrigins);
        this.corsPolicy.Headers.AddCommaSeperatedValues(this.Headers);
        this.corsPolicy.Methods.AddCommaSeperatedValues(this.Methods);
    }

    public string Headers { get; set; }
    public string Methods { get; set; }

    public Task<CorsPolicy> GetCorsPolicyAsync(HttpRequestMessage request,
                                               CancellationToken cancellationToken)
    {
        return Task.FromResult(this.corsPolicy);
    }
}

    internal static class CollectionExtensions
{
    public static void AddCommaSeperatedValues(this ICollection<string> current, string raw)
    {
        if (current == null)
        {
            return;
        }

        var paths = new List<string>(AppSettings.whiteListDomainCors.Split(new char[] { ',' }));
        foreach (var value in paths)
        {
            current.Add(value);
        }
    }
}

Encontré esta guía en línea y funcionó a las mil maravillas:

http://jnye.co/Posts/2032/dynamic-cors-origins-from-appsettings-using-web-api-2-2-cross-origin-support

Pensé en dejar eso aquí para cualquiera que lo necesite.

Helpha
fuente
Esta es una respuesta de solo enlace. Por favor, haga que la respuesta se mantenga por sí sola.
Reincorporación a Monica
1
Ok, soy nuevo aquí, ¿se parece más a lo que se supone que es?
Helpha
3

Logré resolver esto en el código de manejo de solicitudes siguiendo los consejos de 'monsur'.

string origin = WebOperationContext.Current.IncomingRequest.Headers.Get("Origin");

WebOperationContext.Current.OutgoingResponse.Headers.Add("Access-Control-Allow-Origin", origin);
bsandhu
fuente
Esa es la forma de hacerlo en formulario web, por ejemplo. Simplemente use Request.Headers cuando esté disponible. Y, si es necesario, use una lista blanca para filtrar solo los dominios permitidos.
AFract
3
Esto es tan bueno como agregar <add name = "Access-Control-Allow-Origin" value = "*" /> en el archivo web.config
Isaiah4110
3

Para IIS 7.5+, puede utilizar el módulo IIS CORS: https://www.iis.net/downloads/microsoft/iis-cors-module

Tu web.config debería ser algo como esto:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <system.webServer>
        <cors enabled="true" failUnlistedOrigins="true">
            <add origin="http://localhost:1506">
                <allowMethods>                    
                    <add method="GET" />
                    <add method="HEAD" />
                    <add method="POST" />
                    <add method="PUT" /> 
                    <add method="DELETE" /> 
                </allowMethods>
            </add>
            <add origin="http://localhost:1502">
                <allowMethods>
                    <add method="GET" />
                    <add method="HEAD" />
                    <add method="POST" />
                    <add method="PUT" /> 
                    <add method="DELETE" /> 
                </allowMethods>
            </add>
        </cors>
    </system.webServer>
</configuration>

Puede encontrar la referencia de configuración aquí: https://docs.microsoft.com/en-us/iis/extensions/cors-module/cors-module-configuration-reference

Mario Arturo
fuente
Si esto funciona como dice, ¡me gustaría que lo hubieras publicado hace 3 años! ¡Guau!
Michael
1

Puede agregar este código a su proyecto asp.net webapi

en el archivo Global.asax

    protected void Application_BeginRequest()
{
    string origin = Request.Headers.Get("Origin");
    if (Request.HttpMethod == "OPTIONS")
    {
        Response.AddHeader("Access-Control-Allow-Origin", origin);
        Response.AddHeader("Access-Control-Allow-Headers", "*");
        Response.AddHeader("Access-Control-Allow-Methods", "GET,POST,PUT,OPTIONS,DELETE");
        Response.StatusCode = 200;
        Response.End();
    }
    else
    {
        Response.AddHeader("Access-Control-Allow-Origin", origin);
        Response.AddHeader("Access-Control-Allow-Headers", "*");
        Response.AddHeader("Access-Control-Allow-Methods", "GET,POST,PUT,OPTIONS,DELETE");
    }
}
Jackdon Wang
fuente
0

Puede usar el middleware owin para definir la política de cors en la que puede definir múltiples orígenes de cors

return new CorsOptions
        {
            PolicyProvider = new CorsPolicyProvider
            {
                PolicyResolver = context =>
                {
                    var policy = new CorsPolicy()
                    {
                        AllowAnyOrigin = false,
                        AllowAnyMethod = true,
                        AllowAnyHeader = true,
                        SupportsCredentials = true
                    };
                    policy.Origins.Add("http://foo.com");
                    policy.Origins.Add("http://bar.com");
                    return Task.FromResult(policy);
                }
            }
        };
chayan banerjee
fuente
-3

Solo necesitas:

  • agregue un Global.asax a su proyecto,
  • eliminar <add name="Access-Control-Allow-Origin" value="*" />de su web.config.
  • luego, agregue esto en el Application_BeginRequestmétodo de Global.asax:

    HttpContext.Current.Response.AddHeader("Access-Control-Allow-Origin","*");
    
    if (HttpContext.Current.Request.HttpMethod == "OPTIONS")
    {
        HttpContext.Current.Response.AddHeader("Access-Control-Allow-Methods", "POST,GET,OPTIONS,PUT,DELETE");
        HttpContext.Current.Response.AddHeader("Access-Control-Allow-Headers", "Content-Type, Authorization, Accept");
        HttpContext.Current.Response.End();
    }

Espero esta ayuda. que funciona para mi.

joanrm20
fuente
Agregar "...- Origen: *" funciona excepto cuando permite las credenciales. Si tiene las credenciales de permiso establecidas en verdadero, entonces debe especificar un dominio (no simplemente *). Ahí es donde radica el meollo de este problema. De lo contrario, puede especificar "... allow-credentials: false" y terminar.
Richard