astamuse Lab

astamuse Labとは、アスタミューゼのエンジニアとデザイナーのブログです。アスタミューゼの事業・サービスを支えている知識と舞台裏の今を発信しています。

昭和二十年のデータ・サイエンティスト

大変ご無沙汰しております。じんと申します。テック・ブログの担当と相成りまして、今回は技術の裏側の思想について考えてみたいと思います。

今回の主人公は堀栄三という人です。詳しくはウィキペディアを参照していただければと思いますが、戦中から戦後にかけて優れた情報分析者として足跡を残しました。彼の著書に『情報なき国家の悲劇 大本営参謀の情報戦記』というものがあります。これはデータに関わる技術者の必読書としてもいいのではないか、とも思われるもので、昔からの愛読書の一つです。

本書は一読するたびに大きな学びがあり、その感想は到底ここで書き尽くせるものではありませんが、今回取り上げてみたい問いは以下のようになります。それは「どうすれば堀栄三のような優れた分析者が生じうるのか?」というものです。これは自分自身が彼のような精密な分析能力をどのようにすれば得られるのか、と考える面もありますし、彼のような分析者をどのように育成できるのか、と考える面もあります。

様々な要素があり、天賦の才というものもあるでしょうし、彼の父が陸軍の幹部であったことも大きな要素として働いているように思えます。一方、彼自身が言及しているように、彼自身が異なる分析姿勢を横断的に経験している点が非常に重要な要素として抽出できるのではないか、と考えるようになり、しばらくそれを整理していました。本書では、大きく3カ国の対する諜報が俎上に上げられています。米国、ソ連、そして当時の同盟国であるドイツです。

大前提として、堀は「情報の究極は権力の中枢から出てくる」と断言しています。権力とはすなわち意思決定の主体ですから、その意思がすなわち情報に他ならず、彼のこの考えは正鵠を射たものと言えましょう。

さて、まずドイツですが、当然、同盟国ですので、情報は比較的入手しやすいものです。ドイツの場合、当時の駐独大使などが、政権内部の要人に対して直接的に聞き取りを行うことができます。すなわち「人」を経由して情報が入ってきます。詳細な情報を得やすい一方、当該情報は同盟国ということもあり、なんらかのバイアスが含まれる危険性があります。一言でいいますと、「盛ってある」可能性が大きいわけです。また、同盟国であるため、どうしてもドイツから提供される情報を心情的に信用しやすくなってしまいます。

次にソ連です。当時は日ソ中立条約下にありますが、ソ連は日本の同盟国ドイツと死闘を繰り広げているなかでもあり、ソ連への諜報はとても難しいものでした。到底要人への聞き取りなど不可能です。したがって、新聞記事、雑誌をはじめとした報道、シベリア鉄道の貨物輸送量、要人の発言内容の過去と現在の違いなど、入手可能な情報を細かく積み上げ、それを多面的に分析していきます。また、当時の陸軍の仮想敵国はそもそもソ連でしたから、ソ連課のアプローチは、相手を端から信用していません。常に情報を疑っています。そのため、精度の高い情報を得るために、様々なデータの収集に余念がありません。ソ連の場合、「データ」を経由して情報を入手していたと言えるでしょう。

最後に米国です。米国に対しては既に開戦していましたから、ソ連と同様に、ドイツのように要人に直接聞き取りを行うことなどはできませんし、ソ連以上に情報収集は難しくなります。さらに状況が悪いことに、当時の陸軍は対ソ戦を重視していましたから、米国に対する分析は不十分なものでした。そこで、当時の堀の上司であった米国担当の杉田大佐は、米国担当を命じられた彼にさっそく現地の様子を見てくるように命じます。彼は驚愕しつつも、ニューギニアのウェワクに出張し、そこで現地司令官の寺本中将から現地の様子を詳しく聞き取ります。ウェワクに到着したまさにその日に彼は早速空襲に晒されます。これはまさに「現場」経由で情報を入手していると言えるでしょう。

まとめると以下のようになるかと思います。

f:id:astamuse:20211008173708p:plain
ドイツ型・ソ連型・米国型のまとめ

堀自身は上司を「親方」、自分を「職人」と書いています。優れた複数の親方から、様々な分析手法を吸い上げ、それを実践、それどころか実戦で磨き上げたことが冒頭の問いへの一つの回答となるでしょう。おそらくこのなかの一つでも欠いたら情報参謀・堀栄三は完成しなかったでしょう。「人」と「データ」だけでは生々しい「現場」はわからず、「人」と「現場」だけでは俯瞰的な「データ」はわからず、「データ」と「現場」だけでは枢要な「人」はわかりません。

東京に戻った堀は様々な情報源から収集した情報を多角的に分析していきます。収集した情報を基に対米戦における基本的な戦術を確立させ、南方戦線に抽出される関東軍部隊に対して対応方針を教授します。この米軍の上陸作戦に対する対応方針は驚嘆すべきもので、データに基づく上陸前の空襲の分類、実際的な数字に基づく彼我戦力の比較など、何やら学術論文を読むような思いがあります。何よりも、彼は『敵軍戦法早わかり』という冊子によって対応方針、すなわち結論を導き出しています。彼は単なる分析者ではなく、体系的な方針を組み立てることができたのでした。

フィリピンでの決戦が近づくなか、堀はフィリピンの山下将軍の情報参謀としてマニラに向かいます。その間、台湾沖航空戦での誤った戦果発表を指摘したものの、それに伴う戦略の誤った変更を止めることはできませんでした。後世から見れば失敗を繰り返していく軍のなかで、堀は山下将軍を補佐し、最善を尽くします。レイテ島で敗北を喫したのち、ルソン島の山下将軍に殉じる覚悟であった堀は、東京に送り返され、本土決戦に向けた諜報に粉骨砕身します。その結果は後世の我々には明らかですが、彼の努力には感銘を受けざるを得ません。

とまれ、データ・サイエンスというものを、一定の規模のデータから何らかの価値、究極的には意思決定をもたらすものであるとすると、常に、そのデータは信用できるのか、意思決定に必要なデータを集め切れているのか、という懸念が生じます。私自身も十分なデータを集め切れているのか、と懸念を感じることが多々あります。これに対して堀は明快に回答しています。

コンピューターが出来て、もうそんな事態はあり得ないと思う人もいるだろうが、今度はインプットするデーターが百パーセント揃っている保証がない。〇・〇一パーセントでも誤差があれば、株式相場のブラックマンデーは必ず起ることになる。

ーー堀栄三. 情報なき国家の悲劇 大本営参謀の情報戦記. 文春文庫, 1996年.

戦後、警察予備隊が陸上自衛隊に改組されて間もない1956年、スエズ動乱が発生します。陸上自衛隊に参画していた堀は、冷戦下にあった米ソの全面衝突の可能性について上司から見解を求められます。堀の判断は「衝突には至らない」というものでした。この判断は堀の上司が上司自身の見解として提出しなければなりません。顧問会議などを作り、その責任の所在を曖昧にしたい、とこぼす上司に堀は冷然と言い放ちます。

「顧問を作っても同じですよ、情報の判断には、百パーセントのデーターが集まることは不可能です。三十パーセントでも、四十パーセントでも白紙の部分は常にあります。この空白の霧の部分を、専門的な勘と、責任の感とで乗り切る以外にありません」

ーー堀栄三. 情報なき国家の悲劇 大本営参謀の情報戦記. 文春文庫, 1996年.

何らかの判断のために情報を収集し、見解を提示する仕事をここしばらく多く行っています。誤った判断は組織に惨禍をもたらし、その可能性を考えるとこれは必ずしも気安い作業ではありません。そんなとき、常に彼のことを考えています。最後にまた冒頭の問いに戻ります。幽明相隔てた彼に直接尋ねることはできませんが、熱心に本書を読み返し、彼の思考を辿り、現代の「データ・サイエンス」を踏まえて、彼に「どうすればあなたのようになれますか?」と尋ねてみます。彼は冷然と一言、言い放つのではないか、と想像しています。「顧問」を「ビッグ・データとAI」と置き換えて。

しかし、0.01%の誤差まで努力した結果は無駄ではない、専門家としての矜恃と責任感を持つ限り、と彼は付け加えます。その時の彼は、私の覚悟を問うように、冷厳に私の眼を見据えています。

データの収集と解析、分析は重要です!アスタミューゼ株式会社ではエンジニアを募集しています!ひりつくようなデータエンジニアリングやデータサイエンスに対して、我こそは、という方からのご連絡をお待ちしております。

簡易的にデータリネージを試してみる

はじめまして。データチームのKimy(@yuu_kimy)です。
日々、各種データの整備に関わる開発を行っています。
早いもので、アスタミューゼにジョインしてから、1年が過ぎました。

ジョインしてから、グラント(研究助成)や特許データの整備開発、各種案件対応を行ってきましたが、最近では、特許の概念や用語の勉強の日々です。
だいぶ慣れてきたかなと感じていますが、やっぱり、難しいですね..

さて、案件対応では、蓄積されたデータを活用して、各種データの加工・集計等を行っていますが、様々なデータを利用することで、データのインプット・アウトプットの流れが分かり辛くなってくることがあります。
利用するテーブルが数個であれば、そのデータを処理するソースコードを見れば、パッと流れが掴めますが、数多くのテーブルを利用して、集計して、集計した結果を別に処理をした結果とジョインして、更に、フィルターをかけて..etc となってくると、処理が複雑となり、大まかな流れが掴み辛くなってきます。

データマネジメント領域で言う「データリネージ」(どのデータを利用して、どう生成されているかが明らかになっていること)ができると何かと便利そうなので、簡易的なデータリネージにトライしてみたいと思います。

今回は、SQLに対するデータリネージをやってみます。
(SQLであれば、何でも大丈夫だと思います。)

データリネージとは?

前述の通り、「どのデータを使って、どういう風に生成しているか」といったデータの流れが整理・可視化されることを指します。また、データの処理における変換も対象となります。
データリネージにより、データガバナンスにおける色々な検証を可能にするかと思いますが、それだけで話が多岐に渡りそうなので、詳細は割愛させて頂きます。

Pythonモジュールの「sqllineage」について

Pythonのモジュールに、sqllineage なるツールを聞きつけたので、試しに使ってみたいと思います。
このツールは、SQLを解析して、ソーステーブルやターゲットテーブルの情報を教えてくれる便利なものです。(裏側では、sqlparseというSQLパーサが利用されているようです。)

早速、使ってみる

インストールは、非常に簡単です。以下を実行するだけです。

pip install sqllineage

インストール後は、ドキュメントにあるように、以下のようにコマンドを実行すれば、SQLの解析結果を表示してくれます。

sqllineage -e "insert into table_foo 
    select * from table_bar union select * from table_baz"

結果は、以下のように表示されます。

Statements(#): 1
Source Tables:
    <default>.table_bar
    <default>.table_baz
Target Tables:
    <default>.table_foo

SQLに書かれている通り、ソーステーブルの"table_bar" と "table_baz"、ターゲットテーブルの"table_foo"が表示されています。

SQLを解析してみる

それでは、SQLを解析してみます。コマンドに、SQLの全てを書くのは面倒なので、クエリファイルに保存してから、先ほどと同様に、コマンドを実行します。

# 検証用SQL: target_sql_1.sqlとして保存
# 注意: 以下のSQLは、あくまで、今回のサンプル用に用意したものです。(以下同様)

CREATE TABLE output.final_result_table AS
WITH intermediate_table AS (
    SELECT
        input_table_1.original_col_1,
        input_table_2.original_col_2,
        LEFT(input_table_3.original_col_3, 4) AS converted_col_3
    FROM input.input_table_1 AS input_table_1
        INNER JOIN input.input_table_2 AS input_table_2
            ON input_table_1.dummy_common_col_1
                = input_table_2.dummy_common_col_1
                AND input_table_2.dummiy_flag = '1'
        INNER JOIN input.input_table_3 AS input_table_3
            ON input_table_2.dummy_common_col_2
                = input_table_3.dummy_common_col_2
)

SELECT
    input_table_5.original_col_5,
    input_table_6.original_col_6,
    input_table_7.original_col_7,
    ROUND(input_table_8.original_col_8 /
        input_table_9.original_col_9) AS round_result_1,
    intermediate_table.original_col_1,
    intermediate_table.original_col_2,
    intermediate_table.converted_col_3
FROM input.input_table_5 AS input_table_5
    LEFT JOIN input.input_table_6 AS input_table_6
        ON input_table_5.dummy_common_col_5
            = input_table_6.dummy_common_col_5
    LEFT JOIN input.input_table_7 AS input_table_7
        ON input_table_6.dummy_common_col_6
            = input_table_7.dummy_common_col_6
    LEFT JOIN input.input_table_8 AS input_table_8
        ON input_table_7.dummy_common_col_7
            = input_table_8.dummy_common_col_7
    LEFT JOIN input.input_table_9 AS input_table_9
        ON input_table_8.dummy_common_col_8
            = input_table_9.dummy_common_col_8
    LEFT JOIN intermediate_table
        ON input_table_9.dummy_common_col_9
            = intermediate_table.dummy_common_col_9

上記のSQLを解析してみます。クエリファイルを指定する場合は、-f のオプションを指定します。

sqllineage -f target_sql_1.sql

結果は以下の通りです。

Statements(#): 1
Source Tables:
    input.input_table_1
    input.input_table_2
    input.input_table_3
    input.input_table_5
    input.input_table_6
    input.input_table_7
    input.input_table_8
    input.input_table_9
Target Tables:
    output.final_result_table

WITH句で利用したテーブルのinput_table_1〜3も、きちんと、ソーステーブルとして表示されているようです。

更に、詳細な情報が欲しい場合は、-v オプションも指定して、実行します。(-v -fの順番で指定する必要があるようです。)

次は、以下のSQLで解析を実行してみます。

## 検証用SQL: target_sql_2.sqlとして保存
INSERT INTO output.final_result_table_2
SELECT
  input_table_1.original_col_1,
  converted_table.common_col_2_1,
  converted_table.converted_rank,
  input_table_3.original_col_3
FROM
  input.input_table_1 AS input_table_1
INNER JOIN
   (
        SELECT DISTINCT
            common_col_2_1,
            common_col_2_2,
            RANK() OVER (
                PARTITION BY common_col_2_1 ORDER BY common_col_2_2
            ) AS converted_rank
        FROM input.input_table_2
    ) AS converted_table
    ON
      input_table_1.common_col_1 = converted_table.common_col_1
INNER JOIN
  input.input_table_3 AS input_table_3
    ON input_table_1.common_col_2 = input_table_3.common_col_2
WHERE
    input_table_3.dummy_flag = '1'
    AND input_table_3.dummy_date <= '2021-09-21'

以下のように実行します。

sqllineage -v -f target_sql_2.sql

結果は、以下の通りです。

Statement #1: INSERT INTO output.final_result_table_2SELECT  inp...
    table read: [Table: input.input_table_1, Table: input.input_table_2, Table: input.input_table_3]
    table write: [Table: output.final_result_table_2]
    table rename: []
    table drop: []
    table intermediate: []
==========
Summary:
Statements(#): 1
Source Tables:
    input.input_table_1
    input.input_table_2
    input.input_table_3
Target Tables:
    output.final_result_table_2

サブクエリの中で呼び出している input_table_2 も、きちんと、ソーステーブルに含まれているようです。 -v オプションを付けることで、SQLのステートメントごとのREAD/WRITE等を表示してくれるようですね。

更には、SQLの解析結果を可視化することも可能です。

# -gオプションをつけて実行する
sqllineage -g -f target_sql/target_sql_5.sql

実行すると、リンクが表示されますので、ブラウザからアクセスすると、以下のような描画が表示されます。
f:id:astamuse:20210921230253p:plain

描画されたテーブルのノードにカーソルを当てると、ハイライトしてくれるようですね。 f:id:astamuse:20210921230602p:plain

SQLのテーブルのインプットとアウトプットの流れが、パッと見て、分かる状態になりました。

まとめ

sqllineageを利用することで、SQLの流れを容易に掴むことができました。今回のサンプルでは、シンプルなSQLでしたが、更に、複雑なSQLだと、その威力を発揮してくれそうです。
ただ、あくまで、テーブルのインプットとアウトプットの流れを掴むことがメインのため、カラムレベルまで踏み込んだデータリネージではないようです。

また、注意ですが、sqllineage (その裏側で動いているsqlparse)は、SQL自体の妥当性をチェックしているわけではないので、本来は、エラーが発生するようなSQLでも、リネージが表示されてしまいます。その場合、想定した解析結果にならないケースもあり得るため、注意が必要なようです。

アスタミューゼでは、エンジニア・デザイナーを募集中です。ご興味のある方は、弊社の採用サイトからご応募頂けたらと思います。

ご応募をお待ちしておりますm( )m

データベースを使わずに、有効期限付きURLを生成したい

自称データエンジニアのaranです。 月日の流れは早いもので、去年の9月以来の再登板になります

私ごとですが
先月に健康診断があり、試しに1日1食の生活を3ヶ月ほど実施してみました。

ストイックに毎日続けるのは無理なので、以下の条件下で試しました

  • 1日1食だが、好きなだけ食べる

  • 土日は食事制限しない

  • ガマンできない時はチョコレート(一口分)を食べてよい。ただし3口分まで

  • みんなと食事するときは、お昼を食べてよい

開始当初は、お腹がなりまくっていたのですが、しばらくするとお腹はならなくなります。 また、一番の体の変化は、昼以降に眠くならないことです。
(前日の睡眠時間次第のところはありますが)

友人によく、朝食べないと体にエネルギーがなく、パフォーマンスが...的なことを言われました。
ただ、私に限ったことですが、まったく問題なかったです。
むしろ、昼以降に眠くならず、集中力が続くので、作業効率はUPしたような気がします

健康診断の結果は改善した項目があり、私にとって1日3食は食べ過ぎなんだなって思いました。

はじめに

わたしのここ最近の業務はデータ整備で
構造化データをパースし、データ整形してデータ登録する いわゆるETL作業を行っております。

楽しく作業している傍ら、データソース元は公開データでかつ、閉じた環境で作業していることもあり
セキュリティに無頓着になっています。。

こんな状況の中とある案件で
簡単に改ざんできないような有効期限付きURLが必要になり
久しぶりにセキュリティを意識することになりました。

今回は、有効期限付きURLをどう生成したかについてお話したいと思います。

有効期限付きURLについて

有効期限付きURLが必要の際
URLにパラメータを入れて、そのURLと有効期限をデータベースに登録する方法を
主に採用すると思います。

当案件の要件(制約)は、データベース利用せずに有効期限付きURLを生成することで
これって賢人が既にやっているだろうと思い、ちょっと調べていたら
賢人のブログを発見しました。

このブログのサンプルソースは PHP なのですが
当案件では、python(ver 3.8)を利用しているので、一部移植してみました。

手始めに

今回は参考サイトの手法以外も試しています。

AES方式を使って有効期限付きURLを生成

まず、AES方式を使って有効期限付きURLを生成してみます。

簡単な手順は以下になります

  1. 秘密鍵を事前に生成する

  2. 有効期限を暗号化

  3. 暗号化した有効期限をURLパラメータ化

  4. 受け取ったパラメータを復号化

  5. 有効期限のチェック

暗号化・復号化には PyCryptodome ライブラリを利用しました。

pycryptodome.readthedocs.io

最初は PyCrypto ライブラリを利用していましたが
ドキュメントを読むと2013年10月以降アップデートが止まっているので、
PyCryptoの利用は控えました。

尚、暗号化・復号化の処理はこちらのソースを参考しています

有効期限付きURL生成するサンプルコードです。

サンプルコード

import datetime
import base64
from urllib import parse

from Crypto import Random
from Crypto.Cipher import AES
from Crypto.Util import Padding

SECRET_KEY: bytes = b'秘密鍵を生成し、適宜管理して下さい'
BASE_URL: str = 'http://example.com'


def generate_url() -> str:
    def __encrypt(raw_data: str) -> str:
        iv: bytes = Random.get_random_bytes(AES.block_size)
        cipher = AES.new(SECRET_KEY, AES.MODE_CBC, iv)
        pad_data = Padding.pad(raw_data.encode('utf-8'), AES.block_size, 'pkcs7')
        return base64.b64encode(iv + cipher.encrypt(pad_data)).decode()

    # 有効期限の設定
    expiry: datetime.datetime = datetime.datetime.now() + datetime.timedelta(minutes=5)
    expiry_string: str = str(int(expiry.timestamp()))
    # 有効期限の暗号化
    param_expiry: str = __encrypt(expiry_string)
    # 有効期限付きURL生成
    return f"{BASE_URL}?expiry={param_expiry}"

こちらを実行すると、以下のように有効期限つきURLを生成できます

>>> generate_url()
https://example.com?expiry=N934g0hrNZp4weCvIpYsTw1psEgGIwW6T4NMSVjDI6E=

生成した有効期限付きURLをパースしてみます。

サンプルコード

# import文は上記と同じなので、省略

def validate_url(url: str):
    def __parse_url(url: str) -> str:
        qs: str = parse.urlparse(url).query
        q: dict = parse.parse_qs(qs)

        expiry_from_request: str = q['expiry'][0]
        return expiry_from_request

    def __decrypt(enc_data: str) -> str:
        enc: bytes = base64.b64decode(enc_data)
        iv: bytes = enc[:AES.block_size]
        cipher = AES.new(SECRET_KEY, AES.MODE_CBC, iv)
        unpad_data: bytes = Padding.unpad(cipher.decrypt(enc[AES.block_size:]), AES.block_size, 'pkcs7')
        return unpad_data.decode('utf-8')

    def __now_ts() -> int:
        return int(datetime.datetime.now().timestamp())

    # URLパラメータのパース
    expiry_from_request: str = __parse_url(url)
    # 有効期限の復号化
    expiry_ts: int = int(__decrypt(expiry_from_request))

    if expiry_ts < __now_ts():
        print('期限切れURL')

有効期限付きURLをチェックしてみます。

>>> url: str = 'https://example.com?expiry=N934g0hrNZp4weCvIpYsTw1psEgGIwW6T4NMSVjDI6E='
>>> validate_url(url)
期限切れURL

有効期限の時間経過後にチェックしたところ、期限切れを確認できました。

ハッシュ(HMAC)を使って有効期限付きURLを生成

次にブログにある
ハッシュ(HMAC)を使って、有効期限付きURLを生成したいと思います。

簡単な手順は以下になります

  1. 秘密鍵を事前に生成する

  2. 有効期限をシリアライズ

  3. 共有鍵を生成する

  4. 導出鍵を生成する

  5. 有効期限、共有鍵、導出鍵をURLパラメータ化

  6. 受け取ったパラメータを復号化

  7. 改ざんチェック

  8. 有効期限をデシリアライズ

  9. 有効期限チェック

尚、ブログでPythonにはない関数を利用していますので、完全な移植ではありませんし、
一部処理を簡素化しています。

サンプルコード

有効期限付きURLを生成するサンプルコードです。

import base64
import datetime
import hashlib
import hmac
import secrets
import pickle
from typing import Tuple
from urllib import parse

def generate_url() -> str:
    def __generate_salt() -> bytes:
        return secrets.token_hex(16).encode('utf-8')

    def __generate_context(expiry: str) -> bytes:
        return pickle.dumps((base_url, expiry))

    def __generate_derived_key(salt: bytes, context: bytes) -> bytes:
        prk: bytes = hmac.new(SECRET_KEY, salt, hashlib.sha256).digest()
        return hmac.new(prk, context, hashlib.sha256).digest()

    # 有効期限の設定
    expiry: datetime.datetime = datetime.datetime.now() + datetime.timedelta(minutes=5)
    expiry_string: str = str(int(expiry.timestamp()))
    # コンテクスト(URL, 有効期限)
    context: bytes = __generate_context(expiry=expiry_string)
    # 公開鍵の生成
    salt: bytes = __generate_salt()
    # 導出鍵
    derived_key: bytes = __generate_derived_key(salt=salt, context=context)

    param_key1: str = base64.b64encode(derived_key).decode()
    param_key2: str = base64.b64encode(salt).decode()
    param_context: str = base64.b64encode(context).decode()
    # 有効期限付きURL生成
    return f"{BASE_URL}?key1={param_key1}&key2={param_key2}&context={param_context}"

生成した有効期限付きURLをチェックするサンプルコードです。

サンプルコード

# import文は上記と同じため、省略

def validate_url(url: str):
    def __parse_url(url: str) -> Tuple[bytes, bytes, bytes]:
        qs: str = parse.urlparse(url).query
        q: dict = parse.parse_qs(qs)

        # TODO: デコード時にエラーが発生する場合がありますが、今回はその対策を省略しています

        # 送られてきた導出鍵
        encoding_derived_key = q['key1'][0]
        derived_key_from_request: bytes = base64.b64decode(encoding_derived_key)

        # 送られてきた共有鍵
        encoding_salt = q['key2'][0]
        salt_from_request: bytes = base64.b64decode(encoding_salt)

        # 送られてきたコンテクスト(URL, 有効期限)
        encoding_context = q['context'][0]
        context_from_request: bytes = base64.b64decode(encoding_context)

        return (derived_key_from_request, salt_from_request, context_from_request)

    def __validate_request(derived_key_from_request: bytes, salt_from_request: bytes, context_from_request: bytes) -> bool:
        validate_prk: bytes = hmac.new(SECRET_KEY, salt_from_request, hashlib.sha256).digest()
        validate_derived_key: bytes = hmac.new(validate_prk, context_from_request, hashlib.sha256).digest()

        return hmac.compare_digest(derived_key_from_request, validate_derived_key)

    def __now_ts() -> int:
        return int(datetime.datetime.now().timestamp())

    def __extracted_expiry_ts(context_from_request: bytes) -> int:
        deserialize_contents: Tuple['str', 'str'] = pickle.loads(context_from_request)
        return int(deserialize_contents[1])

    # リクエストパラメータのパース
    derived_key_from_request, salt_from_request, context_from_request = __parse_url(url)

    if not __validate_request(derived_key_from_request, salt_from_request, context_from_request):
        raise Exception("不正なリクエスト")

    # 有効期限の抽出
    expiry_ts: int = __extracted_expiry_ts(context_from_request)
    if expiry_ts < __now_ts():
        print('期限切れURL')

最後に

参考にしたブログに書いてある通り、データベースを使わないので、大量の有効期限付きURLを発行しても
アクセス管理ができることがメリットだとわかりました。
要件次第になりますが、選択肢のひとつになるかと思います。

アスタミューゼでは、エンジニア・デザイナーを募集中です。 ご興味のある方は遠慮なく採用サイトからご応募願います。是非、お待ちしています。

Copyright © astamuse company, ltd. all rights reserved.