Chuyển đến nội dung chính

レッスン 5: 病院用の Keycloak レルムのセットアップ — マルチテナント

複数の病院の医療システム向けのKeycloakレルム設計: 病院対組織ごとのレルム構造、HIS/EMR/LISのクライアント構成、医療スタッフ用のユーザー・プロファイル・スキーマ、患者ポータル・クライアント、セッション管理、セキュリティ防御、レルムのインポート/エクスポートの自動化。

🏗️ アーキテクチャ — レッスン 5 レッスン 5: 病院用の Keycloak レルムのセットアップ — マルチテナンシー

マイクロサービス ヘルスケア システムの構築 — HIPAA 標準を備えた Quarkus、PostgreSQL、Keycloak

パート 2: Keycloak を使用した ID とアクセス管理

xdev.asia

1. 医療システムのマルチテナント戦略

複数分院病院システム用のKeycloak Realmアーキテクチャ

###1.1. Keycloakを使用したマルチテナントモデル

多くの病院/診療所の医療システムを構築する場合、次の 3 つの戦略があります。

3 複数病院システムのための Keycloak マルチテナント戦略

戦略モデル隔離適切
A: 病院ごとのレルム各病院には独自のレルムがあります。最上位 - 孤立したユーザー、ロール、クライアント独立系病院
B: 共有レルム + 組織1 つの医療領域、各病院は 1 つの組織中 - 共有 ID + 組織の分離病院チェーン、保健省
C: 共有レルム + グループ1 つのレルム、グループごとに分割低 — シンプル、分離度が低い小さなクリニック

###1.2.ベトナムの健康に関する推奨事項

シナリオ推奨戦略理由
私立病院チェーン戦略 B (組織)患者 ID の共有、組織レベルの分離
独立した公立病院戦略 A (病院ごとのレルム)最大限の分離、独立した管理
小規模総合病院戦略 C (グループ)シンプルで管理が簡単
保健省は多くの施設を管理しています。戦略 B (組織)一元管理、組織の委任

2. ヘルスケア領域の設計

###2.1.レルム構成

{
  "realm": "healthcare",
  "displayName": "Healthcare Platform",
  "enabled": true,
  "sslRequired": "all",
  "registrationAllowed": false,
  "loginWithEmailAllowed": true,
  "duplicateEmailsAllowed": false,
  "resetPasswordAllowed": true,
  "editUsernameAllowed": false,
  "bruteForceProtected": true,
  "permanentLockout": false,
  "maxFailureWaitSeconds": 900,
  "minimumQuickLoginWaitSeconds": 60,
  "waitIncrementSeconds": 300,
  "quickLoginCheckMilliSeconds": 1000,
  "maxDeltaTimeSeconds": 43200,
  "failureFactor": 5,

  "passwordPolicy": "length(12) and upperCase(1) and lowerCase(1) and digits(1) and specialChars(1) and notUsername and passwordHistory(5) and maxLength(128)",

  "ssoSessionIdleTimeout": 900,
  "ssoSessionMaxLifespan": 28800,
  "accessTokenLifespan": 300,
  "accessTokenLifespanForImplicitFlow": 300,
  "offlineSessionIdleTimeout": 2592000,

  "actionTokenGeneratedByAdminLifespan": 43200,
  "actionTokenGeneratedByUserLifespan": 300,

  "organizationsEnabled": true,

  "attributes": {
    "cibaBackchannelTokenDeliveryMode": "poll",
    "cibaExpiresIn": "120",
    "cibaAuthRequestedUserHint": "login_hint",
    "parRequestUriLifespan": "60"
  }
}

###2.2.セキュリティ防御の構成

{
  "browserSecurityHeaders": {
    "contentSecurityPolicy": "default-src 'self'; frame-src 'self'; frame-ancestors 'self'; object-src 'none'; script-src 'self' 'unsafe-inline';",
    "contentSecurityPolicyReportOnly": "",
    "xContentTypeOptions": "nosniff",
    "xRobotsTag": "none",
    "xFrameOptions": "SAMEORIGIN",
    "strictTransportSecurity": "max-age=31536000; includeSubDomains",
    "xXSSProtection": "1; mode=block",
    "referrerPolicy": "no-referrer"
  }
}

3. ヘルスケア用のクライアント構成

###3.1.クライアントの概要

クライアントタイプ説明
彼のウェブアプリパブリックHIS Web フロントエンド
emr-web-appパブリックEMR Web フロントエンド
患者ポータルパブリック患者ポータル
モバイルドクターアプリパブリックドクターモバイルアプリ
患者サービス機密患者API
臨床サービス機密臨床API
ラボサービス機密ラボ結果 API
薬局サービス機密薬局 API
スケジューリングサービス機密スケジューリング API
請求サービス機密課金 API
通知サービス機密お知らせ
監査サービス機密監査/ロギング
APIゲートウェイ機密コング/APISIX
管理者-cli機密管理操作
fhirサーバー機密HAPI FHIR サーバー

###3.2.患者ポータル クライアント (パブリック)

{
  "clientId": "patient-portal",
  "name": "Patient Portal",
  "description": "Patient self-service portal for viewing medical records",
  "enabled": true,
  "publicClient": true,
  "standardFlowEnabled": true,
  "directAccessGrantsEnabled": false,
  "implicitFlowEnabled": false,
  "serviceAccountsEnabled": false,
  "authorizationServicesEnabled": false,

  "rootUrl": "https://portal.hospital.vn",
  "baseUrl": "/",
  "redirectUris": [
    "https://portal.hospital.vn/*"
  ],
  "webOrigins": [
    "https://portal.hospital.vn"
  ],

  "defaultClientScopes": [
    "openid", "profile", "email", "patient-scope"
  ],
  "optionalClientScopes": [
    "offline_access"
  ],

  "attributes": {
    "pkce.code.challenge.method": "S256",
    "post.logout.redirect.uris": "https://portal.hospital.vn/*",
    "access.token.lifespan": "300",
    "client.session.idle.timeout": "600"
  }
}

###3.3.マイクロサービス クライアント (機密)

{
  "clientId": "patient-service",
  "name": "Patient Microservice",
  "description": "Backend service managing patient demographics",
  "enabled": true,
  "publicClient": false,
  "standardFlowEnabled": false,
  "directAccessGrantsEnabled": false,
  "serviceAccountsEnabled": true,
  "authorizationServicesEnabled": true,

  "secret": "${PATIENT_SERVICE_SECRET}",

  "defaultClientScopes": [
    "openid", "microprofile-jwt"
  ],

  "attributes": {
    "use.jwks.url": "true",
    "token.endpoint.auth.signing.alg": "RS256"
  },

  "authorizationSettings": {
    "policyEnforcementMode": "ENFORCING",
    "decisionStrategy": "AFFIRMATIVE",
    "resources": [
      {
        "name": "Patient Record",
        "type": "urn:patient-service:resources:patient",
        "uris": ["/api/v1/patients/*"],
        "scopes": [
          {"name": "read"},
          {"name": "write"},
          {"name": "delete"}
        ]
      }
    ]
  }
}

4. 医療用のユーザー プロファイル スキーマ

###4.1.医療ユーザーの属性

{
  "attributes": [
    {
      "name": "employeeId",
      "displayName": "Mã nhân viên",
      "required": { "roles": ["user"] },
      "permissions": { "edit": ["admin"], "view": ["admin", "user"] },
      "validations": { "pattern": { "pattern": "^NV-\\d{6}$" } }
    },
    {
      "name": "medicalLicenseNumber",
      "displayName": "Số giấy phép hành nghề",
      "required": { "roles": ["doctor"] },
      "permissions": { "edit": ["admin"], "view": ["admin", "user"] },
      "validations": { "length": { "min": 5, "max": 20 } }
    },
    {
      "name": "department",
      "displayName": "Khoa/Phòng",
      "required": { "roles": ["user"] },
      "permissions": { "edit": ["admin"], "view": ["admin", "user"] },
      "validations": {
        "options": {
          "options": [
            "KHOA_NOI", "KHOA_NGOAI", "KHOA_SAN", "KHOA_NHI",
            "KHOA_UNG_BUOU", "KHOA_TIM_MACH", "KHOA_THAN_KINH",
            "KHOA_XET_NGHIEM", "KHOA_CHAN_DOAN_HINH_ANH",
            "KHOA_DUOC", "KHOA_CAP_CUU", "PHONG_HANH_CHINH"
          ]
        }
      }
    },
    {
      "name": "hospitalCode",
      "displayName": "Mã bệnh viện",
      "required": { "roles": ["user"] },
      "permissions": { "edit": ["admin"], "view": ["admin"] }
    },
    {
      "name": "specialization",
      "displayName": "Chuyên khoa",
      "permissions": { "edit": ["admin"], "view": ["admin", "user"] }
    }
  ]
}

###4.2.医療保険請求用のカスタム トークン マッパー

{
  "name": "healthcare-claims-mapper",
  "protocol": "openid-connect",
  "protocolMapper": "oidc-usermodel-attribute-mapper",
  "config": {
    "user.attribute": "department",
    "claim.name": "department",
    "jsonType.label": "String",
    "id.token.claim": "true",
    "access.token.claim": "true",
    "userinfo.token.claim": "true"
  }
}

JWT トークンには、医療固有のクレームが含まれます。

{
  "sub": "019e1a40-user-0001-d001-f0a1b2c30001",
  "name": "BS. Nguyễn Văn A",
  "preferred_username": "bs.nguyen.a",
  "email": "[email protected]",
  "realm_access": {
    "roles": ["doctor", "department_head"]
  },
  "resource_access": {
    "patient-service": { "roles": ["patient_read", "patient_write"] },
    "lab-service": { "roles": ["lab_read", "lab_order"] },
    "pharmacy-service": { "roles": ["prescription_write"] }
  },
  "department": "KHOA_NOI",
  "hospitalCode": "BV-CR-001",
  "medicalLicenseNumber": "GPHN-12345",
  "employeeId": "NV-001234",
  "org_id": "bv-cho-ray"
}

5. 医療の役割階層

###5.1.レルムの役割

Realm Roles:
├── super_admin          (Quản trị hệ thống toàn cục)
├── hospital_admin       (Quản trị bệnh viện)
├── department_head      (Trưởng khoa)
├── doctor              (Bác sĩ)
├── senior_nurse         (Điều dưỡng trưởng)
├── nurse               (Điều dưỡng)
├── lab_technician       (Kỹ thuật viên xét nghiệm)
├── pharmacist           (Dược sĩ)
├── radiologist          (Bác sĩ chẩn đoán hình ảnh)
├── receptionist         (Lễ tân)
├── billing_staff        (Nhân viên thanh toán)
├── patient              (Bệnh nhân - Patient Portal)
└── auditor              (Kiểm toán viên - read-only)

###5.2.複合役割

{
  "name": "doctor",
  "composite": true,
  "composites": {
    "realm": [],
    "client": {
      "patient-service": ["patient_read", "patient_write"],
      "clinical-service": ["encounter_read", "encounter_write", "diagnosis_write"],
      "lab-service": ["lab_read", "lab_order"],
      "pharmacy-service": ["prescription_read", "prescription_write"],
      "scheduling-service": ["appointment_read", "appointment_write"],
      "imaging-service": ["imaging_read", "imaging_order"]
    }
  }
}

6. 病院向けのセッション管理

###6.1.セッションポリシー

ユーザータイプセッションアイドルセッション最大値理由
医師15分8時間共有ワークステーション、頻繁な自動ログオフ
看護師10分8時間モバイル カート、素早いアクセスが必要
患者ポータル15分2時間公衆インターネットアクセス
管理者30分8時間管理タスク、PHI への曝露が減少
サービスアカウント該当なし該当なしトークンベース、対話型セッションなし

###6.2.共有ワークステーションのサポート

病院には共有ワークステーションがあることが多く、多くの医師や看護師が 1 台のコンピュータを共有しています。解決策:

病院の共有ワークステーションでの 3 つの認証オプション

オプションメカニズム利点制限事項
1.ユーザーの高速切り替えKeycloak ショートセッション + バッジログイン早くて便利インフラストラクチャが必要 (バッジ リーダー)
2.自動ログオフ + クイック再認証セッションタイムアウト 10 ~ 15 分 + PINシンプルでハードウェアは不要バッジよりも遅い
3.仮想デスクトップ (VDI)各ユーザーは独自の仮想デスクトップを持っています。完全な隔離高価で待ち時間が長い

7. レルムのエクスポート/インポートの自動化

###7.1. Keycloakのコードとしてのインフラストラクチャ

#!/bin/bash
# export-realm.sh - Export healthcare realm for version control

KEYCLOAK_URL="https://keycloak.hospital.internal"
ADMIN_TOKEN=$(curl -s -X POST "${KEYCLOAK_URL}/realms/master/protocol/openid-connect/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "username=admin" \
  -d "password=${KC_ADMIN_PASSWORD}" \
  -d "grant_type=password" \
  -d "client_id=admin-cli" | jq -r '.access_token')

# Export realm (excluding users for security)
curl -s -X GET "${KEYCLOAK_URL}/admin/realms/healthcare" \
  -H "Authorization: Bearer ${ADMIN_TOKEN}" \
  -H "Accept: application/json" | jq '.' > realm-healthcare-export.json

echo "Realm exported successfully"

###7.2. Keycloak 用の Terraform (オプション)

# keycloak.tf - Keycloak Realm as Code
resource "keycloak_realm" "healthcare" {
  realm   = "healthcare"
  enabled = true

  login_theme = "healthcare-theme"

  security_defenses {
    brute_force_detection {
      permanent_lockout                = false
      max_login_failures               = 5
      wait_increment_seconds           = 300
      quick_login_check_milli_seconds  = 1000
      minimum_quick_login_wait_seconds = 60
      max_failure_wait_seconds         = 900
    }
  }

  ssl_required    = "all"
  password_policy = "length(12) and upperCase(1) and lowerCase(1) and digits(1) and specialChars(1)"

  sso_session_idle_timeout = "15m"
  sso_session_max_lifespan = "8h"
  access_token_lifespan    = "5m"
}

resource "keycloak_role" "doctor" {
  realm_id    = keycloak_realm.healthcare.id
  name        = "doctor"
  description = "Bác sĩ - Full clinical access"
  composite_roles = [
    keycloak_role.patient_read.id,
    keycloak_role.patient_write.id,
    keycloak_role.prescription_write.id,
  ]
}

8. まとめ

このレッスンでは次のことを行います。

  • マルチ病院システム向けの 3 つのマルチテナンシー戦略を比較します
  • セキュリティ強化を備えた ヘルスケア レルム 設計
  • 患者ポータル、マイクロサービス、管理者の クライアント を構成する
  • 医療固有の属性を使用して ユーザー プロファイル スキーマを構築する
  • 病院組織に合わせて 役割階層 を設計する
  • 共有ワークステーションの セッション管理 を構成する
  • Infrastructure as Code を使用して レルムのエクスポート/インポート を自動化する

演習

  1. レッスンのすべてのセキュリティ構成を使用して「ヘルスケア」Keycloak レルムを作成します
  2. 5 つのクライアントを登録します: 患者ポータル、患者サービス、検査サービス、薬局サービス、API ゲートウェイ
  3. 役割階層を作成し、それをテスト ユーザーに割り当てます: 医師 1 人、看護師 1 人、患者 1 人
  4. レルム設定をエクスポートし、Git リポジトリにコミットします。


◀ 前の記事次の記事 ▶
レッスン 4: 医療情報システムの STRIDE/DREAD の脅威モデリングレッスン 6: RBAC と ABAC - 医師、看護師、患者の分散化