我正在探索使用 ServiceStack 作为 WCF 的替代品。我的要求之一是服务器和客户端必须使用证书相互验证。客户端是一项服务,因此我不能使用涉及用户输入的任何类型的身份验证。此外,客户端需要能够使用单声道在 Linux 上运行,这样 Windows 身份验证就失效了。
我已经使用 netsh.exe 将我的服务器证书绑定(bind)到我的服务器端口,验证了客户端正在获取服务器证书并且数据正在使用 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));
}
}
最佳答案
Some people suggested using request filters to validate the client certificate, but that seems very inefficient since every request would check the client certificate. Performance is a very high priority.
REST 是无状态的,因此如果您不愿意在每个请求中检查客户端证书,您将需要提供替代身份验证 token 以显示已提供有效身份。
因此您可以避免在后续请求中检查证书,如果在验证客户端证书之后,您向客户端提供一个可以验证的 session ID cookie。
However I can't for the life of me figure out how to configure the server to require a client certificate.
客户端证书仅在原始 http 请求对象上可用,这意味着您必须强制转换请求对象才能访问此值。下面的代码用于将请求转换为自托管应用程序使用的 ListenerRequest
。
服务器进程:
请求过滤器将检查:
首先是一个有效的 session cookie,如果有效,将允许请求无需进一步处理,因此不需要在后续请求中验证客户端证书。
如果未找到有效 session ,过滤器将尝试检查客户端证书请求。如果它存在,则尝试根据某些条件匹配它,并在接受后为客户端创建一个 session ,并返回一个 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
的自定义 session 类,您可以根据需要将其替换为您自己的 session 对象。
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 );
};
一旦您向服务器发出第一个请求,您的客户端将收到一个 session ID cookie,并且您可以选择删除发送的客户端证书,直到 session 无效。
希望对您有所帮助。
关于c# - 如何使用 ServiceStack 中的证书对客户端进行身份验证?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24748769/