🐡

認可サーバーを開発してみた

2023/05/07に公開

認可サーバーを開発してみました。
CIBAのスペックもサポートしています。

Gitリポジトリ

https://github.com/hirokazu-kobayashi-koba-hiro/idp-server

モチベーション

ライブラリーやサービスの実装をみても、コードから仕様が見えてこなかったので、自分で実装してスペックの仕様をコードで表現しようと思った。

OAuthには拡張仕様がいくつもあるので、各スペックをサポートすることで拡張性の高い設計が身につくと考えた。

コンセプト

  • OAuth関連スペックを全てサポート(まだ途中)
  • ライブラリーで全てハンドリング
  • スペックに定義のないユーザー管理、認証は利用側で自由に実装

サポートスペック

  • RFC6749 The OAuth 2.0 Authorization Framework
    1. authorization code flow
    2. implicit flow
    3. client credentials flow
  • OpenID Connect Core 1.0 incorporating errata set 1
    1. authorization code flow
    2. implicit flow
    3. hybrid flow
    4. request object
    5. userinfo
  • OpenID Connect Discovery 1.0 incorporating errata set 1
  • OpenID Connect Client-Initiated Backchannel Authentication Flow - Core 1.0
    1. poll mode
  • RFC7009 OAuth 2.0 Token Revocation
  • RFC7636 Proof Key for Code Exchange by OAuth Public Clients
  • RFC7662 OAuth 2.0 Token Introspection

利用可能なクライアント認証

  • client_secret_post
  • client_secret_basic
  • client_secret_jwt
  • private_key_jwt

サポートしているスペックの正常系は一通り動作します。
バリデーションの基盤を実装していて、一部必須チェクなど実装している状況です。

実装方法

SpringBootでの実装例となります。

IdpServerApplication

IdpServerApplicationをBean登録します。

@Value("${idp.configurations.basePath}")
  String configurationBasePath;

  @Bean
  public IdpServerApplication idpServerApplication() {
    List<String> serverPaths = List.of(configurationBasePath + "/server.json");
    List<String> clientPaths = new ArrayList<>();
    clientPaths.add(configurationBasePath + "/clients/clientSecretBasic.json");
    clientPaths.add(configurationBasePath + "/clients/clientSecretPost.json");
    clientPaths.add(configurationBasePath + "/clients/clientSecretJwt.json");
    clientPaths.add(configurationBasePath + "/clients/privateKeyJwt.json");
    return new IdpServerApplication(new MemoryDataSourceConfig(serverPaths, clientPaths));
  }

現時点では、DataSourceがオンメモのみとなっています。

API

Cibaのバックチャンネル認証リクエストAPIの場合の実装例となります。

package org.idp.sample;

import java.util.Map;
import org.idp.server.CibaApi;
import org.idp.server.IdpServerApplication;
import org.idp.server.handler.ciba.io.*;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("{tenant-id}/api/v1/backchannel/authentications")
public class CibaV1Api implements ParameterTransformable {

  CibaApi cibaApi;
  UserService userService;

  public CibaV1Api(IdpServerApplication idpServerApplication, UserService userService) {
    this.cibaApi = idpServerApplication.cibaApi();
    this.userService = userService;
  }

  @PostMapping
  public ResponseEntity<?> request(
      @RequestBody(required = false) MultiValueMap<String, String> body,
      @RequestHeader(required = false, value = "Authorization") String authorizationHeader,
      @PathVariable("tenant-id") String tenantId) {
    Map<String, String[]> params = transform(body);
    Tenant tenant = Tenant.of(tenantId);
    CibaRequest cibaRequest = new CibaRequest(authorizationHeader, params, tenant.issuer());
    CibaRequestResponse response = cibaApi.request(cibaRequest, userService);
    HttpHeaders httpHeaders = new HttpHeaders();
    httpHeaders.add("Content-Type", response.contentTypeValue());
    return new ResponseEntity<>(
        response.contents(), httpHeaders, HttpStatus.valueOf(response.statusCode()));
  }

各リクエストをそのままライブラリーのDTOに設定し、Apiクラスのメソッドを実行します。

メソッドの戻り値を利用して、APIのレスポンスを組み立てます。

  • response body: response.contents()
  • content type: response.contentTypeValue()
  • status code: response.statusCode()

Sample Server

サンプルサーバーで動作確認をすることができます。

bootRun

./gradlew bootRun

e2e

cd e2e
jest test

今後の予定

  1. 各スペックのバリデーションの実装
  2. OIDC認定テストでの動作確認
  3. RARのサポート
  4. DataSourceの拡充

Discussion