代码之家  ›  专栏  ›  技术社区  ›  r2_118

如何使用ServiceStack中的证书验证客户端?

  •  9
  • r2_118  · 技术社区  · 12 年前

    我正在探索使用ServiceStack作为WCF的替代方案。我的要求之一是服务器和客户端必须使用证书进行相互认证。客户端是一个服务,因此我不能使用任何涉及用户输入的身份验证类型。此外,客户端需要能够使用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));
        }
    }
    
    1 回复  |  直到 12 年前
        1
  •  11
  •   Scott Keith    12 年前

    一些人建议使用请求过滤器来验证客户端证书,但这似乎非常低效,因为每个请求都会检查客户端证书。性能是一个非常高的优先级。

    REST是无状态的,因此如果您不愿意在每个请求上检查客户端证书,则需要提供另一个身份验证令牌,以显示已经提供了有效身份。

    因此,如果在对客户端证书进行身份验证后,向客户端提供了一个会话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,并且您可以选择删除发送的客户端证书,直到会话无效。

    我希望这有帮助。

    推荐文章