9

WCF の代替として ServiceStack の使用を検討しています。私の要件の 1 つは、サーバーとクライアントが証明書を使用して相互に認証する必要があることです。クライアントはサービスであるため、ユーザー入力を伴う認証は使用できません。また、クライアントは mono を使用して Linux で実行できる必要があるため、Windows 認証は無効になります。

netsh.exe を使用してサーバー証明書をサーバーのポートにバインドし、クライアントがサーバー証明書を取得しており、wireshark を使用してデータが暗号化されていることを確認しました。ただし、クライアント証明書を要求するようにサーバーを構成する方法を理解することはできません。

一部の人々は、クライアント証明書を検証するために要求フィルターを使用することを提案しましたが、すべての要求がクライアント証明書をチェックするため、非常に非効率的です。パフォーマンスは非常に高い優先度です。カスタム IAuthProvider を作成することは有望に思えますが、すべてのドキュメントと例は、証明書ではなく、ある時点でのユーザー操作を伴う認証タイプを対象としています。

https://github.com/ServiceStack/ServiceStack/wiki/Authentication-and-authorization

証明書を使用して、セルフホステッド ServiceStack サービスでクライアントとサーバーを相互に認証することはできますか?

これが参考のための私のテストサービスです。

public class Host : AppHostHttpListenerBase
{
    public Host()
        : base("Self-hosted thing", typeof(PutValueService).Assembly)
    {
        //TODO - add custom IAuthProvider to validate the client certificate?
        this.RequestFilters.Add(ValidateRequest);

        //add protobuf plugin
        //https://github.com/ServiceStack/ServiceStack/wiki/Protobuf-format
        Plugins.Add(new ProtoBufFormat());

        //register protobuf
        base.ContentTypeFilters.Register(ContentType.ProtoBuf,
                (reqCtx, res, stream) => ProtoBuf.Serializer.NonGeneric.Serialize(stream, res),
                ProtoBuf.Serializer.NonGeneric.Deserialize);
    }

    public override void Configure(Funq.Container container)
    {}

    void ValidateRequest(IHttpRequest request, IHttpResponse response, object dto)
    {
        //TODO - get client certificate?
    }
}

[DataContract]
[Route("/putvalue", "POST")]
//dto
public class PutValueMessage : IReturnVoid
{
    [DataMember(Order=1)]
    public string StreamID { get; set; }

    [DataMember(Order=2)]
    public byte[] Data { get; set; }
}

//service
public class PutValueService : Service
{
    public void Any(PutValueMessage request)
    {
        //Comment out for performance testing

        Console.WriteLine(DateTime.Now);
        Console.WriteLine(request.StreamID);
        Console.WriteLine(Encoding.UTF8.GetString(request.Data));
    }
}
4

1 に答える 1

10

一部の人々は、クライアント証明書を検証するために要求フィルターを使用することを提案しましたが、すべての要求がクライアント証明書をチェックするため、非常に非効率的です。パフォーマンスは非常に高い優先度です。

REST はステートレスであるため、リクエストごとにクライアント証明書を確認したくない場合は、有効な ID が既に提供されていることを示す代替認証トークンを提供する必要があります。

したがって、クライアント証明書を認証した後、代わりに検証できるセッション ID Cookie をクライアントに提供すると、後続の要求で証明書をチェックすることを回避できます。

ただし、クライアント証明書を要求するようにサーバーを構成する方法を理解することはできません。

クライアント証明書は、元の http 要求オブジェクトでのみ使用できます。つまり、この値にアクセスするには、要求オブジェクトをキャストする必要があります。以下のコードはListenerRequest、セルフ ホスティング アプリケーションで使用される にリクエストをキャストするためのものです。

サーバー プロセス:

リクエスト フィルタは以下をチェックします。

  • 最初は有効なセッション cookie です。有効な場合、それ以上の処理を行わずにリクエストを許可するため、後続のリクエストでクライアント証明書を検証する必要はありません。

  • 有効なセッションが見つからない場合、フィルターはクライアント証明書の要求をチェックしようとします。存在する場合は、いくつかの基準に基づいて一致を試み、受け入れられたら、クライアントのセッションを作成し、Cookie を返します。

  • クライアント証明書が一致しなかった場合、認証例外をスローします。

GlobalRequestFilters.Add((req, res, requestDto) => {

    // Check for the session cookie
    const string cookieName = "auth";
    var sessionCookie = req.GetCookieValue(cookieName);
    if(sessionCookie != null)
    {
        // Try authenticate using the session cookie
        var cache = req.GetCacheClient();
        var session = cache.Get<MySession>(sessionCookie);
        if(session != null && session.Expires > DateTime.Now)
        {
            // Session is valid permit the request
            return;
        }
    }

    // Fallback to checking the client certificate
    var originalRequest = req.OriginalRequest as ListenerRequest;
    if(originalRequest != null)
    {
        // Get the certificate from the request
        var certificate = originalRequest.HttpRequest.GetClientCertificate();

        /*
         * Check the certificate is valid
         * (Replace with your own checks here)
         * You can do this by checking a database of known certificate serial numbers or the public key etc.
         * 
         * If you need database access you can resolve it from the container
         * var db = HostContext.TryResolve<IDbConnection>();
         */

        bool isValid = certificate != null && certificate.SerialNumber == "XXXXXXXXXXXXXXXX";

        // Handle valid certificates
        if(isValid)
        {
            // Create a session for the user
            var sessionId = SessionExtensions.CreateRandomBase64Id();
            var expiration = DateTime.Now.AddHours(1);

            var session = new MySession {
                Id = sessionId,
                Name = certificate.SubjectName,
                ClientCertificateSerialNumber = certificate.SerialNumber,
                Expires = expiration
            };

            // Add the session to the cache
            var cache = req.GetCacheClient();
            cache.Add<MySession>(sessionId, session);

            // Set the session cookie
            res.SetCookie(cookieName, sessionId, expiration);

            // Permit the request
            return;
        }
    }

    // No valid session cookie or client certificate
    throw new HttpError(System.Net.HttpStatusCode.Unauthorized, "401", "A valid client certificate or session is required");
});

MySessionこれは、必要に応じて独自のセッション オブジェクトに置き換えることができる、というカスタム セッション クラスを使用しました。

public class MySession
{
    public string Id { get; set; }
    public DateTime Expires { get; set; }
    public string Name { get; set; }
    public string ClientCertificateSerialNumber { get; set; }
}

クライアント プロセス:

クライアントは、リクエストとともに送信するクライアント証明書を設定する必要があります。

var client = new JsonServiceClient("https://servername:port/");
client.RequestFilter += (httpReq) => {
    var certificate = ... // Load the client certificate
    httpReq.ClientCertificates.Add( certificate );
};

サーバーで最初のリクエストを行うと、クライアントはセッション ID Cookie を受け取ります。オプションで、セッションが無効になるまでクライアント証明書の送信を削除できます。

それが役立つことを願っています。

于 2014-07-15T12:41:51.800 に答える