はじめに
Spring Security プロジェクトは、認可サーバーの実装を今後サポートしない旨、2019 年 11 月 14 日付の『Spring Security OAuth 2.0 Roadmap Update』でアナウンスしました。しかしその後、一部の有志が Spring の認可サーバー実装プロジェクト『Spring Authorization Server』を開始しました。この記事は、そのプロジェクトに対する警鐘となります。
OAuth 2.0 Multiple Response Type Encoding Practices
2012 年 10 月の RFC 6749(The OAuth 2.0 Authorization Framework)のリリースから 1 年 4 ヶ月後の 2014 年 2 月、OAuth 2.0 Multiple Response Type Encoding Practices(以降 OAuth.Responses)という仕様が 2014 年 2 月に公開されました。その 9 ヶ月後の 2014 年 11 月に公開された OpenID Connect Core 1.0 は、当仕様を大前提としています。
公開日 | 仕様 |
---|---|
2012 年 10 月 | RFC 6749(The OAuth 2.0 Authorization Framework) |
2014 年 02 月 | OAuth 2.0 Multiple Response Type Encoding Practices |
2014 年 11 月 | OpenID Connect Core 1.0 |
OAuth.Responses が認可サーバーの実装に与える影響は甚大で、Spring Security OAuth が行き詰まった理由の一つだと私は考えています(参考:Issue #619 Handling additional response_types)。
OAuth.Responses により、認可リクエストの response_type
パラメーターの処理方法が大きく変わりました。それまでは、つまりは RFC 6749 の時点では、「response_type
の値は code
または token
のどちらかになる」という前提で認可サーバーの実装を書いていればよかったのですが、OAuth.Responses 以降は、「response_type
は、code
/id_token
/token
の任意の組合せ、もしくは単独の none
」という複雑な状況に対応しなければならなくなりました。
response_type | |
---|---|
RFC 6749 時点 |
code もしくは token
|
OAuth.Responses 以降 |
code /id_token /token の任意の組合せ、もしくは単独の none
|
仕様上、RFC 6749 は response_type
に複数の値を入れることを許容しています。しかし、認可レスポンスパラメーターを置く場所に関して、code
と token
で仕様が衝突するため、衝突の解決方法が提示されていない段階では(OAuth.Responses が規定されるまでは)、code
と token
が同時に response_type
に含まれることを想定することは困難でした。
RFC 6749 の時点では、response_type
の処理の実装は、下記の仮想コードのように、response_type
の値が特定の値と「一致するかどうか」を調べていれば済みました。
if (response_type == 'code') {
// 認可コードフロー (RFC 6749 Section 4.1)
// 認可コードを発行する
}
else if (response_type == 'token') {
// インプリシットフロー (RFC 6749 Section 4.2)
// アクセストークンを発行する
}
else {
// response_type の値が不正
}
一方、OAuth.Responses 以降は、response_type
に特定の値が「含まれているか」を調べる必要がでてきます。
if ('code' in response_type) {
// 認可コードを発行する
}
if ('token' in response_type) {
// アクセストークンを発行する
}
if ('id_token' in response_type) {
// ID トークンを発行する
}
if ('none' in response_type) {
// 何も発行しない. response_type に none 以外が
// 含まれていればエラー.
}
response_type
に複数の値が含まれるということは、認可エンドポイントから複数のトークンが発行されうるということを意味します。例えば response_type
が code id_token token
の場合、認可エンドポイントからは認可コード、ID トークン、アクセストークン、の三つが発行されます。詳細は『OpenID Connect 全フロー解説』を参照してください。
response_type | 認可コード | ID トークン | アクセストークン |
---|---|---|---|
code |
|||
token |
|||
id_token |
|||
id_token token |
|||
code id_token |
|||
code token |
|||
code id_token token |
|||
none |
ID トークンと同時に認可コードやアクセストークンが発行される場合、それぞれのハッシュ値を c_hash
クレームや at_hash
クレームの値として ID トークンに含めなければならないというルールなどもあり(OIDC Core 1.0 Section 3.3.2.11)、OAuth.Responses により認可エンドポイントの実装はかなり複雑になります。
Spring Authorization Server の実装
Spring Security が認可サーバーの実装を今後サポートしないことを発表してから約 9 ヶ月後の 2020 年 8 月 21 日、Spring.IO のブログ『Get the very first bits of Spring Authorization Server 0.0.1 !』により、Spring 用の認可サーバー実装のバージョン 0.0.1 がアナウンスされました。この実装に大きな期待を寄せている方々もいらっしゃるでしょう。
この実装の response_type
の処理は OAuth2AuthorizationEndpointFilter.java
にあり、現時点では次のようになっています。
// response_type (REQUIRED)
if (!StringUtils.hasText(authorizationRequestContext.getResponseType()) ||
authorizationRequestContext.getParameters().get(OAuth2ParameterNames.RESPONSE_TYPE).size() != 1) {
authorizationRequestContext.setError(
createError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.RESPONSE_TYPE));
return;
} else if (!authorizationRequestContext.getResponseType().equals(OAuth2AuthorizationResponseType.CODE.getValue())) {
authorizationRequestContext.setError(
createError(OAuth2ErrorCodes.UNSUPPORTED_RESPONSE_TYPE, OAuth2ParameterNames.RESPONSE_TYPE));
return;
}
ご覧の通り、OAuth.Responses 仕様を念頭に置いていないことは明らかで、Spring Security OAuth プロジェクトの OpenID Connect サポートが行き詰まったのと同じ過ちを繰り返しています。
このコードが参照している OAuth2AuthorizationResponseType.CODE
ですが、これは Spring Security 本体に含まれています。OAuth2AuthorizationResponseType
クラスには現在、CODE
と TOKEN
の 2 つのクラス変数が定義されています。少なくともここに ID_TOKEN
が入ってこないと、新 Spring Authorization Server プロジェクトで OpenID Connect をサポートすることはできないでしょう。
OAuth2AuthorizationEndpointFilter.java
の認可リクエストパラメーターの処理順序を見ると、scope
よりも前に redirect_uri
がチェックされています。この処理順序ですと、「scope
に openid
が含まれる場合は redirect_uri
が必須」という OpenID Connect Core 1.0 の要求事項のチェックができません。新 Spring Authorization Server プロジェクトが OpenID Connect を考慮していないことが分かります。
当然、OpenID Connect Core 1.0 で規定されているリクエストオブジェクトの実装は含まれていません。しかし、リクエストオブジェクトはリクエストパラメーターの処理に大きな影響を与えるので、設計段階から考慮に入れておく必要があります。なお、Financial-grade API(FAPI)の Part 2 でリクエストオブジェクトが必須になっていることからも分かるとおり、高いセキュリティが要求される環境では、リクエストオブジェクトの実装が必要です。リクエストオブジェクトの詳細は『JAR (JWT Secured Authorization Request) に関する実装者の覚書』を参照してください。
何年も前に明らかになっている問題点を繰り返していること、ソースコードの読みにくさ、認可画面の HTML を OAuth2AuthorizationEndpointFilter.java
にハードコーディングするセンス、そもそも認可エンドポイントでの処理を『フィルター』として実装しようとするアプローチ、を鑑みれば、新 Spring Authorization Server の実装品質が良くないことは明らかです。
新 Spring Authorization Server プロジェクトに期待をしている方々には、Spring の名を過信せず、ソースコードの品質を確認した上で利用の可否を判断するよう、助言させていただきたく思います。
代替プロジェクト
オープンソースに限っても他に選択肢はいろいろあります。中には、正式に OpenID 認証を取得し、商用レベルの品質のものもあります。OpenID Foundation のウェブサイトにライブラリ・製品・ツールのリストがあるので、そちらで要件に合うものを探されるのが良いと思います。