一些人建议使用请求过滤器来验证客户端证书,但这似乎非常低效,因为每个请求都会检查客户端证书。性能是一个非常高的优先级。
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,并且您可以选择删除发送的客户端证书,直到会话无效。
我希望这有帮助。