拦截服务器和客户端中的 gRPC C++ 调用
Intercept gRPC C++ calls in server and client
我想做的基本任务:在 gRPC 服务器中提供一个 Authenticate
服务,所有客户端最初调用(并提供用户名和密码)以获得授权令牌(比如 JWT)。接下来,当客户端进行其他服务调用时,应该验证令牌。
这可以在 Java API 中使用 ServerInterceptor
和 ClientInterceptor
接口轻松实现。在 ServerInterceptor
中,我可以检查调用了哪个服务并决定是允许还是拒绝该调用。在 ClientInterceptor
方面,我可以将授权令牌作为元数据添加到每个服务调用中。
在 C++ 中有这个 AuthMetadataProcessor
抽象 class。但不确定如何完成类似于 Java API 的任务。有没有办法在 C++ API 中做类似的事情?
是的。您需要子类化 AuthMetadataProcessor
,覆盖其 Process
方法并向您的服务注册派生类型的实例。完成后,所有方法调用都将被 Process
拦截,并且会获得与请求一起发送的客户端元数据。
您对 Process
的实现必须决定拦截方法是否需要身份验证(即,您的 Authenticate
方法不需要身份验证,但随后调用的各种方法都需要身份验证)。这可以通过检查 :path
元数据密钥来完成,如问题 #9211 中所述,这是指定拦截方法的可信值。
您对 Process
的实施必须决定令牌是否在请求中提供并且是否有效。这是一个实现细节,但通常 Process
指的是由 Authenticate
生成的有效令牌的存储。这可能是您已经在 Java 中设置的方式。
不幸的是,不能在不安全的凭据之上注册 AuthMetadataProcessor,这意味着您将不得不使用 SSL,否则将尝试以不同的方式拦截方法。
该框架还提供方便的功能,允许您使用对等身份 属性。 Process
可以在身份验证上下文中调用 AddProperty
,提供令牌隐含的身份,然后是 SetPeerIdentityPropertyName
。然后,被调用的方法可以使用 GetPeerIdentity
访问信息并避免将标记重新映射到身份。
AuthMetadataProcessor 实现示例
struct Const
{
static const std::string& TokenKeyName() { static std::string _("token"); return _; }
static const std::string& PeerIdentityPropertyName() { static std::string _("username"); return _; }
};
class MyServiceAuthProcessor : public grpc::AuthMetadataProcessor
{
public:
grpc::Status Process(const InputMetadata& auth_metadata, grpc::AuthContext* context, OutputMetadata* consumed_auth_metadata, OutputMetadata* response_metadata) override
{
// determine intercepted method
std::string dispatch_keyname = ":path";
auto dispatch_kv = auth_metadata.find(dispatch_keyname);
if (dispatch_kv == auth_metadata.end())
return grpc::Status(grpc::StatusCode::INTERNAL, "Internal Error");
// if token metadata not necessary, return early, avoid token checking
auto dispatch_value = std::string(dispatch_kv->second.data());
if (dispatch_value == "/MyPackage.MyService/Authenticate")
return grpc::Status::OK;
// determine availability of token metadata
auto token_kv = auth_metadata.find(Const::TokenKeyName());
if (token_kv == auth_metadata.end())
return grpc::Status(grpc::StatusCode::UNAUTHENTICATED, "Missing Token");
// determine validity of token metadata
auto token_value = std::string(token_kv->second.data());
if (tokens.count(token_value) == 0)
return grpc::Status(grpc::StatusCode::UNAUTHENTICATED, "Invalid Token");
// once verified, mark as consumed and store user for later retrieval
consumed_auth_metadata->insert(std::make_pair(Const::TokenKeyName(), token_value)); // required
context->AddProperty(Const::PeerIdentityPropertyName(), tokens[token_value]); // optional
context->SetPeerIdentityPropertyName(Const::PeerIdentityPropertyName()); // optional
return grpc::Status::OK;
}
std::map<std::string, std::string> tokens;
};
安全服务中的 AuthMetadataProcessor 设置
class MyServiceImplSecure : public MyPackage::MyService::Service
{
public:
MyServiceImplSecure(std::string _server_priv, std::string _server_cert, std::string _ca_cert) :
server_priv(_server_priv), server_cert(_server_cert), ca_cert(_ca_cert) {}
std::shared_ptr<grpc::ServerCredentials> GetServerCredentials()
{
grpc::SslServerCredentialsOptions::PemKeyCertPair pkcp;
pkcp.private_key = server_priv;
pkcp.cert_chain = server_cert;
grpc::SslServerCredentialsOptions ssl_opts;
ssl_opts.pem_key_cert_pairs.push_back(pkcp);
ssl_opts.pem_root_certs = ca_cert;
std::shared_ptr<grpc::ServerCredentials> creds = grpc::SslServerCredentials(ssl_opts);
creds->SetAuthMetadataProcessor(auth_processor);
return creds;
}
void GetContextUserMapping(::grpc::ServerContext* context, std::string& username)
{
username = context->auth_context()->GetPeerIdentity()[0].data();
}
private:
std::string server_priv;
std::string server_cert;
std::string ca_cert;
std::shared_ptr<MyServiceAuthProcessor> auth_processor =
std::shared_ptr<MyServiceAuthProcessor>(new MyServiceAuthProcessor());
};
我想做的基本任务:在 gRPC 服务器中提供一个 Authenticate
服务,所有客户端最初调用(并提供用户名和密码)以获得授权令牌(比如 JWT)。接下来,当客户端进行其他服务调用时,应该验证令牌。
这可以在 Java API 中使用 ServerInterceptor
和 ClientInterceptor
接口轻松实现。在 ServerInterceptor
中,我可以检查调用了哪个服务并决定是允许还是拒绝该调用。在 ClientInterceptor
方面,我可以将授权令牌作为元数据添加到每个服务调用中。
在 C++ 中有这个 AuthMetadataProcessor
抽象 class。但不确定如何完成类似于 Java API 的任务。有没有办法在 C++ API 中做类似的事情?
是的。您需要子类化 AuthMetadataProcessor
,覆盖其 Process
方法并向您的服务注册派生类型的实例。完成后,所有方法调用都将被 Process
拦截,并且会获得与请求一起发送的客户端元数据。
您对 Process
的实现必须决定拦截方法是否需要身份验证(即,您的 Authenticate
方法不需要身份验证,但随后调用的各种方法都需要身份验证)。这可以通过检查 :path
元数据密钥来完成,如问题 #9211 中所述,这是指定拦截方法的可信值。
您对 Process
的实施必须决定令牌是否在请求中提供并且是否有效。这是一个实现细节,但通常 Process
指的是由 Authenticate
生成的有效令牌的存储。这可能是您已经在 Java 中设置的方式。
不幸的是,不能在不安全的凭据之上注册 AuthMetadataProcessor,这意味着您将不得不使用 SSL,否则将尝试以不同的方式拦截方法。
该框架还提供方便的功能,允许您使用对等身份 属性。 Process
可以在身份验证上下文中调用 AddProperty
,提供令牌隐含的身份,然后是 SetPeerIdentityPropertyName
。然后,被调用的方法可以使用 GetPeerIdentity
访问信息并避免将标记重新映射到身份。
AuthMetadataProcessor 实现示例
struct Const
{
static const std::string& TokenKeyName() { static std::string _("token"); return _; }
static const std::string& PeerIdentityPropertyName() { static std::string _("username"); return _; }
};
class MyServiceAuthProcessor : public grpc::AuthMetadataProcessor
{
public:
grpc::Status Process(const InputMetadata& auth_metadata, grpc::AuthContext* context, OutputMetadata* consumed_auth_metadata, OutputMetadata* response_metadata) override
{
// determine intercepted method
std::string dispatch_keyname = ":path";
auto dispatch_kv = auth_metadata.find(dispatch_keyname);
if (dispatch_kv == auth_metadata.end())
return grpc::Status(grpc::StatusCode::INTERNAL, "Internal Error");
// if token metadata not necessary, return early, avoid token checking
auto dispatch_value = std::string(dispatch_kv->second.data());
if (dispatch_value == "/MyPackage.MyService/Authenticate")
return grpc::Status::OK;
// determine availability of token metadata
auto token_kv = auth_metadata.find(Const::TokenKeyName());
if (token_kv == auth_metadata.end())
return grpc::Status(grpc::StatusCode::UNAUTHENTICATED, "Missing Token");
// determine validity of token metadata
auto token_value = std::string(token_kv->second.data());
if (tokens.count(token_value) == 0)
return grpc::Status(grpc::StatusCode::UNAUTHENTICATED, "Invalid Token");
// once verified, mark as consumed and store user for later retrieval
consumed_auth_metadata->insert(std::make_pair(Const::TokenKeyName(), token_value)); // required
context->AddProperty(Const::PeerIdentityPropertyName(), tokens[token_value]); // optional
context->SetPeerIdentityPropertyName(Const::PeerIdentityPropertyName()); // optional
return grpc::Status::OK;
}
std::map<std::string, std::string> tokens;
};
安全服务中的 AuthMetadataProcessor 设置
class MyServiceImplSecure : public MyPackage::MyService::Service
{
public:
MyServiceImplSecure(std::string _server_priv, std::string _server_cert, std::string _ca_cert) :
server_priv(_server_priv), server_cert(_server_cert), ca_cert(_ca_cert) {}
std::shared_ptr<grpc::ServerCredentials> GetServerCredentials()
{
grpc::SslServerCredentialsOptions::PemKeyCertPair pkcp;
pkcp.private_key = server_priv;
pkcp.cert_chain = server_cert;
grpc::SslServerCredentialsOptions ssl_opts;
ssl_opts.pem_key_cert_pairs.push_back(pkcp);
ssl_opts.pem_root_certs = ca_cert;
std::shared_ptr<grpc::ServerCredentials> creds = grpc::SslServerCredentials(ssl_opts);
creds->SetAuthMetadataProcessor(auth_processor);
return creds;
}
void GetContextUserMapping(::grpc::ServerContext* context, std::string& username)
{
username = context->auth_context()->GetPeerIdentity()[0].data();
}
private:
std::string server_priv;
std::string server_cert;
std::string ca_cert;
std::shared_ptr<MyServiceAuthProcessor> auth_processor =
std::shared_ptr<MyServiceAuthProcessor>(new MyServiceAuthProcessor());
};