文章來自gRPC 官方文檔中文版
認證
gRPC 被設計成可以利用插件的形式支持多種授權機制。本文檔對多種支持的授權機制提供了一個概覽,並且用例子來論述對應API,最后就其擴展性作了討論。 馬上將會推出更多文檔和例子。
支持的授權機制
SSL/TLS
gRP 集成 SSL/TLS 並對服務端授權所使用的 SSL/TLS 進行了改良,對客戶端和服務端交換的所有數據進行了加密。對客戶端來講提供了可選的機制提供憑證來獲得共同的授權。
OAuth 2.0
gRPC 提供通用的機制(后續進行描述)來對請求和應答附加基於元數據的憑證。當通過 gRPC 訪問 Google API 時,會為一定的授權流程提供額外的獲取訪問令牌的支持,這將通過以下代碼例子進行展示。 警告:Google OAuth2 憑證應該僅用於連接 Google 的服務。把 Google 對應的 OAuth2 令牌發往非 Google 的服務會導致令牌被竊取用作冒充客戶端來訪問 Google 的服務。
API
為了減少復雜性和將混亂最小化, gRPC 以一個統一的憑證對象來進行工作。 憑證可以是以下兩類:
- 頻道憑證, 被附加在
頻道
上, 比如 SSL 憑證。 - 調用憑證, 被附加在調用上(或者 C++ 里的
客戶端上下文
)。 憑證可以用組合頻道憑證
來進行組合。一個組合頻道憑證
可以將一個頻道憑證
和一個調用憑證
關聯創建一個新的頻道憑證
。結果在這個頻道上的每次調用會發送組合的調用憑證
來作為授權數據。 例如,一各頻道憑證
可以由一個Ssl 憑證
和一個訪問令牌憑證
生成。結果是在這個頻道上的每次調用都會發送對應的訪問令牌。調用憑證
可以用組合憑證
來組裝。組裝后的調用憑證
應用到一個客戶端上下文
里,將觸發發送這兩個調用憑證
的授權數據。
服務端認證加密使用的 SSL/TLS
這是個最簡單的認證場景:一個客戶端僅僅想認證服務器並且加密所有數據。
// Create a default SSL ChannelCredentials object.
auto channel_creds = grpc::SslCredentials(grpc::SslCredentialsOptions());
// Create a channel using the credentials created in the previous step.
auto channel = grpc::CreateChannel(server_name, creds);
// Create a stub on the channel.
std::unique_ptr<Greeter::Stub> stub(Greeter::NewStub(channel));
// Make actual RPC calls on the stub.
grpc::Status s = stub->sayHello(&context, *request, response);
對於高級的用例比如改變根 CA 或使用客戶端證書,可以在發送給工廠方法的 SslCredentialsOptions 參數里的相應選項進行設置。
通過 Google 進行認證
gRPC應用可以使用一個簡單的API來創建一個可以工作在不同部署場景下的憑證。
auto creds = grpc::GoogleDefaultCredentials();
// Create a channel, stub and make RPC calls (same as in the previous example)
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 計算引擎 (GCE)里運行的應用一樣使用服務賬號。在前面的案例里,服務賬號的密鑰從環境變量 GOOGLE_APPLICATION_CREDENTIALS
對應的文件里加載。這些密鑰被用來生成承載令牌附加在在相應頻道的每次 RPC 調用里。 對於 GCE 里運行的應用,可以在虛擬機設置的時候為其配置一個默認的服務賬號和相應的 OAuth2 范圍。在運行時,這個憑證被用來與認證系統通訊來獲取 OAuth2 訪問令牌並且把令牌用作在相應的頻道上的 RPC 調用。
擴展 gRPC 支持其他的認證機制
相應的憑證插件 API 允許開發者開發自己的憑證插件。
MetadataCredentialsPlugin
抽象類包含需要被開發者創建的子類實現的純虛方法GetMetadata
。MetadataCredentialsFromPlugin
方法可以從MetadataCredentialsPlugin
創建一個調用者憑證
。 這類有個簡單的憑證插件例子,是通過在自定義頭了設置一個認證票據。
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 的憑證實現以插件的形式集成進核心層。gRPC 內部也允許用其他加密機制來替換 SSL/TLS 。
例子
這些授權機制將會在所有 gRPC 支持的語言里提供。以下的一些節里展示了上文提到的認證和授權在每種語言里如何實現:很快將會推出更多語言的支持。
通過 SSL/TLS 進行服務端授權和加密(Ruby)
# Base case - No encryption
stub = Helloworld::Greeter::Stub.new('localhost:50051', :this_channel_is_insecure)
...
# With server authentication SSL/TLS
creds = GRPC::Core::Credentials.new(load_certs) # load_certs typically loads a CA roots file
stub = Helloworld::Greeter::Stub.new('localhost:50051', creds)
通過 SSL/TLS 進行服務端授權和加密 (C#)
// Base case - No encryption/authentication
var channel = new Channel("localhost:50051", ChannelCredentials.Insecure);
var client = new Greeter.GreeterClient(channel);
...
// With server authentication SSL/TLS
var channelCredentials = new SslCredentials(File.ReadAllText("roots.pem")); // Load a custom roots file.
var channel = new Channel("myservice.example.com", channelCredentials);
var client = new Greeter.GreeterClient(channel);
通過 SSL/TLS 進行服務端授權和加密 (Python)
from grpc.beta import implementations
import helloworld_pb2
# Base case - No encryption
channel = implementations.insecure_channel('localhost', 50051)
stub = helloworld_pb2.beta_create_Greeter_stub(channel)
...
# With server authentication SSL/TLS
creds = implementations.ssl_channel_credentials(open('roots.pem').read(), None, None)
channel = implementations.secure_channel('localhost', 50051, creds)
stub = helloworld_pb2.beta_create_Greeter_stub(channel)
通過 Google 進行授權 (Ruby)
基本案例 - 無加密/授權
stub = Helloworld::Greeter::Stub.new('localhost:50051', :this_channel_is_insecure)
用無限制憑證進行授權 (推薦途徑)
require 'googleauth' # from http://www.rubydoc.info/gems/googleauth/0.1.0
...
ssl_creds = GRPC::Core::ChannelCredentials.new(load_certs) # load_certs typically loads a CA roots file
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)
用 OAuth2 令牌進行認證(傳統途徑)
require 'googleauth' # from http://www.rubydoc.info/gems/googleauth/0.1.0
...
ssl_creds = GRPC::Core::ChannelCredentials.new(load_certs) # load_certs typically loads a CA roots file
scope = 'https://www.googleapis.com/auth/grpc-testing'
authentication = Google::Auth.get_application_default(scope)
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)
通過 Google 進行授權 (Node.js)
基本案例 - 無加密/授權
var stub = new helloworld.Greeter('localhost:50051', grpc.credentials.createInsecure());
用無限制憑證進行授權 (推薦途徑)
// 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);
});
通過 Google 進行授權 (C#)
基本案例 - 無加密/授權
var channel = new Channel("localhost:50051", ChannelCredentials.Insecure);
var client = new Greeter.GreeterClient(channel);
...
用無限制憑證進行授權 (推薦途徑)
using Grpc.Auth; // from Grpc.Auth NuGet package
...
// Loads Google Application Default Credentials with publicly trusted roots.
var channelCredentials = await GoogleGrpcCredentials.GetApplicationDefaultAsync();
var channel = new Channel("greeter.googleapis.com", channelCredentials);
var client = new Greeter.GreeterClient(channel);
...
用 OAuth2 令牌進行認證(傳統途徑)
using Grpc.Auth; // from Grpc.Auth NuGet package
...
string scope = "https://www.googleapis.com/auth/grpc-testing";
var googleCredential = await GoogleCredential.GetApplicationDefaultAsync();
if (googleCredential.IsCreateScopedRequired)
{
googleCredential = googleCredential.CreateScoped(new[] { scope });
}
var channel = new Channel("greeter.googleapis.com", googleCredential.ToChannelCredentials());
var client = new Greeter.GreeterClient(channel);
...
授權一個 gRPC 調用
var channel = new Channel("greeter.googleapis.com", new SslCredentials()); // Use publicly trusted roots.
var client = new Greeter.GreeterClient(channel);
...
var googleCredential = await GoogleCredential.GetApplicationDefaultAsync();
var result = client.SayHello(request, new CallOptions(credentials: googleCredential.ToCallCredentials()));
...
通過 Google 進行授權 (PHP)
基本案例 - 無加密/授權
$client = new helloworld\GreeterClient('localhost:50051', [
'credentials' => Grpc\ChannelCredentials::createInsecure(),
]);
...
用無限制憑證進行授權 (推薦途徑)
Authenticate using scopeless credentials (recommended approach)
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);
通過 Google 進行授權 (Python)
基本案例 - 無加密/授權
channel = implementations.insecure_channel('localhost', 50051)
stub = helloworld_pb2.beta_create_Greeter_stub(channel)
...
用 OAuth2 令牌進行認證(傳統途徑)
transport_creds = implementations.ssl_channel_credentials(open('roots.pem').read(), None, None)
def oauth2token_credentials(context, callback):
try:
credentials = oauth2client.client.GoogleCredentials.get_application_default()
scoped_credentials = credentials.create_scoped([scope])
except Exception as error:
callback([], error)
return
callback([('authorization', 'Bearer %s' % scoped_credentials.get_access_token().access_token)], None)
auth_creds = implementations.metadata_plugin_credentials(oauth2token_credentials)
channel_creds = implementations.composite_channel_credentials(transport_creds, auth_creds)
channel = implementations.secure_channel('localhost', 50051, channel_creds)
stub = helloworld_pb2.beta_create_Greeter_stub(channel)