2014-12-05 6 views
19

bearbeiten für zukünftige Leser: Leider ist die Prämie ausgezeichnet Antwort nicht funktioniert; ich kann nichts dagegen tun. Aber lesen Sie meine eigene Antwort unten (durch Tests) - bestätigt mit minimaler Code arbeiten ändertSSL-Client-Zertifikat auf * einigen * WebAPI-Controllern deaktivieren?

Wir haben ein Azure Cloud Service (WebRole), das ist ganz in ASP.NET WebAPI 2.2 (kein MVC, Frontend ist Angular). Einige unserer Controller/REST-Endpunkte sprechen mit einem Cloud-Dienst eines Drittanbieters über SSL (client cert auth/mutual auth) und die übrigen Controller/Endpoints sprechen mit dem Frontend HTML5/AngularJS, auch über SSL (aber eher traditionelle Serverauthentifizierung) SSL). Wir haben keinen Nicht-SSL-Endpunkt. Wir haben über einen Cloud-Service-Start-Task-Client SSL aktiviert wie:

IF NOT DEFINED APPCMD SET APPCMD=%SystemRoot%\system32\inetsrv\AppCmd.exe 
%APPCMD% unlock config /section:system.webServer/security/access 

Problem: Diese Einstellung ist standortweite so auch wenn Benutzer die erste Seite getroffen (sagen wir https://domain.com, gibt die index.html für AngularJS) ihre Browser fragt sie nach Client-SSL-Zertifikat. (Bild unten)

Wenn es eine Möglichkeit, entweder

  1. Grenze der Client-SSL-Zertifikat-Anforderungen nur die WebAPI Controller, die auf den 3rd-Party-Cloud-Service sprechen?

OR

  1. überspringen Client SSL auth für unsere Frontend WebAPI Controller einschalten?

web.config Unser Server ist komplex, aber der entsprechende Code-Schnipsel ist unten:

<system.webServer> 
    <security> 
    <access sslFlags="SslNegotiateCert" /> 
    </security> 
</system.webServer> 

Und der Screenshot des Clients noch versuchen, Client-SSL-Authentifizierung eines regelmäßigen WebAPI Endpunkt trifft (geschieht in jedem Browser, Chrome , Firefox oder IE)

+0

können Sie Ihr Ergebnis serverWebConfig und WebConfig-Anwendung anzeigen? –

+0

Server Web.Konfiguration oben, Anwendung webconfig existiert nicht, da die Client-Anwendung ein Browser ist – DeepSpace101

+0

Ich denke, dass diese Frage besser auf Serverfehler passen würde. –

Antwort

10

Leider Cleftheris 'Antwort, die das Kopfgeld verliehen wird, funktioniert nicht. Es versucht, zu spät in der HTTP-Server-Pipeline/Verarbeitung zu arbeiten, um das Client-Zertifikat zu erhalten, aber this post gab mir einige Ideen.

Die Lösung basiert auf web.config, die eine spezielle Behandlung von "Verzeichnissen" erfordert (funktioniert auch für virtuelle Ordner oder WebAPI-Routen). Hier

ist die gewünschte Logik:

https://www.server.com/acmeapi/ ** => SSL mit Client-Zertifikate

https://www.server.com/ ** => SSL

Hier ist die entsprechende Konfiguration

<configuration> 
    ... 
    <system.webServer> 
    <!-- This is for the rest of the site --> 
    <security> 
     <access sslFlags="Ssl" /> 
    </security> 
    </system.webServer> 

    <!--This is for the 3rd party API endpoint--> 
    <location path="acmeapi"> 
    <system.webServer> 
     <security> 
     <access sslFlags="SslNegotiateCert"/> 
     </security> 
    </system.webServer> 
    </location> 
... 
</configuration> 

Bonuspunkte

Das oben genannte richtet den SSL-Handshake entsprechend ein. Jetzt müssen Sie noch das Client-SSL-Zertifikat in Ihrem Code überprüfen, wenn es das ist, was Sie erwarten. Das wird wie folgt durchgeführt

Controller-Code:

[RoutePrefix("acmeapi")] 
[SslClientCertActionFilter] // <== key part! 
public class AcmeProviderController : ApiController 
{ 
    [HttpGet] 
    [Route("{userId}")] 
    public async Task<OutputDto> GetInfo(Guid userId) 
    { 
     // do work ... 
    } 
} 

Actual Attribut von oben, dass SSL-Client-Validierung durchzuführen ist unten. Kann verwendet werden, um den gesamten Controller oder nur bestimmte Methoden zu dekorieren.

public class SslClientCertActionFilterAttribute : ActionFilterAttribute 
{ 
    public List<string> AllowedThumbprints = new List<string>() 
    { 
     // Replace with the thumbprints the 3rd party 
     // server will be presenting. You can make checks 
     // more elaborate but always have thumbprint checking ... 
     "0011223344556677889900112233445566778899", 
     "1122334455667788990011223344556677889900" 
    }; 

    public override void OnActionExecuting(HttpActionContext actionContext) 
    { 
     var request = actionContext.Request; 

     if (!AuthorizeRequest(request)) 
     { 
      throw new HttpResponseException(HttpStatusCode.Forbidden); 
     } 
    } 

    private bool AuthorizeRequest(HttpRequestMessage request) 
    { 
     if (request==null) 
      throw new ArgumentNullException("request"); 

     var clientCertificate = request.GetClientCertificate(); 

     if (clientCertificate == null || AllowedThumbprints == null || AllowedThumbprints.Count < 1) 
     { 
      return false; 
     } 

     foreach (var thumbprint in AllowedThumbprints) 
     { 
      if (clientCertificate.Thumbprint != null && clientCertificate.Thumbprint.Equals(thumbprint, StringComparison.InvariantCultureIgnoreCase)) 
      { 
       return true; 
      } 
     } 
     return false; 
    } 
} 
+0

Das funktionierte wie ein Charme. Ich konnte keine anderen Referenzen finden, die eine funktionierende Lösung vorschlugen. Danke – Saravanan

2

Leider kann dies nicht auf der Controller-Ebene konfiguriert werden. Der Server entscheidet basierend auf dem Inhalt einer HTTP-Anfrage (normalerweise der Anfragepfad), welcher Controller verwendet werden soll. SSL schützt den Inhalt einer HTTP-Nachricht durch Verschlüsselung und der Anfragepfad ist Teil der verschlüsselten Nachricht. Der SSL-Kanal muss eingerichtet werden, bevor HTTP-Nachrichten gesendet werden. Daher kann die Konfiguration des SSL-Kanals (ob der Server versucht, ein Client-Zertifikat auszuhandeln oder nicht) nicht auf den Inhalt von HTTP vertrauen Mitteilungen.

hier sind also Ihre Möglichkeiten:

  1. Spin eine zweite Webrolle auf die Client-Zertifikate nicht zu verhandeln konfiguriert ist. Dafür benötigen Sie eine zweite Domäne, da es sich im Wesentlichen um einen separaten Dienst handelt. Sie müssten also https://domain.com auf das Nicht-Client-Zertifikat eins und https://foo.domain.com zeigen, das auf das zeigt, das Client-Zertifikate erfordert.

  2. Verwenden Sie dieselbe Webrolle, richten Sie jedoch einen zweiten Port für IIS ein, der überwacht werden soll, und konfigurieren Sie, dass ein Client-Zertifikat nicht ausgehandelt wird. Die Verwendung von Nicht-Standard-Ports ist jedoch ein Problem, da einer Ihrer Clients https://domain.com:444 (oder einen anderen Port neben 443) tun muss.

  3. Deaktivieren Sie Client Cert Negotiation auf der ganzen Linie. Je nachdem, wie Ihr Dienst auf das Clientzertifikat zugreift, funktioniert dies möglicherweise nicht. Wenn Sie jedoch auf die ClientCertificate-Eigenschaft eines System.Web.HttpRequest-Objekts (oder eines entsprechenden Objekts) zugreifen, wird bei Bedarf ein Zertifikat ausgehandelt. Dies bedeutet, dass es die vorhandene SSL-Sitzung transparent abreißt und eine neue erstellt, die dieses Mal den Client für das Zertifikat herausfordert. Dies ist ziemlich ineffizient, da das Einrichten einer SSL-Verbindung in erster Linie mehrere Round-Trips erfordert, und es ist schmerzhaft, es zweimal zu tun. Abhängig von Ihren verfügbaren Optionen, den Leistungsanforderungen für die Anforderungen, die Client-Zertifikate verwenden, und davon, ob Sie eine große Anzahl Verbindungswiederverwendung aus Keep-Alives erhalten, kann diese Option sinnvoll sein.

Hoffe, das hilft.

+0

Wenn nicht die Controller, kann ich IIS selbst mitteilen, welche Pfade Client-SSL erzwingen (oder überspringen) soll? Basierend auf Ihrer Beschreibung ist es die richtige Ebene für diese Entscheidungsfindung. – DeepSpace101

+0

Leider können Sie nicht. Das einzige, was Sie benutzen können, ist die IP und der Port. Wenn eine HTTP-Anfrage eingeht, muss die SSL-Verhandlung stattfinden, bevor der Server den Pfad findet. Option # 3 ist der nächste Weg, um verschiedene Entscheidungen basierend auf dem Pfad/Controller zu treffen. Aber es ist weniger effizient. – daspek

+0

@ DeepSpace101 Konnten Sie versuchen, einen leeren physischen Ordner mit seiner eigenen web.config zu erstellen und dann Ihre sichere Controller-Route unter diesem Pfad zu machen? Dann können Sie das Zugriffselement möglicherweise nur für diesen Standort entsperren, indem Sie den Befehl Appcmd Unlock Config verwenden. – cleftheris

9

Sie könnten einfachen HTTP-Verkehr auf der web.config-Ebene zulassen und dafür einen benutzerdefinierten delegierenden Handler in die Web-API-Pipeline schreiben. Sie können einen Client-Cert-Delegierungshandler here und here finden. Dann könnten Sie diesen Handler wie in diesem Beispiel "Per-Route" aktiv machen. here:

So würde Ihre Routenkonfiguration aussehen.

public static class WebApiConfig 
{ 
    public static void Register(HttpConfiguration config) 
    { 
     config.Routes.MapHttpRoute(
      name: "Route1", 
      routeTemplate: "api/{controller}/{id}", 
      defaults: new { id = RouteParameter.Optional } 
     ); 

     config.Routes.MapHttpRoute(
      name: "Route2", 
      routeTemplate: "api2/{controller}/{id}", 
      defaults: new { id = RouteParameter.Optional }, 
      constraints: null, 
      handler: new CustomCertificateMessageHandler() // per-route message handler 
     ); 

     config.MessageHandlers.Add(new SomeOtherMessageHandler()); // global message handler 
    } 
} 

Bitte beachten Sie, dass im Fall, dass Sie „pro-Route“ müssen delegieren Handler Sie müssen sie in der globalen Nachrichtenhandler Liste nicht setzen.

+0

Beachten Sie, dass dies eine Implementierung von Option # 3 aus meiner Antwort ist, so dass es das Leistungsproblem hat, die SSL-Sitzung zweimal einzurichten. – daspek

+0

@ DeepSpace101 werfen Sie einen Blick auf [Codeplex Workitem] (http://aspnetwebstack.codeplex.com/workitem/980). Der aufgelistete Code sollte gut funktionieren. – cleftheris

+0

An zukünftige Leser: Bitte lesen Sie meine Antwort auf meine eigene Frage. Es ist bestätigt zu arbeiten und erfordert minimale Code-Änderungen – DeepSpace101

Verwandte Themen