web-dev-qa-db-fra.com

WebAPI CORS avec authentification Windows - permet la demande anonyme d'OPTIONS

J'ai un service WebAPI 2 REST exécuté avec l'authentification Windows. Il est hébergé séparément du site Web. J'ai donc activé CORS à l'aide du package ASP.NET CORS NuGet. Mon site client utilise AngularJS. 

Jusqu'ici, voici ce que j'ai vécu:

  1. Je n'avais pas de paramètre withCredentials, les demandes CORS renvoyaient donc un 401. Résolu en ajoutant withCredentials à la configuration $ httpProvider.
  2. Ensuite, j'avais défini EnableCorsAttribute avec une origine générique, ce qui n'est pas autorisé lors de l'utilisation d'informations d'identification. Résolu en établissant la liste explicite des origines.
  3. Cela a permis à mes requêtes GET de réussir, mais mon POST a émis une demande de contrôle en amont et je n'avais créé aucune action de contrôleur pour prendre en charge le verbe OPTIONS. Pour résoudre ce problème, j'ai implémenté un MessageHandler en tant que gestionnaire global OPTIONS. Il renvoie simplement 200 pour toute demande OPTIONS. Je sais que ce n'est pas parfait, mais fonctionne pour le moment, chez Fiddler.

Là où je suis coincé - mes _ appels de contrôle en amont Angular n'incluent pas les informations d'identification. Selon cette réponse , cela est inhérent au projet, car les requêtes OPTIONS sont conçues pour être anonymes. Cependant, l'authentification Windows arrête la demande avec un 401.

J'ai essayé de mettre l'attribut [AllowAnonymous] sur mon MessageHandler. Cela fonctionne sur mon ordinateur de développeur - les verbes OPTIONS ne nécessitent pas d’authentification, contrairement aux autres. Cependant, lorsque je compile et déploie sur le serveur de test, je continue à recevoir un 401 avec ma demande OPTIONS.

Est-il possible d'appliquer [AllowAnonymous] sur mon MessageHandler lors de l'utilisation de l'authentification Windows? Si oui, des conseils sur la façon de le faire? Ou est-ce le mauvais trou de lapin et je devrais envisager une approche différente?

UPDATE: J'ai pu le faire fonctionner en définissant l'authentification Windows et l'authentification anonyme sur le site dans IIS. Cela a permis à tout d'autoriser les anonymes. J'ai donc ajouté un filtre global d'autorisation, tout en conservant l'option AllowAnonymous sur mon MessageHandler. 

Cependant, cela ressemble à un hack ... J'ai toujours compris qu'il ne fallait utiliser qu'une seule méthode d'authentification (pas de méthode mixte). Si quelqu'un a une meilleure approche, j'aimerais en entendre parler.

14
Dave Simione

J'ai utilisé l'auto-hébergement avec HttpListener et la solution suivante a fonctionné pour moi:

  1. J'autorise les requêtes OPTIONS anonymes
  2. Activer CORS avec SupportsCredentials défini sur true
var cors = new EnableCorsAttribute("*", "*", "*");
cors.SupportsCredentials = true;
config.EnableCors(cors);
var listener = appBuilder.Properties["System.Net.HttpListener"] as HttpListener;
if (listener != null)
{
    listener.AuthenticationSchemeSelectorDelegate = (request) => {
    if (String.Compare(request.HttpMethod, "OPTIONS", true) == 0)
    {
        return AuthenticationSchemes.Anonymous;
    }
    else
    {
        return AuthenticationSchemes.IntegratedWindowsAuthentication;
    }};
}
14
Igor Tkachenko

J'ai eu du mal à faire en sorte que les demandes de la SCRO répondent aux contraintes suivantes (très similaires à celles des PO): 

  • Authentification Windows pour tous les utilisateurs
  • Aucune authentification anonyme autorisée
  • Fonctionne avec IE11 qui, dans certains cas , n'envoie pas de demandes de contrôle en amont CORS (ou du moins n'atteint pas global.asax BeginRequest en tant que demande OPTIONS) 

Ma configuration finale est la suivante:

web.config - autorise les requêtes de contrôle en amont non authentifiées (anonymes) (OPTIONS)

<system.web>
    <authentication mode="Windows" />
    <authorization>
        <allow verbs="OPTIONS" users="*"/>
        <deny users="?" />
    </authorization>
</system.web>

global.asax.cs - répondre correctement avec des en-têtes permettant à l'appelant d'un autre domaine de recevoir des données

protected void Application_AuthenticateRequest(object sender, EventArgs e)
{
    if (Context.Request.HttpMethod == "OPTIONS")
    {
        if (Context.Request.Headers["Origin"] != null)
            Context.Response.AddHeader("Access-Control-Allow-Origin", Context.Request.Headers["Origin"]);

        Context.Response.AddHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, MaxDataServiceVersion");
        Context.Response.AddHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
        Context.Response.AddHeader("Access-Control-Allow-Credentials", "true");

        Response.End();
    }
}

CORS permettant

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        // all requests are enabled in this example. SupportsCredentials must be here to allow authenticated requests          
        var corsAttr = new EnableCorsAttribute("*", "*", "*") { SupportsCredentials = true };
        config.EnableCors(corsAttr);
    }
}

protected void Application_Start()
{
    GlobalConfiguration.Configure(WebApiConfig.Register);
}
6
Alexei

Je l'ai résolu de manière très similaire mais avec quelques détails et concentré sur le service oData

Je n'ai pas désactivé l'authentification anonyme dans IIS, car j'en avais besoin pour POST demander

Et j'ai ajouté dans Global.aspx (Ajout de MaxDataServiceVersion dans Access-Control-Allow-Headers) le même code que ci-dessus

protected void Application_BeginRequest(object sender, EventArgs e)
{
    if ((Context.Request.Path.Contains("api/") || Context.Request.Path.Contains("odata/")) && Context.Request.HttpMethod == "OPTIONS")
    {
        Context.Response.AddHeader("Access-Control-Allow-Origin", Context.Request.Headers["Origin"]);
        Context.Response.AddHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept,MaxDataServiceVersion");
        Context.Response.AddHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
        Context.Response.AddHeader("Access-Control-Allow-Credentials", "true");
        Context.Response.End();
    }
} 

et WebAPIConfig.cs

public static void Register(HttpConfiguration config)
{
   // Web API configuration and services
   var cors = new EnableCorsAttribute("*", "*", "*");
   cors.SupportsCredentials = true;
   config.EnableCors(cors);


   config.Routes.MapHttpRoute(
       name: "DefaultApi",
       routeTemplate: "api/{controller}/{id}",
       defaults: new { id = RouteParameter.Optional }
   );
}

et appel AngularJS

$http({
       method: 'POST',
        url: 'http://XX.XXX.XXX.XX/oData/myoDataWS.svc/entityName',
        withCredentials: true,
        headers: {
            'Content-Type': 'application/json;odata=verbose',
            'Accept': 'application/json;odata=light;q=1,application/json;odata=verbose;q=0.5',
            'MaxDataServiceVersion': '3.0'
        },
        data: {
            '@odata.type':'entityName',
            'field1': 1560,
            'field2': 24,
            'field3': 'sjhdjshdjsd',
            'field4':'wewewew',
            'field5':'ewewewe',
            'lastModifiedDate':'2015-10-26T11:45:00',
            'field6':'1359',
            'field7':'5'
        }
    });
2
Fran Rodriguez

Il s'agit d'une solution beaucoup plus simple: quelques lignes de code permettant à toutes les requêtes "OPTIONS" d'emprunter l'identité du compte de pool d'applications. Vous pouvez laisser Anonymous désactivé et configurer les stratégies CORS conformément aux pratiques habituelles, mais ajoutez ensuite les éléments suivants à votre fichier global.asax.cs:

            protected void Application_AuthenticateRequest(object sender, EventArgs e)
            {
                if (Context.Request.HttpMethod == "OPTIONS" && Context.User == null)
                {
                    Context.User = System.Security.Principal.WindowsPrincipal.Current;
                }
            }
2
willman

Ceci est ma solution.

Global.asax *

protected void Application_BeginRequest(object sender, EventArgs e)
{
    if(!ListOfAuthorizedOrigins.Contains(Context.Request.Headers["Origin"])) return;

    if (Request.HttpMethod == "OPTIONS")
    {
        HttpContext.Current.Response.Headers.Remove("Access-Control-Allow-Origin");
        HttpContext.Current.Response.AddHeader("Access-Control-Allow-Origin", Context.Request.Headers["Origin"]);
        HttpContext.Current.Response.StatusCode = 200;
        HttpContext.Current.Response.End();
    }

    if (Request.Headers.AllKeys.Contains("Origin"))
    {
        HttpContext.Current.Response.Headers.Remove("Access-Control-Allow-Origin");
        HttpContext.Current.Response.AddHeader("Access-Control-Allow-Origin", Context.Request.Headers["Origin"]);
    }
}
0
Dblock247

Les autres solutions trouvées sur le Web ne me convenaient pas ou semblaient trop compliquées; à la fin je suis venu avec une solution plus simple et efficace:

web.config:

<system.web>
    ...
    <authentication mode="Windows" />
    <authorization>
        <deny users="?" />
    </authorization>
</system.web>

Propriétés du projet:

  1. Activer Windows Authentication
  2. Désactiver Anonymous Authentication

Setup CORS:

[Assembly: OwinStartup(typeof(Startup))]
namespace MyWebsite
{
    public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            app.UseCors(CorsOptions.AllowAll);

Cela nécessite un assemblage Microsoft.Owin.Cors disponible sur NUget.

Angular initialisation:

$httpProvider.defaults.withCredentials = true;
0
Vedran

désactivez l'authentification anonyme dans IIS si vous n'en avez pas besoin.

Puis ajoutez ceci dans votre asax global:

protected void Application_BeginRequest(object sender, EventArgs e)
{
    if ((Context.Request.Path.Contains("api/") || Context.Request.Path.Contains("odata/")) && Context.Request.HttpMethod == "OPTIONS")
    {
        Context.Response.AddHeader("Access-Control-Allow-Origin", Context.Request.Headers["Origin"]);
        Context.Response.AddHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
        Context.Response.AddHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
        Context.Response.AddHeader("Access-Control-Allow-Credentials", "true");
        Context.Response.End();
    }
} 

Assurez-vous que lorsque vous activez cors, vous activez également l'utilisation des informations d'identification, par exemple:

public static void Register(HttpConfiguration config)
{
   // Web API configuration and services
   var cors = new EnableCorsAttribute("*", "*", "*");
   cors.SupportsCredentials = true;
   config.EnableCors(cors);

   // Web API routes
   config.MapHttpAttributeRoutes();

   config.Routes.MapHttpRoute(
       name: "DefaultApi",
       routeTemplate: "api/{controller}/{id}",
       defaults: new { id = RouteParameter.Optional }
   );
}

Comme vous pouvez le constater, j'active CORS globalement et, à l'aide de l'application BeginRequest hook, j'authentifie toutes les demandes OPTIONS pour l'API (Web Api) et les demandes Odata (si vous l'utilisez).

Cela fonctionne très bien avec tous les navigateurs, côté client, n'oubliez pas d'ajouter le xhrFiled withCredentials comme indiqué ci-dessous.

$.ajax({
    type : method,
    url : apiUrl,
    dataType : "json",
    xhrFields: {
        withCredentials: true
    },
    async : true,
    crossDomain : true,
    contentType : "application/json",
    data: data ? JSON.stringify(data) : ''
}).....

J'essaie de trouver une autre solution en évitant d'utiliser le crochet, mais sans succès jusqu'à présent, J'utiliserais la configuration web.config pour effectuer les opérations suivantes: T TRAVAIL!

  <system.web>
    <compilation debug="true" targetFramework="4.5" />
    <httpRuntime targetFramework="4.5" />
    <authentication mode="Windows" />
    <authorization>
      <deny verbs="GET,PUT,POST" users="?" />
      <allow verbs="OPTIONS" users="?"/>
    </authorization>
  </system.web>
  <location path="api">
    <system.web>
      <authorization>
        <allow users="?"/>
      </authorization>
    </system.web>
  </location>
0
Manuel

Dave,

Après avoir joué avec le paquet CORS, voici ce qui m’a fait marcher: [EnableCors (origines: "", en-têtes: "", méthodes: "*", SupportsCredentials = true )]

Je devais activer SupportsCredentials = true. Les origines, les en-têtes et les méthodes sont tous définis sur "*"

0
jr3