💯
【GCS】WebアプリでCloudStorageの画像を安全に表示する
やりたいこと
GoogleCloudStorageにある画像を自Webアプリで表示する。
セキュリティ的にインターネット上で公開する際は"自アプリを通してのみ"見られるようにしたい。
構成と前提
自Webアプリ フロントエンド
- FireBaseでホスティングにて公開済み
- Rect × TypeScript
自Webアプリ バックエンド
- herokuで公開済み
- Go(gin)
ファイルストレージ
- herokuはファイルを置けないのでGoogleCloudStorageで管理している
- GCPのプロジェクト諸々は設定済み
- Googleのサービスアカウント作成済み
対応策
- 署名付きURLを使ってインターネットに公開する
- 署名付きURLの生成にはGoogleのサービスアカウントのプリンシパルを使用する
Google Cloud Storage にある画像を認証済みURLを使って Web アプリで表示する際に、Google のサービスアカウントのプリンシパルを適用する。
そのためには、署名付きURL(Signed URL)を使用する。
署名付きURLを作成することで、指定した期間内に限り、特定のリソース(画像など)に対してアクセス権を付与できる。
このURLはサービスアカウントの認証を基に生成され、他のユーザーもそのURLを使ってリソースにアクセスできます。
署名付きURLの利点
- 認証済みユーザーだけが、限られた期間内にリソースにアクセスでききる
- バケットやオブジェクトに対するグローバルなアクセス権を変更する必要がなく、セキュリティが向上する
- サービスアカウントを使ってバックエンド側で署名付きURLを生成するため、ユーザーが直接認証情報を扱う必要がない(これ嬉しい)
注意点
- 署名付きURLは、生成後の有効期間が過ぎるとアクセスできなくなる。
- ユーザーが直接ブラウザのブックマークに入れてしまわないような構成にしてあげる配慮がいる
- 秘密鍵の管理は慎重に行う。
- 署名付きURLに限った話ではない。(鍵だけに)
- サービスアカウントの認証JSONファイルは安全な場所に保管する必要がある
- herokuでのデプロイの際には工夫が必要。
- これに関しては神記事がある↓
- https://qiita.com/YoshiYamamura/items/e478ca4d50e34ea7ccf4
やり方
署名付きURLをGoで生成する
署名付きURLを生成するためのコードをバックエンドに追加します。storage.SignedURL関数を使用してURLを生成します。
Goバックエンド
package main
import (
"context"
"fmt"
"log"
"time"
"cloud.google.com/go/storage"
"google.golang.org/api/option"
)
func generateSignedURL(bucketName, objectName, credentialFile string) (string, error) {
ctx := context.Background()
// ストレージクライアントを作成
client, err := storage.NewClient(ctx, option.WithCredentialsFile(credentialFile))
if err != nil {
return "", fmt.Errorf("storage.NewClient: %v", err)
}
defer client.Close()
// 署名付きURLのオプションを指定
opts := &storage.SignedURLOptions{
GoogleAccessID: "[YOUR_SERVICE_ACCOUNT]@your-project-id.iam.gserviceaccount.com", // サービスアカウントのメールアドレス
PrivateKey: []byte("[YOUR_PRIVATE_KEY]"), // サービスアカウントの秘密鍵
Method: "GET",
Expires: time.Now().Add(15 * time.Minute), // URLの有効期限
}
// 署名付きURLの生成
url, err := storage.SignedURL(bucketName, objectName, opts)
if err != nil {
return "", fmt.Errorf("storage.SignedURL: %v", err)
}
return url, nil
}
func main() {
bucketName := "your-bucket-name"
objectName := "your-object-name"
credentialFile := "path/to/your-service-account.json"
signedURL, err := generateSignedURL(bucketName, objectName, credentialFile)
if err != nil {
log.Fatalf("Failed to generate signed URL: %v", err)
}
fmt.Printf("Signed URL: %s\n", signedURL)
}
- GoogleAccessID: 署名付きURLを生成するために使用するサービスアカウントのメールアドレス。
- PrivateKey: サービスアカウントの秘密鍵。この鍵は、サービスアカウントのJSONファイル内に含まれています。
- Expires: 署名付きURLの有効期限を指定。ここでは15分間に設定しています。
Webアプリでの画像表示
上記のコードで生成した署名付きURLをフロントエンドに渡し、そのURLを使って画像を表示します。例えば、React で画像を表示する場合は以下のようにします。
Reactフロントエンド
import React, { useState, useEffect } from 'react';
const ImageComponent = () => {
const [imageUrl, setImageUrl] = useState('');
useEffect(() => {
// バックエンドから署名付きURLを取得するリクエスト
fetch('/api/get-signed-url')
.then(response => response.json())
.then(data => {
setImageUrl(data.signedUrl);
})
.catch(error => {
console.error('Error fetching signed URL:', error);
});
}, []);
return (
<div>
{imageUrl ? <img src={imageUrl} alt="Your image" /> : <p>Loading...</p>}
</div>
);
};
export default ImageComponent;
- /api/get-signed-url: バックエンド側で署名付きURLを生成し、フロントエンドに返すAPIエンドポイント。
- imageUrl: 署名付きURLを使用して画像を表示。
Discussion