5
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

時刻ずれを考慮したアクセストークンの有効期間判定

5
Posted at

アクセストークンの有効期間

アクセストークンは、最大で次の三つの時刻関連属性を持ちえます。

  1. 発行時刻
  2. 有効期限開始時刻
  3. 有効期限終了時刻

アクセストークンの情報をイントロスペクションエンドポイントから得たのか、または JWT のペイロードから得たのか、に関わらず、これらの値は iat (Issued At)、nbf (Not Before)、exp (Expiration Time) という名前で参照されます。

名前 イントロスペクション
レスポンス
JWT ペイロード
発行時刻 iat RFC 7662 Section 2.2 RFC 7519 Section 4.1.6
有効期間開始時刻 nbf RFC 7662 Section 2.2 RFC 7519 Section 4.1.5
有効期間終了時刻 exp RFC 7662 Section 2.2 RFC 7519 Section 4.1.4

これらの属性の値により、アクセストークンの有効期間が決まります。次の図は、iatnbfexp が全て指定されている場合のアクセストークン有効期間を示したものです。

access_token_validity_period.png

時刻ずれを考慮したアクセストークンの有効期間

リソースサーバは、現在時刻がアクセストークンの有効期間内であることを確認します。具体的には次の確認を行います。

  1. 発行時刻が現在時刻以前である
  2. 有効期間開始時刻が現在時刻以前である
  3. 有効期間終了時刻が現在時刻より後である

ただし、実際に運用するシステムでは、システム間の時刻ずれ (クロックスキュー) の可能性を考慮して確認を行います。というのは、本来有効であると判定されるべきアクセストークンが、時刻ずれの影響で無効であると判定されることが、現実世界ではよく起こるからです。例えば、アクセストークンを受け取るシステム (リソースサーバ) の時刻が、アクセストークンを発行したシステム (認可サーバ) の時刻よりも遅れていると、「アクセストークンの有効期間がまだ始まっていない (現在時刻が nbf で指定された時刻より前である)」と判定される可能性があります。

「NTP (Network Time Protocol) を使って時刻同期をしていればそういう問題は起こらないはずだ」 という意見も耳にすることもあるでしょうが、現実的には起こります。各国のオープンバンキングエコシステムの長年の運用経験から得られた知見が FAPI 2.0 Security Profile に書かれているので、ここでご紹介します。

NOTE 3: Clock skew is a cause of many interoperability issues. Even a few hundred milliseconds of clock skew can cause JWTs to be rejected for being "issued in the future". The DPoP specification [RFC9449] suggests that JWTs are accepted in the reasonably near future (on the order of seconds or minutes). This document goes further by requiring authorization servers to accept JWTs that have timestamps up to 10 seconds in the future. 10 seconds was chosen as a value that does not affect security while greatly increasing interoperability. Implementers are free to accept JWTs with a timestamp of up to 60 seconds in the future. Some ecosystems have found that the value of 30 seconds is needed to fully eliminate clock skew issues. To prevent implementations switching off iat and nbf checks completely this document imposes a maximum timestamp in the future of 60 seconds.

注3: クロックスキュー (時刻のずれ) は、多くの相互運用性の問題の原因となっています。数百ミリ秒程度のわずかな時刻ずれであっても、JWT が「未来に発行された」と判断されて拒否される可能性があります。DPoP 仕様 ([RFC9449]) では、数秒から数分といった「現実的に近い未来」であれば JWT を受け入れるよう提案されています。本ドキュメントではさらに踏み込んで、認可サーバは最大で 10 秒未来のタイムスタンプを持つ JWT を受け入れることを必須としています。この「10 秒」という値は、セキュリティに影響を与えずに相互運用性を大きく向上させる値として選ばれました。実装者は、最大で 60 秒未来のタイムスタンプを持つ JWT を受け入れても構いません。一部のエコシステムでは、クロックスキュー問題を完全に解消するためには 30 秒が必要であるとされています。なお、本ドキュメントでは、実装が iat (発行時刻) や nbf (有効期間開始時刻) のチェックを完全に無効化することを防ぐために、未来時刻の上限を最大 60 秒とする制限を設けています。

技術的には、時刻ずれの考慮には、判定を厳しくする方向 (有効と判定されにくくなる) と、判定を緩くする方向 (有効と判定されやすくなる) があります。システム運用の現場で求められているのは後者です。これを踏まえ、iatnbf を現在時刻と比較する際は許容する最大時刻ずれ (例えば 10 秒) を引いてから比較をおこない、exp を現在時刻と比較する際は許容する最大時刻ずれを足してから比較をおこないます。

access_token_validity_period_with_clock_skew.png

時刻ずれを考慮した時刻比較処理

コードで考えてみます。

Java の Instant クラスには isBefore(Instant) メソッドと isAfter(Instant) メソッドがあり、それぞれ、Instant インスタンスの表す時刻が引数で与えられた時刻よりも前 (過去) なのか後 (未来) なのかを判定します。

下記のプログラムでは、現在時刻を表す Instant インスタンス (now) を作成し、isBefore メソッドと isAfter メソッドを使って、それより 5 秒前の時刻 (past) と 5 秒後の時刻 (future) との時刻比較をおこなっています。

Test.java
import java.time.Instant;

public class Test
{
    public static void main(String[] args)
    {
        Instant now    = Instant.now();
        Instant past   = now.minusSeconds(5);
        Instant future = now.plusSeconds(5);

        System.out.format("now.isBefore(past)   = %s%n", now.isBefore(past));
        System.out.format("now.isAfter (past)   = %s%n", now.isAfter (past));
        System.out.format("now.isBefore(future) = %s%n", now.isBefore(future));
        System.out.format("now.isAfter (future) = %s%n", now.isAfter (future));
    }
}

このプログラムを実行すると、

java Test.java

次の出力が得られます。

now.isBefore(past)   = false
now.isAfter (past)   = true
now.isBefore(future) = true
now.isAfter (future) = false

現在時刻が過去より前かどうかの判定 (now.isBefore(past)) の結果は期待通りに偽 (false) となっており、また、現在時刻が未来より後かどうかの判定 (now.isAfter(future)) の結果も期待通りに偽 (false) となっています。

Instant クラスはクロックスキューを考慮した時刻比較メソッドを提供していません。ですので、ユーリティティメソッドを下記のように自作します。

/**
 * clockSkew で指定された時刻ずれを許容した上で、
 * 時刻 a が時刻 b より前 (過去) かどうか判定する。
 */
public static boolean isBefore(Instant a, Instant b, Duration clockSkew)
{
    return a.minus(clockSkew).compareTo(b) < 0;
}

/**
 * clockSkew で指定された時刻ずれを許容した上で、
 * 時刻 a が時刻 b より後 (未来) かどうか判定する。
 */
public static boolean isAfter(Instant a, Instant b, Duration clockSkew)
{
    return a.plus(clockSkew).compareTo(b) > 0;
}

下記のプログラムでは、上記のメソッド群を用い、10 秒の時刻ずれを許容して時刻比較を行なっています。

ClockSkewTest.java
import java.time.Duration;
import java.time.Instant;

public class ClockSkewTest
{
    public static void main(String[] args)
    {
        Instant  now    = Instant.now();
        Instant  past   = now.minusSeconds(5);
        Instant  future = now.plusSeconds(5);
        Duration skew   = Duration.ofSeconds(10);

        System.out.format("isBefore(now, past,   skew) = %s%n", isBefore(now, past,   skew));
        System.out.format("isAfter (now, past,   skew) = %s%n", isAfter (now, past,   skew));
        System.out.format("isBefore(now, future, skew) = %s%n", isBefore(now, future, skew));
        System.out.format("isAfter (now, future, skew) = %s%n", isAfter (now, future, skew));
    }

    public static boolean isBefore(Instant a, Instant b, Duration clockSkew)
    {
        return a.minus(clockSkew).compareTo(b) < 0;
    }

    public static boolean isAfter(Instant a, Instant b, Duration clockSkew)
    {
        return a.plus(clockSkew).compareTo(b) > 0;
    }
}

このプログラムを実行すると、

java ClockSkewTest.java

次の出力が得られます。

isBefore(now, past,   skew) = true
isAfter (now, past,   skew) = true
isBefore(now, future, skew) = true
isAfter (now, future, skew) = true

今回は判定結果が全て真 (true) になっています。10 秒の時刻ずれを許容しているので、本来の比較対象時刻から 5 秒分範囲外であっても、範囲内と判定されるからです。

比較の際、同時刻の場合も許容したいことがあります。そのためのユーティリティメソッドも用意しておくとよいでしょう。

/**
 * clockSkew で指定された時刻ずれを許容した上で、
 * 時刻 a が時刻 b と等しいか、または、より前 (過去) かどうか判定する。
 */
public static boolean isOnOrBefore(Instant a, Instant b, Duration clockSkew)
{
    return a.minus(clockSkew).compareTo(b) <= 0;
}

/**
 * clockSkew で指定された時刻ずれを許容した上で、
 * 時刻 a が時刻 b と等しいか、また、より後 (未来) かどうか判定する。
 */
public static boolean isOnOrAfter(Instant a, Instant b, Duration clockSkew)
{
    return a.plus(clockSkew).compareTo(b) >= 0;
}

おわりに

本記事ではアクセストークンの有効期間を題材としましたが、有効期間の判定が必要な箇所は他にも多くあります。例えば、認可サーバの実装では『OAuth/OIDCのJWTまとめ』に列挙されている数々の JWT の有効期間判定処理が必要になります。

商用品質のソフトウェアを書く際は時刻ずれを考慮しましょう!

本記事の先頭の 2 節の内容は、Authlete 社のウェブサイト上で公開されている文書『標準仕様による徹底的な API 保護』から抜粋したものです。

5
2
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
5
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?