Quarkusにおける認可の実装方法

2024/10/07

Quarkusにおける認可の実装方法

Quarkusにおける認可の方法は、公式の次のページに書かれています。

https://quarkus.io/guides/security-jwt

ただ、認可の種類には以下の3つがあると考えられ、そのうち1-iの場合の説明となっています。

  1. 通常のRBACで良い場合
    1. IdPで定義したRoleを元に、JWT中のRoleで判断するので問題ない場合: IdPのRoleを元に@RolesAllowedなどのアノテーションを用いる
    2. 権限設定の変更を即座に反映したい場合: 動的にSecurityContextをセットし、そこで定義したRoleを元に@RolesAllowedなどのアノテーションを用いる
  2. 認可の判断がリソースに基づくなど、動的に変わったりAPIの種類によって認可ロジックが異なる場合: ContainerRequestFilterで動的に処理する

それ以外のケースについては詳しく書かれているものが探した限りは見つからなかったです。 今回は2の場合の私達の実現方法を紹介させていただきます。

方法

具体的な実装は以下のようなイメージです(一部改変しています)

@Provider
class AuthFilter : ContainerRequestFilter {
    @Inject lateinit var authorizationService: AuthorizationService
    @Inject lateinit var userIdentity: UserIdentity

    override fun filter(requestContext: ContainerRequestContext) {
        requestContext.uriInfo.pathParameters.getFirst("customerId")?.let { customerId ->
            authorizationService.run(customerId.toLong(), userIdentity.user).let {
                if (!it) {
                    throw ForbiddenProblem(
                        problemDetail = "当該顧客に対する権限がありません",
                    )
                }
            }
        }
        return
    }
}

Jakarta RESTのContainerRequestFilterを用いて実現しています。 パスパラメータを使用したいのとユーザー情報を用いたいため、PostMatchフィルターを使うのがポイントの一つです。 パスパラメータを取得することができるので、API定義で{customerId}が存在するAPIについては、 リクエスト時のcustomerIdを取得し、それを元にAuthorizationServiceを呼び出しログインユーザーに当該customerに対して権限があるかチェックしています。 ある場合はtrueが返ってくるので、ない場合は例外を飛ばしています。

お気づきの方もいるかと思いますが、個々のAPI実装の最初の部分に同じロジックを呼び出す方法でも勿論同様に実現できます。 ただ、入れ忘れのリスクがあるのと、入れる工数も些細ですがかかります。 この方法では、パスパラメータ名さえ間違えなければAPI側で意識しなくても個々のAPIに導入されるというメリットがあります。 また、この例ではcustomerですが、他のエンティティについては別のロジックがある場合、また別のFilterを定義すれば実現できます。