认证

gRPC 认证概述,包括内置的认证机制,以及如何插入自定义的认证系统。

概述

gRPC 设计上支持多种认证机制,方便安全地与其他系统通信。你可以使用 gRPC 提供的支持机制——SSL/TLS(可以选择是否结合 Google 基于令牌的认证)——或者通过扩展 gRPC 提供的代码,插入你自己的认证系统。

gRPC 还提供了一个简单的认证 API,允许你在创建通道或发起调用时,提供所有必要的认证信息作为凭据。

支持的认证机制

以下是 gRPC 内置的认证机制:

  • SSL/TLS:gRPC 集成了 SSL/TLS,并推荐使用 SSL/TLS 来认证服务器,以及加密客户端与服务器之间交换的所有数据。还提供了可选的机制,允许客户端提供证书进行双向认证。

  • ALTS:如果应用运行在 Compute Engine 或 Google Kubernetes Engine (GKE) 上,gRPC 支持 ALTS 作为传输安全机制。详细内容请参阅以下语言特定页面:C++ 中的 ALTS、Go 中的 ALTS、Java 中的 ALTS、Python 中的 ALTS。

  • 基于令牌的 Google 认证:gRPC 提供了一个通用机制(如下所述),可以将元数据类型的凭据附加到请求和响应中。还提供了特定认证流程的支持,可以在通过 gRPC 访问 Google API 时获取访问令牌(通常是 OAuth2 令牌)。通常情况下,这种机制必须与 SSL/TLS 一起使用——Google 不允许在没有 SSL/TLS 的通道上建立连接,且大多数 gRPC 语言实现不会在未加密的通道上发送凭据。

Google 凭据仅应用于连接 Google 服务。将 Google 发放的 OAuth2 令牌发送给非 Google 服务可能会导致该令牌被窃取,并被用来冒充客户端访问 Google 服务。

认证 API

gRPC 提供了一个简单的认证 API,基于统一的凭据对象概念,可以在创建整个 gRPC 通道或单个调用时使用。

凭据类型

凭据可以有两种类型:

  • 通道凭据,用于附加到通道,例如 SSL 凭据。

  • 调用凭据,用于附加到单个调用(或 C++ 中的 ClientContext)。

你还可以将这些凭据组合成 CompositeChannelCredentials,例如,在通道上指定 SSL 细节,同时为该通道上的每个调用指定调用凭据。CompositeChannelCredentials 会将与组合的 CallCredentials 关联的认证数据发送到该通道上的每个调用。

例如,可以通过 SslCredentialsAccessTokenCredentials 创建 ChannelCredentials,应用到通道时,会在每个调用中发送相应的访问令牌。

单独的 CallCredentials 也可以使用 CompositeCallCredentials 组合。组合后的 CallCredentials 在调用时会触发发送与两个 CallCredentials 关联的认证数据。

使用客户端 SSL/TLS

下面让我们看看如何使用支持的认证机制之一来工作。这是最简单的认证场景,客户端只是想认证服务器并加密所有数据。下面的示例为 C++ 代码,但其他语言的 API 相似:你可以在我们下面的示例部分查看如何在更多语言中启用 SSL/TLS。

// 创建一个默认的 SSL ChannelCredentials 对象。
auto channel_creds = grpc::SslCredentials(grpc::SslCredentialsOptions());
// 使用前面创建的凭据创建通道。
auto channel = grpc::CreateChannel(server_name, channel_creds);
// 在通道上创建一个存根。
std::unique_ptr<Greeter::Stub> stub(Greeter::NewStub(channel));
// 在存根上发起实际的 RPC 调用。
grpc::Status s = stub->sayHello(&context, *request, response);

对于需要修改根 CA 或使用客户端证书等高级用例,可以在 SslCredentialsOptions 参数中设置相应的选项。

非 POSIX 系统(如 Windows)需要在 SslCredentialsOptions 中指定根证书,因为默认情况下只为 POSIX 文件系统配置了根证书。

使用 OAuth 令牌认证

OAuth 2.0 协议是行业标准的授权协议,允许网站或应用程序使用 OAuth 令牌获取对用户账户的有限访问权限。

gRPC 提供了一套简单的 API 来将 OAuth 2.0 集成到应用程序中,简化认证过程。

使用 OAuth 令牌认证通常包括以下 3 个步骤:

  1. 在客户端获取或生成 OAuth 令牌

    • 可以按照下述说明生成 Google 特定的令牌。

  2. 使用 OAuth 令牌创建凭据

    • OAuth 令牌总是作为每个调用的凭据的一部分,你也可以将这些调用凭据附加到某些通道凭据中。

    • 令牌会作为 HTTP Authorization 头的一部分发送到服务器。

  3. 服务器端验证令牌

    • 在大多数实现中,验证通常是通过服务器端拦截器完成的。

有关如何在不同语言中使用 OAuth 令牌的详细信息,请参阅我们下面的示例。

使用 Google 令牌认证

gRPC 应用程序可以使用简单的 API 创建与 Google 认证系统兼容的凭据。在下面的示例中,我们使用 C++,但你可以在其他语言的示例部分找到相关代码。

auto creds = grpc::GoogleDefaultCredentials();
// 创建通道、存根并发起 RPC 调用(与上面的示例相同)
auto channel = grpc::CreateChannel(server_name, creds);
std::unique_ptr<Greeter::Stub> stub(Greeter::NewStub(channel));
grpc::Status s = stub->sayHello(&context, *request, response);

这个通道凭据对象适用于使用服务账户的应用程序,也适用于在 Google Compute Engine (GCE) 上运行的应用程序。在前一种情况中,服务账户的私钥会从环境变量 GOOGLE_APPLICATION_CREDENTIALS 指定的文件中加载,使用该私钥生成承载令牌,并将其附加到每个传出的 RPC 请求上。

对于运行在 GCE 上的应用程序,可以在 VM 设置过程中配置默认服务账户和相关 OAuth2 范围。在运行时,这个凭据会处理与认证系统的通信,以获取 OAuth2 访问令牌,并将其附加到每个传出的 RPC 请求上。

扩展 gRPC 支持其他认证机制

凭据插件 API 允许开发者插入自己类型的凭据。具体包括:

  • MetadataCredentialsPlugin 抽象类,其中包含需要由开发者创建的子类实现的纯虚拟 GetMetadata 方法。

  • MetadataCredentialsFromPlugin 函数,用于从 MetadataCredentialsPlugin 创建 CallCredentials

下面是一个简单的凭据插件示例,它在自定义头中设置了认证票据:

class MyCustomAuthenticator : public grpc::MetadataCredentialsPlugin {
 public:
  MyCustomAuthenticator(const grpc::string& ticket) : ticket_(ticket) {}

  grpc::Status GetMetadata(
      grpc::string_ref service_url, grpc::string_ref method_name,
      const grpc::AuthContext& channel_auth_context,
      std::multimap<grpc::string, grpc::string>* metadata) override {
    metadata->insert(std::make_pair("x-custom-auth-ticket", ticket_));
    return grpc::Status::OK;
  }

 private:
  grpc::string ticket_;
};

auto call_creds = grpc::MetadataCredentialsFromPlugin(
    std::unique_ptr<grpc::MetadataCredentialsPlugin>(
        new MyCustomAuthenticator("super-secret-ticket")));

通过将 gRPC 的凭据实现集成到核心层,还可以实现更深层次的集成,甚至将 SSL/TLS 替换为其他加密机制。

语言指南和示例

这些认证机制将会在所有 gRPC 支持的语言中提供。以下表格链接到各语言中展示认证和授权的示例。

语言 示例 文档

C++

N/A

N/A

Go

Go 示例

Go 文档

Java

Java 示例 TLS (Java 示例 ATLS)

Java 文档

Python

Python 示例

Python 文档

OAuth 令牌认证的语言指南和示例

以下表格链接到展示 OAuth 令牌认证和授权的不同语言示例。

语言 示例 文档

C++

N/A

N/A

Go

Go OAuth 示例

Go OAuth 文档

Java

Java OAuth 示例

Java OAuth 文档

Python

Python OAuth 示例

Python OAuth 文档

额外示例

以下部分展示了如何在其他语言中使用上述的认证和授权功能。

Ruby

基本示例 - 无加密或认证

stub = Helloworld::Greeter::Stub.new('localhost:50051', :this_channel_is_insecure)

使用服务器认证 SSL/TLS

creds = GRPC::Core::ChannelCredentials.new(load_certs)  # load_certs 通常加载 CA 根文件
stub = Helloworld::Greeter::Stub.new('myservice.example.com', creds)

与 Google 认证

require 'googleauth'  # 来自 http://www.rubydoc.info/gems/googleauth/0.1.0
...
ssl_creds = GRPC::Core::ChannelCredentials.new(load_certs)  # load_certs 通常加载 CA 根文件
authentication = Google::Auth.get_application_default()
call_creds = GRPC::Core::CallCredentials.new(authentication.updater_proc)
combined_creds = ssl_creds.compose(call_creds)
stub = Helloworld::Greeter::Stub.new('greeter.googleapis.com', combined_creds)

Node.js

基本示例 - 无加密或认证

var stub = new helloworld.Greeter('localhost:50051', grpc.credentials.createInsecure());

使用服务器认证 SSL/TLS

const root_cert = fs.readFileSync('path/to/root-cert');
const ssl_creds = grpc.credentials.createSsl(root_cert);
const stub = new helloworld.Greeter('myservice.example.com', ssl_creds);

使用 Google 认证

// Authenticating with Google
var GoogleAuth = require('google-auth-library'); // from https://www.npmjs.com/package/google-auth-library
...
var ssl_creds = grpc.credentials.createSsl(root_certs);
(new GoogleAuth()).getApplicationDefault(function(err, auth) {
  var call_creds = grpc.credentials.createFromGoogleCredential(auth);
  var combined_creds = grpc.credentials.combineChannelCredentials(ssl_creds, call_creds);
  var stub = new helloworld.Greeter('greeter.googleapis.com', combined_credentials);
});

使用 OAuth2 令牌对谷歌进行身份验证(遗留方法)

var GoogleAuth = require('google-auth-library'); // from https://www.npmjs.com/package/google-auth-library
...
var ssl_creds = grpc.Credentials.createSsl(root_certs); // load_certs typically loads a CA roots file
var scope = 'https://www.googleapis.com/auth/grpc-testing';
(new GoogleAuth()).getApplicationDefault(function(err, auth) {
  if (auth.createScopeRequired()) {
    auth = auth.createScoped(scope);
  }
  var call_creds = grpc.credentials.createFromGoogleCredential(auth);
  var combined_creds = grpc.credentials.combineChannelCredentials(ssl_creds, call_creds);
  var stub = new helloworld.Greeter('greeter.googleapis.com', combined_credentials);
});

使用服务器身份验证 SSL/TLS 和带有令牌的自定义标头

const rootCert = fs.readFileSync('path/to/root-cert');
const channelCreds = grpc.credentials.createSsl(rootCert);
const metaCallback = (_params, callback) => {
    const meta = new grpc.Metadata();
    meta.add('custom-auth-header', 'token');
    callback(null, meta);
}
const callCreds = grpc.credentials.createFromMetadataGenerator(metaCallback);
const combCreds = grpc.credentials.combineChannelCredentials(channelCreds, callCreds);
const stub = new helloworld.Greeter('myservice.example.com', combCreds);

PHP

基本示例 - 无加密或认证

$client = new helloworld\GreeterClient('localhost:50051', [
    'credentials' => Grpc\ChannelCredentials::createInsecure(),
]);

使用服务器认证 SSL/TLS

$client = new helloworld\GreeterClient('myservice.example.com', [
    'credentials' => Grpc\ChannelCredentials::createSsl(file_get_contents('roots.pem')),
]);

使用 Google 认证

function updateAuthMetadataCallback($context)
{
    $auth_credentials = ApplicationDefaultCredentials::getCredentials();
    return $auth_credentials->updateMetadata($metadata = [], $context->service_url);
}
$channel_credentials = Grpc\ChannelCredentials::createComposite(
    Grpc\ChannelCredentials::createSsl(file_get_contents('roots.pem')),
    Grpc\CallCredentials::createFromPlugin('updateAuthMetadataCallback')
);
$opts = [
  'credentials' => $channel_credentials
];
$client = new helloworld\GreeterClient('greeter.googleapis.com', $opts);

使用 OAuth2 令牌对谷歌进行身份验证(遗留方法)

// the environment variable "GOOGLE_APPLICATION_CREDENTIALS" needs to be set
$scope = "https://www.googleapis.com/auth/grpc-testing";
$auth = Google\Auth\ApplicationDefaultCredentials::getCredentials($scope);
$opts = [
  'credentials' => Grpc\Credentials::createSsl(file_get_contents('roots.pem'));
  'update_metadata' => $auth->getUpdateMetadataFunc(),
];
$client = new helloworld\GreeterClient('greeter.googleapis.com', $opts);

Dart

基本示例 - 无加密或认证

final channel = new ClientChannel('localhost',
      port: 50051,
      options: const ChannelOptions(
          credentials: const ChannelCredentials.insecure()));
final stub = new GreeterClient(channel);

使用服务器认证 SSL/TLS

// Load a custom roots file.
final trustedRoot = new File('roots.pem').readAsBytesSync();
final channelCredentials =
    new ChannelCredentials.secure(certificates: trustedRoot);
final channelOptions = new ChannelOptions(credentials: channelCredentials);
final channel = new ClientChannel('myservice.example.com',
    options: channelOptions);
final client = new GreeterClient(channel);

使用 Google 认证

// Uses publicly trusted roots by default.
final channel = new ClientChannel('greeter.googleapis.com');
final serviceAccountJson =
     new File('service-account.json').readAsStringSync();
final credentials = new JwtServiceAccountAuthenticator(serviceAccountJson);
final client =
    new GreeterClient(channel, options: credentials.toCallOptions);

验证单个RPC调用

// Uses publicly trusted roots by default.
final channel = new ClientChannel('greeter.googleapis.com');
final client = new GreeterClient(channel);
...
final serviceAccountJson =
     new File('service-account.json').readAsStringSync();
final credentials = new JwtServiceAccountAuthenticator(serviceAccountJson);
final response =
    await client.sayHello(request, options: credentials.toCallOptions);