web-dev-qa-db-fra.com

Gestion des demandes de contrôle en amont CORS vers des actions ASP.NET MVC

J'essaie d'exécuter une demande inter-domaine POST à une action de contrôleur ASP.NET MVC. Cette action de contrôleur accepte et utilise divers paramètres. Le problème est que lorsque la demande de contrôle en amont se produit, le l'action du contrôleur tente en fait de s'exécuter et parce que la demande OPTIONS ne transmet aucune donnée, l'action du contrôleur génère une erreur HTTP 500. Si je supprime le code qui utilise le paramètre ou le paramètre lui-même, la chaîne de demande entière est terminée avec succès .

Un exemple de la façon dont cela est mis en œuvre:

Action du contrôleur

public ActionResult GetData(string data)
{
    return new JsonResult
    {
        Data = data.ToUpper(),
        JsonRequestBehavior = JsonRequestBehavior.AllowGet
    };
}

code côté client

<script type="text/javascript">
        $(function () {
            $("#button-request").click(function () {
                var ajaxConfig = {
                    dataType: "json",
                    url: "http://localhost:8100/Host/getdata",
                    contentType: 'application/json',
                    data: JSON.stringify({ data: "A string of data" }),
                    type: "POST",
                    success: function (result) {
                        alert(result);
                    },
                    error: function (jqXHR, textStatus, errorThrown) {
                        alert('Error: Status: ' + textStatus + ', Message: ' + errorThrown);
                    }
                };

                $.ajax(ajaxConfig);
            });
        });
    </script>

Désormais, chaque fois que la demande de contrôle en amont se produit, elle renvoie un code HTTP 500, car le paramètre "data" est nul, car la demande OPTIONS ne transmet aucune valeur.

L'application serveur a été configurée dans mon local IIS sur le port 8100 et la page exécutant le code côté client est configurée sur le port 8200 pour imiter les appels interdomaines.

J'ai également configuré l'hôte (sur 8100) avec les en-têtes suivants:

Access-Control-Allow-Headers: Content-Type
Access-Control-Allow-Methods: POST, GET
Access-Control-Allow-Origin: http://localhost:8200

Une solution de contournement que j'avais trouvée était de vérifier la méthode HTTP qui exécute l'action et s'il s'agit d'une demande OPTIONS pour renvoyer simplement du contenu vide, sinon exécutez le code d'action. Ainsi:

public ActionResult GetData(string data)
{
    if (Request.HttpMethod == "OPTIONS") {
        return new ContentResult();
    } else {
        return new JsonResult
        {
            Data = data.ToUpper(),
            JsonRequestBehavior = JsonRequestBehavior.AllowGet
        };
    }
}

Mais cette approche me semble très maladroite. J'ai envisagé d'ajouter ce type de logique à un Attribute, mais même cela signifierait décorer chaque action qui sera appelée à l'aide de CORS avec.

Existe-t-il une solution plus élégante pour faire fonctionner cette fonctionnalité?

47

J'ai donc trouvé une solution qui fonctionne. Pour chaque demande, je vérifie s'il s'agit d'une demande CORS et si la demande arrive avec le verbe OPTIONS, indiquant qu'il s'agit de la demande de contrôle en amont. Si c'est le cas, j'envoie simplement une réponse vide (qui ne contient que les en-têtes configurés dans IIS bien sûr), annulant ainsi l'exécution de l'action du contrôleur.

Ensuite, si le client confirme qu'il est autorisé à exécuter la demande sur la base des en-têtes retournés du contrôle en amont, la réelle POST est effectuée et l'action du contrôleur est exécutée. Et exemple de mon code:

protected void Application_BeginRequest()
{
    if (Request.Headers.AllKeys.Contains("Origin", StringComparer.OrdinalIgnoreCase) &&
        Request.HttpMethod == "OPTIONS") {
        Response.Flush();
    }
}

Comme mentionné, cela a fonctionné pour moi, mais si quelqu'un connaît une meilleure méthode ou des défauts dans ma mise en œuvre actuelle, j'apprécierais d'en entendre parler.

61

développant la réponse de Carl, j'ai pris son code et l'ai branché sur mon pipeline OWIN:

app.Use((context, next) =>
{
     if (context.Request.Headers.Any(k => k.Key.Contains("Origin")) && context.Request.Method == "OPTIONS")
     {
         context.Response.StatusCode = 200;
         return context.Response.WriteAsync("handled");
     }

     return next.Invoke();
});

Ajoutez simplement ceci au début (ou n'importe où avant d'enregistrer le WebAPI) de votre IAppBuilder dans Startup.cs

10
Jonesopolis

La réponse acceptée fonctionne comme un charme, mais j'ai trouvé que la demande était en fait transmise au contrôleur. Je recevais un code d'état 200, Mais le corps de la réponse contenait beaucoup de code HTML à l'exception du contrôleur. Donc, au lieu d'utiliser Response.Flush(), j'ai trouvé qu'il était préférable d'utiliser Response.End(), ce qui arrête l'exécution de la demande. Cette solution alternative ressemblerait à ceci:

EDIT: correction d'une faute de frappe issue de la réponse d'origine.

protected void Application_BeginRequest()
{
    if (Request.Headers.AllKeys.Contains("Origin", StringComparer.OrdinalIgnoreCase) &&
        Request.HttpMethod == "OPTIONS") {
        Response.End();
    }
}
5
Gabriel Diéguez

Voici comment j'ai géré les problèmes de contrôle en amont/CORS avec ASP.Net Web Api. J'ai simplement ajouté le package Nuget Microsoft.AspNet.WebApi.Cors à mon projet Web, puis dans mon fichier WebApiConfig.cs j'ai ajouté cette ligne:

config.EnableCors(new ApplicationCorsPolicy());

et créé une classe PolicyProvider personnalisée

public class ApplicationCorsPolicy : Attribute, ICorsPolicyProvider
{
    public async Task<CorsPolicy> GetCorsPolicyAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        var corsRequestContext = request.GetCorsRequestContext();
        var originRequested = corsRequestContext.Origin;

        if (await IsOriginFromAPaidCustomer(originRequested))
        {
            // Grant CORS request
            var policy = new CorsPolicy
            {
                AllowAnyHeader = true,
                AllowAnyMethod = true
            };
            policy.Origins.Add(originRequested);
            return policy;
        }
        // Reject CORS request
        return null;
    }

    private async Task<bool> IsOriginFromAPaidCustomer(string originRequested)
    {
        // Do database look up here to determine if Origin should be allowed.
        // In my application I have a table that has a list of domains that are
        // allowed to make API requests to my service. This is validated here.
        return true;
    }
}

Voir, le framework Cors vous permet d'ajouter votre propre logique pour déterminer les origines autorisées, etc. Ceci est très utile si vous exposez une API REST au monde extérieur et à la liste des personnes (origines) qui peuvent accéder à votre site se trouvent dans un environnement contrôlé comme une base de données. Maintenant, si vous autorisez simplement toutes les origines (ce qui n'est peut-être pas une si bonne idée dans tous les cas), vous pouvez simplement le faire dans WebApiConfig.cs pour activer CORS dans le monde:

config.EnableCors();

Tout comme les filtres et les gestionnaires dans WebApi, vous pouvez également ajouter des annotations de niveau de classe ou de méthode à vos contrôleurs comme ceci:

[EnableCors("*, *, *, *")]

Notez que l'attribut EnableCors a un constructeur qui accepte les paramètres suivants

  1. Liste des origines autorisées
  2. Liste des en-têtes de demande autorisés
  3. Liste des méthodes HTTP autorisées
  4. Liste des en-têtes de réponse autorisés

Vous pouvez spécifier statiquement à chaque contrôleur/point final qui est autorisé à accéder à quelle ressource.

Mise à jour du 24/06/2016: Je dois mentionner que j'ai les éléments suivants dans mon Web.config. Il semble que ce ne soient pas les valeurs par défaut pour tout le monde.

<system.webServer>
    <handlers>
        <remove name="ExtensionlessUrlHandler-Integrated-4.0" />
        <remove name="OPTIONSVerbHandler" />
        <remove name="TRACEVerbHandler" />
        <add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="*" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
        </handlers>
</system.webServer>

Source: Microsoft

4
Rob L

Aucune de ces réponses n'a fonctionné pour moi, mais les paramètres de configuration Web suivants l'ont fait. Les deux paramètres clés pour moi étaient de définir Access-Control-Allow-Headers à Content-Type et commentant la ligne qui supprime le OPTIONSVerbHandler:

  <system.webServer>
    <modules runAllManagedModulesForAllRequests="true"></modules>
    <httpProtocol>
      <customHeaders>
        <add name="Access-Control-Allow-Origin" value="*" />
        <add name="Access-Control-Allow-Headers" value="Content-Type" />
      </customHeaders>
    </httpProtocol>
    <handlers>
      <remove name="ExtensionlessUrlHandler-Integrated-4.0" />
      <!--<remove name="OPTIONSVerbHandler" />-->
      <remove name="TRACEVerbHandler" />
      <add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="*" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
    </handlers>
  </system.webServer>
3
TreeAndLeaf

Cela peut être un hareng rouge. J'ai récemment réussi à faire fonctionner CORS sans sauter à travers les cerceaux que vous faites.

Cela a été fait en utilisant une combinaison de paquet de pépites Thinktecture.IdentityModel, et plus important encore ... SUPPRESSION de toutes les références à WebDAV. Cela inclut la suppression du module webdav d'IIS et la garantie des lignes suivantes dans votre configuration Web:

<system.webServer>
    <validation validateIntegratedModeConfiguration="false" />
    <modules runAllManagedModulesForAllRequests="true">
      <remove name="WebDAVModule" />
      <add name="ErrorLog" type="Elmah.ErrorLogModule, Elmah" preCondition="managedHandler" />
      <add name="ErrorMail" type="Elmah.ErrorMailModule, Elmah" preCondition="managedHandler" />
      <add name="ErrorFilter" type="Elmah.ErrorFilterModule, Elmah" preCondition="managedHandler" />
    </modules>
    <handlers>
      <remove name="WebDAV" />
      <remove name="ExtensionlessUrlHandler-ISAPI-4.0_32bit" />
      <remove name="ExtensionlessUrlHandler-ISAPI-4.0_64bit" />
      <remove name="ExtensionlessUrlHandler-Integrated-4.0" />
      <add name="ExtensionlessUrlHandler-ISAPI-4.0_32bit" path="*." verb="GET,HEAD,POST,DEBUG,PUT,DELETE,PATCH,OPTIONS" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness32" responseBufferLimit="0" />
  <add name="ExtensionlessUrlHandler-ISAPI-4.0_64bit" path="*." verb="GET,HEAD,POST,DEBUG,PUT,DELETE,PATCH,OPTIONS" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness64" responseBufferLimit="0" />
  <add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="GET,HEAD,POST,DEBUG,PUT,DELETE,PATCH,OPTIONS" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
</handlers>

Ensuite, vous pouvez simplement utiliser thinktecture pour configurer votre CORS à partir de votre Global.asax en utilisant une classe statique comme celle-ci:

public class CorsConfig
{
    public static void RegisterCors(HttpConfiguration httpConfiguration)
    {
        var corsConfig = new WebApiCorsConfiguration();
        corsConfig.RegisterGlobal(httpConfiguration);

        corsConfig.ForAllResources().AllowAllOriginsAllMethodsAndAllRequestHeaders();
    }
}

SOURCE: http://brockallen.com/2012/06/28/cors-support-in-webapi-mvc-and-iis-with-thinktecture-identitymodel/

2
beyond-code