🐡
認可サーバーを開発してみた
認可サーバーを開発してみました。
CIBAのスペックもサポートしています。
Gitリポジトリ
モチベーション
ライブラリーやサービスの実装をみても、コードから仕様が見えてこなかったので、自分で実装してスペックの仕様をコードで表現しようと思った。
OAuthには拡張仕様がいくつもあるので、各スペックをサポートすることで拡張性の高い設計が身につくと考えた。
コンセプト
- OAuth関連スペックを全てサポート(まだ途中)
- ライブラリーで全てハンドリング
- スペックに定義のないユーザー管理、認証は利用側で自由に実装
サポートスペック
- RFC6749 The OAuth 2.0 Authorization Framework
- authorization code flow
- implicit flow
- client credentials flow
- OpenID Connect Core 1.0 incorporating errata set 1
- authorization code flow
- implicit flow
- hybrid flow
- request object
- userinfo
- OpenID Connect Discovery 1.0 incorporating errata set 1
- OpenID Connect Client-Initiated Backchannel Authentication Flow - Core 1.0
- 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
今後の予定
- 各スペックのバリデーションの実装
- OIDC認定テストでの動作確認
- RARのサポート
- DataSourceの拡充
Discussion