17
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Rust で Google サービスアカウントの認証 (Sheets からデータ取得サンプル)

Posted at

なにかと社内のデータが入ってるといえば Google スプレッドシートですが、社内ツールを Rust で作ってみようとした際、スプシ操作するクレートの google_sheets4 というのだと、 Google サービスアカウントでの認証には対応していなかったので、直接アクセストークンを取って REST API を叩く形で実装しました。

アクセストークンを取り方がわかれば他の G Suite や Map や GCP や Firebase などの API も操作できるので、その部分のメモを残しておきます。

認証の仕様は Using OAuth 2.0 for Server to Server Applications に書いてあって、流れは Hosting REST API を使用してサイトにデプロイするがわかりやすくて、よくわからないところは Google API クライアント ライブラリの実装をみてやりました。

JWT を作る

serde_json を使って環境変数に入ってるサービスアカウントの 秘密鍵 JSON をパースします。

Cargo.toml
[dependencies]
serde = { version = "^1", features = ["derive"] }
serde_json = "^1"
use std::env;
use serde::{Deserialize, Serialize};

#[derive(Debug, Deserialize)]
struct GoogleCredential {
    r#type: String,
    project_id: String,
    private_key_id: String,
    private_key: String,
    client_email: String,
    client_id: String,
    auth_uri: String,
    token_uri: String,
    auth_provider_x509_cert_url: String,
    client_x509_cert_url: String,
}

fn main() {
    let google_credential: GoogleCredential = serde_json::from_str(&env::var("GOOGLE_CREDENTIAL").unwrap()).unwrap();

chrono を使ってJWTの発行時刻と有効期限のタイムスタンプを作ります。

Cargo.toml
[dependencies]
chrono = "^0.4"
use chrono::{Duration, Utc};

fn main() {
    let now = Utc::now();
    let iat = now.timestamp();
    let exp = (now + Duration::minutes(60)).timestamp();

jsonwebtoken を使って JWT を作成します。ヘッダ、クレームセットの仕様はこちらに記述されています。
スコープに何を指定するかは、使いたい API のドキュメントに書いてあります。 ()

Cargo.toml
[dependencies]
serde = { version = "^1", features = ["derive"] }
jsonwebtoken = "^7"
use serde::{Deserialize, Serialize};
use jsonwebtoken::{encode, Header, Algorithm, EncodingKey};

#[derive(Debug, Serialize)]
struct Claims {
    iss: String,
    scope: String,
    aud: String,
    exp: i64,
    iat: i64,
}

fn main() {
    let mut header = Header::default();
    header.typ = Some("JWT".to_string());
    header.alg = Algorithm::RS256;

    let my_claims =
        Claims { 
            iss: google_credential.client_email,
            scope: "https://www.googleapis.com/auth/spreadsheets".to_string(),
            aud: google_credential.token_uri,
            exp: exp,
            iat: iat,
        };

    let jwt = encode(&header, &my_claims, &EncodingKey::from_rsa_pem(google_credential.private_key.as_bytes()).unwrap()).unwrap();

アクセストークンを取得する

serde_json と reqwest を使ってトークンリクエストを投げます。リクエストの仕様は上記同様こちらに記述されています。
AWS Lambda などシステムネイティブ TLS が使えない環境で動かすことを想定しています。

Cargo.toml
[dependencies]
serde_json = "^1"

[dependencies.reqwest]
version = "^0.10"
default-features = false
features = ["blocking", "json", "rustls-tls"]
use serde_json::json;
use reqwest::blocking::Client;

fn main() {
    let token_body = json!({
        "grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
        "assertion": jwt
    });
    
    let token_response = Client::new()
        .post(&my_claims.aud)
        .json(&token_body)
        .send()
        .unwrap();

serde_json を使ってレスポンスボディの JSON を構造体にしないパターンでパースしてアクセストークンを取り出します。

Cargo.toml
[dependencies]
serde_json = "^1"
use serde_json::Value;

fn main() {
    let token_response_body: Value = token_response.json().unwrap();
    let access_token = token_response_body.get("access_token").unwrap().as_str().unwrap();

これでアクセストークンが作成できたので、あとは使いたいサービスの API を叩く感じです。
API Library にあるものは大体いけるんじゃないですかね。

(サンプル) スプシのシートの値を取得する

このようなシートを用意して、
Screen Shot 2020-07-26 at 19.48.59.png
値を取得する場合は Method: spreadsheets.values.get を叩きます。

use serde_json::Value;
use reqwest::blocking::Client;

fn main() {
    let sheets_response = Client::new()
        .get("https://sheets.googleapis.com/v4/spreadsheets/{your spreadsheet id}/values/A1:C")
        .bearer_auth(access_token)
        .send()
        .unwrap();

    let values = sheets_response.json::<Value>().unwrap().get("values").unwrap());
    // values: [["A1","B1","C1"],["A2","B2","C2"]]

という感じでとれました。

以上です。
Rust よく分かってないので、ご指摘・アドバイス等あればよろしくお願いします🙇

17
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?