Skip to content
このページは中国語の原文から翻訳されましたが、その後原文が更新されているため、内容が古い可能性があります。正本は中国語版です。 中国語の原文を見る →

OIDC / OAuth2 連携 公開中 ​

Lurus の統一 ID 認証は Casdoor をベースとし、標準的な OIDC / OAuth2 インターフェースを公開しています。アプリが標準 OIDC に対応していれば、コア認証ロジックを変更することなくそのまま Lurus SSO に接続できます。

1Discovery URL 自動検出
9標準エンドポイント
5Grant Type / Flow
S256PKCE 必須方式

クイックスタート ​

ほとんどの OIDC SDK は Discovery に対応しており、URL を 1 つ指定するだけで、すべてのエンドポイント、アルゴリズム、機能を自動的に取得できます。

Discovery URL: https://identity.lurus.cn/.well-known/openid-configuration

SDK の初期化時に(エンドポイントをハードコードするのではなく)この URL を直接指定すれば、サーバー側で鍵がローテーションされたりエンドポイントが変更されたりしても、アプリを修正する必要はありません。

最小構成

client_id + redirect_uri + Discovery URL — この 3 つが揃えば認可コードフローを開始できます。

標準エンドポイント ​

すべてのエンドポイントは https://identity.lurus.cn を Base URL とします。

エンドポイント名パスHTTP メソッド用途
Discovery/.well-known/openid-configurationGETサービスメタデータ。SDK がすべてのエンドポイントとアルゴリズムを自動的に読み取る
Authorization/oauth/v2/authorizeGET / POST認可フローを開始し、ログインページへリダイレクト
Token/oauth/v2/tokenPOSTaccess_token / id_token / refresh_token を取得
UserInfo/oidc/v1/userinfoGETaccess token を使って現在のユーザーの claims を読み取る
JWKS/oauth/v2/keysGETJWK 公開鍵セットを取得し、ローカルで JWT を検証
Introspection/oauth/v2/introspectPOSTtoken の有効性とメタ情報を照会(サーバー側で使用)
Revocation/oauth/v2/revokePOSTaccess / refresh token を失効させる
End Session/oidc/v1/end_sessionGET / POSTログアウト:Casdoor セッションを終了
Device Authorization/oauth/v2/device_authorizationPOSTDevice Code Flow の開始エンドポイント

Authorization エンドポイントのパラメータ ​

パラメータ必須説明
client_id✓コンソールでアプリを作成すると取得できる
redirect_uri✓コンソールに登録した URI と完全に一致する必要がある
response_type✓認可コードフローでは固定で code
scope✓最低限 openid を含む。複数はスペース区切り
state推奨CSRF 対策。コールバック時に元の値を返す
nonce推奨リプレイ対策。id_token に書き込まれる
code_challengePKCE 必須Base64URL(SHA-256(code_verifier))
code_challenge_methodPKCE 必須固定で S256
prompt任意login で強制再ログイン;consent で同意ページを強制表示
login_hint任意ユーザー名を事前入力し、ログインを高速化

サポートする Grant Type / Flow ​

クライアントの種類に応じて認可フローを選択します。SPA / Native / Web では Authorization Code + PKCE を推奨します。

Flow適用シーン推奨度
Authorization Code + PKCESPA、Native App、Web App✓ 第一選択
Client CredentialsM2M、バックエンドサービス✓ M2M シーン
Device CodeCLI、TV、IoT、ブラウザなしデバイス✓ 特殊デバイス
Refresh Token長期セッション、サイレント更新offline_access scope と併用
JWT Bearer(Service User)サービスアカウント。署名済み JWT で token を取得サービスアカウントのシーン

Authorization Code + PKCE の 6 ステップ:

  1. クライアントが code_verifier を生成(ランダムな 43〜128 文字)。
  2. code_challenge = Base64URL(SHA-256(verifier)) を計算。
  3. /oauth/v2/authorize へリダイレクト(challenge 付き)。
  4. ユーザーがログインして認可し、コールバックに code が付与される。
  5. POST /oauth/v2/token(code + code_verifier 付き)。
  6. access / id / refresh token を取得。

Client Credentials:POST /oauth/v2/token with grant_type=client_credentials + client_id + client_secret + scope=openid urn:casdoor:iam:org:project:id:{projectid}:aud → access_token を取得(id_token なし、ユーザー身元なし)。

Refresh Token:初回認可時に scope へ offline_access を含める → refresh_token を安全に保管 → access_token の失効時に POST grant_type=refresh_token + refresh_token=<token> → 新しい token を取得(refresh_token はローテーションされる場合あり)。


Authorization Code + PKCE 完全な例 ​

TypeScript ​

typescript
import { createHash, randomBytes } from "crypto";

// Step 1: 生成 PKCE pair
function generatePKCE() {
  const verifier = randomBytes(32).toString("base64url");
  const challenge = createHash("sha256").update(verifier).digest("base64url");
  return { verifier, challenge };
}

// Step 2: 构造 Authorization URL(持久化 verifier + state 到 sessionStorage)
function buildAuthorizeURL(clientId: string, redirectUri: string): string {
  const { verifier, challenge } = generatePKCE();
  sessionStorage.setItem("pkce_verifier", verifier);
  const state = randomBytes(16).toString("base64url");
  sessionStorage.setItem("oauth_state", state);
  const params = new URLSearchParams({
    response_type: "code", client_id: clientId, redirect_uri: redirectUri,
    scope: "openid profile email offline_access", state,
    nonce: randomBytes(16).toString("base64url"),
    code_challenge: challenge, code_challenge_method: "S256",
  });
  return `https://identity.lurus.cn/oauth/v2/authorize?${params}`;
}

// Step 3: 回调处理 — 验证 state,提取 code
function handleCallback(callbackURL: string) {
  const url = new URL(callbackURL);
  const code = url.searchParams.get("code")!;
  const state = url.searchParams.get("state")!;
  if (state !== sessionStorage.getItem("oauth_state")) {
    throw new Error("State mismatch — possible CSRF attack");
  }
  return code;
}

// Step 4: 用 code 换 tokens
async function exchangeCode(code: string, clientId: string, redirectUri: string) {
  const verifier = sessionStorage.getItem("pkce_verifier")!;
  const resp = await fetch("https://identity.lurus.cn/oauth/v2/token", {
    method: "POST",
    headers: { "Content-Type": "application/x-www-form-urlencoded" },
    body: new URLSearchParams({
      grant_type: "authorization_code", code, redirect_uri: redirectUri,
      client_id: clientId, code_verifier: verifier,
    }),
  });
  if (!resp.ok) throw new Error(`Token exchange failed: ${await resp.text()}`);
  // 获得 { access_token, id_token, refresh_token, expires_in }
  return await resp.json();
}

curl ​

bash
# code_challenge = Base64URL(SHA-256(code_verifier))
CODE_VERIFIER="dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"
CODE_CHALLENGE=$(echo -n "$CODE_VERIFIER" | sha256sum | cut -d' ' -f1 | xxd -r -p | base64 | tr '+/' '-_' | tr -d '=')

# 引导用户访问登录 URL
echo "https://identity.lurus.cn/oauth/v2/authorize?response_type=code\
&client_id=YOUR_CLIENT_ID&redirect_uri=https://yourapp.example.com/callback\
&scope=openid%20profile%20email%20offline_access&state=random_state_value\
&code_challenge=${CODE_CHALLENGE}&code_challenge_method=S256"

# 用回调中的 code 换 tokens
curl -s -X POST https://identity.lurus.cn/oauth/v2/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=authorization_code" \
  -d "code=AUTHORIZATION_CODE_FROM_CALLBACK" \
  -d "redirect_uri=https://yourapp.example.com/callback" \
  -d "client_id=YOUR_CLIENT_ID" -d "code_verifier=${CODE_VERIFIER}" | jq .
# 响应:{ "access_token", "token_type":"Bearer", "expires_in":43199, "id_token", "refresh_token" }

Scopes 一覧 ​

標準 OIDC scopes は返却される claim を決定し、Casdoor 固有の scopes は audience、ロール、組織制約を制御します。

標準 Scopes ​

Scope説明影響する Token
openid必須。OIDC リクエストを宣言し、id_token を返すid_token
profilename、given_name、family_name、preferred_username、locale を取得id_token, userinfo
emailemail、email_verified を取得id_token, userinfo
phonephone_number、phone_number_verified を取得id_token, userinfo
addressユーザーの住所情報を取得id_token, userinfo
offline_accessrefresh_token を要求(Authorization Code フローでのみ有効)—

Casdoor 固有 Scopes ​

Scope説明影響する Token
urn:casdoor:iam:org:project:id:{projectid}:aud指定した project ID を access token の aud に追加。サーバー側の検証で一致が必須access_token
urn:casdoor:iam:org:project:id:casdoor:audCasdoor 自身の project ID を aud に追加(Casdoor API へのアクセス用)access_token
urn:casdoor:iam:org:projects:rolestoken に、認可されたすべてのプロジェクトのロール一覧を含めるid_token, access_token, userinfo
urn:casdoor:iam:org:project:role:{rolekey}特定のロール claim のみを要求(例:...:role:admin)id_token, access_token
urn:casdoor:iam:org:id:{orgid}ユーザーが当該組織に所属していることを必須とする。組織をまたぐログインを強制的に分離検証用
urn:casdoor:iam:org:domain:primary:{domain}ユーザーが所属する組織のプライマリドメインを限定(例:...:primary:lurus.cn)検証用
urn:casdoor:iam:user:metadatatoken にユーザー独自の metadata を含める(Base64 のキー・バリュー)id_token, access_token, userinfo
urn:casdoor:iam:user:resourceownerユーザーが所属する組織の ID、名前、プライマリドメインを取得id_token, access_token, userinfo
urn:casdoor:iam:org:idp:id:{idp_id}指定した IdP(企業微信、飛書)へ直接遷移し、IDP 選択ページをスキップ挙動制御

よく使う組み合わせ(Web App):openid profile email offline_access urn:casdoor:iam:org:projects:roles urn:casdoor:iam:org:project:id:{projectid}:aud


Claims 一覧 ​

下表は、各 claim がどの token に現れるか、およびどの scope に依存するかを示しています。

標準 Claims ​

Claim説明id_tokenaccess_tokenuserinfo依存する Scope
subユーザー一意 ID(Casdoor 内部 ID)✓✓ (JWT)✓常時
issIssuer。固定で https://identity.lurus.cn✓✓—常時
audAudience。アプリの client_id✓✓—常時
exp / iat失効 / 発行時刻(Unix)✓✓—常時
auth_timeユーザーが実際にログインした時刻✓——常時
nonceリプレイ対策のランダム値✓——常時(指定時)
amr認証方式。例:["pwd"]、["mfa"]✓——常時
name / given_name / family_nameフルネーム / 名 / 姓✓*—✓profile
preferred_usernameログイン名(username@primarydomain)✓—✓profile
locale / updated_at言語設定(例:zh)/ 情報の更新時刻✓*—✓profile
email / email_verifiedメールアドレス / 検証済みか(Boolean)✓*—✓email

✓* = response_type に id_token を含む場合、または明示的に要求した場合にのみ返却。

Casdoor 固有 Claims ​

Claim説明id_tokenaccess_tokenuserinfo
urn:casdoor:iam:org:project:rolesユーザーのプロジェクトロール。構造は { "roleName": { "orgId": "domain" } }✓✓ (JWT)✓
urn:casdoor:iam:org:domain:primaryユーザーが所属する組織のプライマリドメイン✓✓ (JWT)✓
urn:casdoor:iam:user:metadataユーザー独自の metadata。{ "key": "base64value" }✓✓ (JWT)✓
urn:casdoor:iam:user:resourceowner:id / :name / :primary_domainユーザーが所属する組織の ID / 名前 / プライマリドメイン✓✓ (JWT)✓

ロール claim の例:

json
{ "urn:casdoor:iam:org:project:roles": { "admin": { "178204173316174381": "lurus.cn" }, "viewer": { "178204173316174381": "lurus.cn" } } }

Metadata claim の例(value は Base64 で、使用時には atob() / base64.StdEncoding.DecodeString() でデコードが必要):

json
{ "urn:casdoor:iam:user:metadata": { "department": "ZW5naW5lZXJpbmc=", "employee_id": "VTEwMDEy" } }

Token の検証 ​

サーバー側で Bearer token を受け取った際、形式だけで有効性を判断してはいけません。必ず以下を実施してください:

1. 从 JWKS 拉公钥(建议缓存 TTL 1小时): GET https://identity.lurus.cn/oauth/v2/keys
2. 用匹配 kid 的公钥验证 JWT 签名
3. 校验标准 claims:iss == "https://identity.lurus.cn";aud 含本应用 client_id 或 project_id;
   exp > now();nbf <= now()(如有)
4. 按需校验业务 claims(角色、组织 ID)

推奨ライブラリ ​

言語ライブラリインストール
TS / Node.jsoidc-client-tsbun add oidc-client-ts
TS / Node.jsjose(軽量な署名検証)bun add jose
Gogo-oidcgo get github.com/coreos/go-oidc/v3
Pythonpython-josepip install python-jose
Elixir / Phoenixlurus-phoenix(プラットフォーム内部ライブラリ)2l-bs-admin を参照

Go 署名検証の例 ​

go
package auth

import (
    "context"
    "fmt"
    "github.com/coreos/go-oidc/v3/oidc"
)

var provider *oidc.Provider

func Init(ctx context.Context) error {
    var err error
    // SDK 自动从 Discovery URL 加载配置和 JWKS
    provider, err = oidc.NewProvider(ctx, "https://identity.lurus.cn")
    return err
}

func VerifyAccessToken(ctx context.Context, rawToken, clientID string) (*oidc.IDToken, error) {
    verifier := provider.Verifier(&oidc.Config{ClientID: clientID})
    token, err := verifier.Verify(ctx, rawToken)
    if err != nil {
        return nil, fmt.Errorf("token verification failed: %w", err)
    }
    return token, nil
}

Introspection(不透明な token) ​

access token が不透明な形式(JWT ではない)の場合は Introspection で検証します:

bash
curl -X POST https://identity.lurus.cn/oauth/v2/introspect \
  -u "YOUR_CLIENT_ID:YOUR_CLIENT_SECRET" \
  -d "token=ACCESS_TOKEN_TO_CHECK"
# 响应:{ "active": true, "sub": "...", "exp": 1234567890, ... } 或 { "active": false }

Device Authorization Flow ​

ブラウザ入力のないデバイス(CLI、TV、IoT)に適しています。Lurus の CLI 製品(Lumen、kova-cli)はいずれもこのフローを使用します。

シーケンス:

  1. デバイスが POST /device_authorization。
  2. device_code + user_code + verification_uri を受け取る。
  3. user_code と URL をユーザーに表示。
  4. ユーザーがブラウザで verification_uri を開き、user_code を入力し、ログインして認可。
  5. デバイスが interval 秒ごとに /token をポーリング。
  6. ユーザーの認可後、次回のポーリングで access / id token が返る。

Step 1: Device Code を要求 ​

bash
curl -s -X POST https://identity.lurus.cn/oauth/v2/device_authorization \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "client_id=YOUR_CLIENT_ID" -d "scope=openid profile email"

レスポンス:

json
{
  "device_code": "Ag_EE...zo9OA",
  "user_code": "GQWC-FWFK",
  "verification_uri": "https://identity.lurus.cn/device",
  "verification_uri_complete": "https://identity.lurus.cn/device?user_code=GQWC-FWFK",
  "expires_in": 300,
  "interval": 5
}

Step 2: ユーザーに表示 ​

verification_uri(https://identity.lurus.cn/device)+ user_code を表示するか、verification_uri_complete を QR コードで読み取らせます。タイムアウトは 5 分。

Step 3: Token エンドポイントをポーリング ​

interval 秒ごとに、成功またはタイムアウトまでポーリングします。エラー処理:authorization_pending → 待機を継続;slow_down → 頻度を落とす;その他 → 失敗として終了。

bash
while true; do
  RESPONSE=$(curl -s -X POST https://identity.lurus.cn/oauth/v2/token \
    -H "Content-Type: application/x-www-form-urlencoded" \
    -d "grant_type=urn:ietf:params:oauth:grant-type:device_code" \
    -d "device_code=Ag_EE...zo9OA" -d "client_id=YOUR_CLIENT_ID")
  ERROR=$(echo "$RESPONSE" | jq -r '.error // empty')
  if [ -z "$ERROR" ]; then echo "授权成功!"; echo "$RESPONSE" | jq .; break
  elif [ "$ERROR" = "authorization_pending" ]; then sleep 5
  elif [ "$ERROR" = "slow_down" ]; then sleep 10
  else echo "授权失败:$ERROR"; break; fi
done

TypeScript での実装も同様です:fetch で POST /device_authorization してフローを開始し、続いて setTimeout(interval*1000) で /token をポーリングし、authorization_pending なら継続、slow_down なら interval を大きくし、resp.ok なら tokens を返します。


よくある質問 ​

最もよくある接続エラーと、一発で直す方法。

audience エラー(aud claim が一致しない)

症状:署名検証で token audience mismatch / invalid audience が出る。原因:access token の aud はデフォルトで client_id しか含まない。解決:scope に urn:casdoor:iam:org:project:id:{projectid}:aud を追加し、project ID を明示的に aud へ書き込む。

roles claim が空または欠落

原因:ユーザーが当該 Project の User Grant を持っていない、またはロール scope を要求していない。確認:① コンソールの Project → Authorizations でロール Grant があることを確認 ② scope に urn:casdoor:iam:org:projects:roles を含む ③ Project 設定で「Assert Roles on Authentication」を有効化。

id_token に email が含まれない

原因:scope で email が抜けている。解決:scope に email を追加(例:openid profile email)。

Refresh token が失効し再ログインが必要

更新時に invalid_grant が返る。考えられる原因:初回認可の scope に offline_access が含まれていない;refresh token の期限切れ;ユーザーがコンソールでセッションを失効させた。解決:Authorization Code フローを再度開始する;長期の更新が必要な場合は scope に offline_access を含め、アプリ種別を「Web」または「Native」にする。

PKCE code_verifier が一致しない

/token が invalid_grant: code verifier mismatch を返す。原因:verifier が 2 つのステップの間で変化した、または Base64URL エンコードが一致しない(= padding を含む、または +/ を使用)。解決:base64url(padding なし、-_ で +/ を置換)を使う;SPA ではページ間で verifier を保持するために sessionStorage を使う。


次のステップ

関連リンク ​

Powered by Lurus Technologies