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

第 6 堂課:RBAC 和 ABAC 去中心化 — 醫生、護士、病人

為醫療保健部署基於角色的存取控制 (RBAC) 和基於屬性的存取控制 (ABAC):設計角色層次結構(科室醫生、主治醫生、護士長、護士、技術員、管理員、患者)、基於科室的訪問、具有策略和權限的 Keycloak 授權服務以及打破玻璃的緊急訪問。

🏗️ 建築 — 第 6 課 第 6 堂課:RBAC 和 ABAC 去中心化 — 醫生、護士 護士、病人

建構微服務醫療保健系統 — Quarkus、PostgreSQL、符合 HIPAA 標準的 Keycloak

第 2 部分:使用 Keycloak 進行身分和存取管理

亞洲開發網

1. 醫療保健領域的 RBAC 與 ABAC

4層存取控制架構:RBAC、ABAC、RLS、病患同意

1.1。基於角色的存取控制 (RBAC)

RBAC 根據使用者在組織內的角色分配權限:

User ──▶ Role ──▶ Permission

Ví dụ:
BS. Nguyễn ──▶ doctor ──▶ patient_read, patient_write, prescription_write
ĐD. Trần   ──▶ nurse  ──▶ patient_read, vital_signs_write
BN. Lê     ──▶ patient ──▶ own_record_read

優點:簡單、易於管理、易於審計 限制:無法處理複雜的規則(例如:只有內科醫生才能看內科病人)

1.2。基於屬性的存取控制 (ABAC)

ABAC 根據使用者、資源、操作和環境的屬性做出決策:

Policy: ALLOW if
  user.role == "doctor" AND
  user.department == resource.department AND
  resource.classification_level <= 3 AND
  environment.time BETWEEN "06:00" AND "22:00" AND
  environment.network == "INTERNAL"

優點:靈活、細粒度、上下文感知 限制:較複雜,難以調試,需要大量元數據

1.3。結合 RBAC + ABAC 促進健康

層名稱機制範例
第 1 層RBAC(Keycloak 角色)使用者是醫生嗎? → 可以存取臨床數據。使用者是護士嗎? → 可以取得生命徵象。用戶是患者嗎? → 可以存取自己的記錄。@RolesAllowed("doctor")
第 2 層ABAC(Keycloak授權服務)部門匹配? → 僅擁有部門資料。待遇關係? → 僅限指定患者。基於時間? → 下班後需 MFA。基於位置? → 外部需要 VPN。客製化政策
第 3 層RLS(PostgreSQL 行級安全性)資料庫依據 JWT 宣告強制執行每行存取CREATE POLICY
第 4 層同意(患者同意檢查)患者已為此目的授予存取權限?FHIR 同意資源

2.Keycloak授權服務

2.1。概念

Resources      → What is being protected? (Patient Record, Lab Result)
Scopes         → What actions? (read, write, delete, export, print)
Policies       → Who can access? (role, user, time, script)
Permissions    → Connect Resources + Scopes + Policies

2.2。資源定義

{
  "resources": [
    {
      "name": "Patient Record",
      "type": "urn:healthcare:resources:patient",
      "uris": ["/api/v1/patients/*"],
      "ownerManagedAccess": false,
      "scopes": [
        {"name": "read"},
        {"name": "write"},
        {"name": "delete"},
        {"name": "export"},
        {"name": "print"}
      ],
      "attributes": {
        "classification": ["3"],
        "contains_phi": ["true"]
      }
    },
    {
      "name": "Lab Result",
      "type": "urn:healthcare:resources:lab-result",
      "uris": ["/api/v1/lab-results/*"],
      "scopes": [
        {"name": "read"},
        {"name": "write"},
        {"name": "verify"},
        {"name": "print"}
      ]
    },
    {
      "name": "Prescription",
      "type": "urn:healthcare:resources:prescription",
      "uris": ["/api/v1/prescriptions/*"],
      "scopes": [
        {"name": "read"},
        {"name": "write"},
        {"name": "dispense"},
        {"name": "cancel"}
      ]
    }
  ]
}

2.3。政策定義

{
  "policies": [
    {
      "name": "Doctor Role Policy",
      "type": "role",
      "logic": "POSITIVE",
      "config": {
        "roles": [
          {"id": "doctor", "required": true}
        ]
      }
    },
    {
      "name": "Same Department Policy",
      "type": "script-js",
      "logic": "POSITIVE",
      "config": {
        "code": "var context = $evaluation.getContext(); var identity = context.getIdentity(); var userDept = identity.getAttributes().getValue('department').asString(0); var resourceDept = $evaluation.getPermission().getResource().getAttribute('department')[0]; if (userDept === resourceDept) { $evaluation.grant(); }"
      }
    },
    {
      "name": "Working Hours Policy",
      "type": "time",
      "logic": "POSITIVE",
      "config": {
        "notBefore": "06:00",
        "notOnOrAfter": "22:00"
      }
    },
    {
      "name": "Emergency Access Policy",
      "type": "role",
      "logic": "POSITIVE",
      "config": {
        "roles": [
          {"id": "emergency_access", "required": true}
        ]
      }
    }
  ]
}

2.4。權限

{
  "permissions": [
    {
      "name": "Doctor Read Patient",
      "type": "scope",
      "resources": ["Patient Record"],
      "scopes": ["read"],
      "policies": ["Doctor Role Policy", "Same Department Policy"],
      "decisionStrategy": "AFFIRMATIVE"
    },
    {
      "name": "Doctor Write Prescription",
      "type": "scope",
      "resources": ["Prescription"],
      "scopes": ["write"],
      "policies": ["Doctor Role Policy"],
      "decisionStrategy": "UNANIMOUS"
    },
    {
      "name": "Emergency Override",
      "type": "scope",
      "resources": ["Patient Record", "Lab Result", "Prescription"],
      "scopes": ["read"],
      "policies": ["Emergency Access Policy"],
      "decisionStrategy": "AFFIRMATIVE"
    }
  ]
}

3. Quarkus 中的實現

3.1。帶有 @RolesAllowed 的 RBAC

@Path("/api/v1/patients")
@Authenticated
public class PatientResource {

    @GET
    @RolesAllowed({"doctor", "nurse", "admin"})
    public List<PatientSummaryDTO> listPatients(
            @QueryParam("department") String department) {
        // Role check passed — now apply ABAC
        return patientService.findByDepartment(department);
    }

    @GET
    @Path("/{id}")
    @RolesAllowed({"doctor", "nurse", "patient"})
    public PatientDetailDTO getPatient(@PathParam("id") UUID id) {
        return patientService.findById(id);
    }

    @PUT
    @Path("/{id}")
    @RolesAllowed({"doctor", "admin"})
    public Response updatePatient(
            @PathParam("id") UUID id,
            PatientUpdateRequest request) {
        return patientService.update(id, request);
    }

    @DELETE
    @Path("/{id}")
    @RolesAllowed({"admin"})
    public Response deletePatient(@PathParam("id") UUID id) {
        // Soft delete only — PHI retention requirements
        return patientService.softDelete(id);
    }
}

3.2。具有自訂 SecurityIdentityAugmentor 的 ABAC

@ApplicationScoped
public class HealthcareSecurityAugmentor implements SecurityIdentityAugmentor {

    @Inject
    TreatmentRelationshipService treatmentService;

    @Override
    public Uni<SecurityIdentity> augment(SecurityIdentity identity,
            AuthenticationRequestContext context) {

        return Uni.createFrom().item(() -> {
            QuarkusSecurityIdentity.Builder builder =
                QuarkusSecurityIdentity.builder(identity);

            // Add department attribute from JWT
            String department = identity.getAttribute("department");
            if (department != null) {
                builder.addAttribute("department", department);
            }

            // Add hospital code
            String hospitalCode = identity.getAttribute("hospitalCode");
            if (hospitalCode != null) {
                builder.addAttribute("hospitalCode", hospitalCode);
            }

            return builder.build();
        });
    }
}

3.3。 ABAC授權攔截器

@Interceptor
@HealthcareAuthorization
@Priority(Interceptor.Priority.APPLICATION)
public class HealthcareAuthorizationInterceptor {

    @Inject
    SecurityIdentity identity;

    @Inject
    AuditService auditService;

    @AroundInvoke
    public Object authorize(InvocationContext ctx) throws Exception {
        HealthcareAuthorization annotation =
            ctx.getMethod().getAnnotation(HealthcareAuthorization.class);

        if (annotation == null) {
            return ctx.proceed();
        }

        // Check department access
        if (annotation.requireSameDepartment()) {
            String userDept = identity.getAttribute("department");
            UUID resourceId = extractResourceId(ctx);
            String resourceDept = getResourceDepartment(resourceId);

            if (!userDept.equals(resourceDept) &&
                !identity.hasRole("emergency_access")) {
                auditService.logAccessDenied(identity,
                    annotation.resource(), resourceId.toString(),
                    "Department mismatch: user=" + userDept +
                    " resource=" + resourceDept);
                throw new ForbiddenException(
                    "Access denied: department mismatch");
            }
        }

        // Check treatment relationship
        if (annotation.requireTreatmentRelationship()) {
            UUID patientId = extractPatientId(ctx);
            String doctorId = identity.getPrincipal().getName();

            if (!treatmentService.hasRelationship(doctorId, patientId)) {
                auditService.logAccessDenied(identity,
                    annotation.resource(), patientId.toString(),
                    "No treatment relationship");
                throw new ForbiddenException(
                    "No treatment relationship with patient");
            }
        }

        return ctx.proceed();
    }
}

3.4。自訂授權註解

@InterceptorBinding
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface HealthcareAuthorization {
    String resource() default "";
    boolean requireSameDepartment() default false;
    boolean requireTreatmentRelationship() default false;
    int maxClassificationLevel() default 3;
}

// Usage
@GET
@Path("/{patientId}/clinical-notes")
@RolesAllowed("doctor")
@HealthcareAuthorization(
    resource = "ClinicalNote",
    requireSameDepartment = true,
    requireTreatmentRelationship = true
)
public List<ClinicalNoteDTO> getClinicalNotes(
        @PathParam("patientId") UUID patientId) {
    return clinicalNoteService.findByPatient(patientId);
}

4. 打破玻璃緊急通道

4.1。概念

Tình huống: Bệnh nhân cấp cứu, bác sĩ trực KHÔNG phải bác sĩ
điều trị → cần truy cập hồ sơ bệnh án NGAY LẬP TỨC

Normal Flow:        Emergency Flow:
Doctor ──▶ Check    Doctor ──▶ Break-the-Glass
           Role ✓              ▶ Log reason
           Dept ✓              ▶ Grant temporary access
           Rel. ✓              ▶ Alert security team
           ──▶ Access          ▶ Time-limited (2 hours)
                               ──▶ Access
                               ──▶ Post-review required

4.2。執行

@Path("/api/v1/emergency-access")
@Authenticated
public class EmergencyAccessResource {

    @Inject
    SecurityIdentity identity;

    @Inject
    EmergencyAccessService emergencyService;

    @Inject
    NotificationService notificationService;

    @POST
    @RolesAllowed({"doctor", "nurse"})
    public Response requestEmergencyAccess(EmergencyAccessRequest request) {

        // Validate and log emergency access
        EmergencyAccess access = emergencyService.create(
            EmergencyAccess.builder()
                .requesterId(identity.getPrincipal().getName())
                .requesterRole(identity.getRoles().toString())
                .patientId(request.patientId())
                .reason(request.reason())  // Required: why emergency access?
                .expiresAt(Instant.now().plus(2, ChronoUnit.HOURS))
                .status(EmergencyAccessStatus.ACTIVE)
                .build()
        );

        // Alert security team and department head
        notificationService.alertEmergencyAccess(access);

        // Grant temporary role via Keycloak Admin API
        keycloakAdminService.grantTemporaryRole(
            identity.getPrincipal().getName(),
            "emergency_access",
            Duration.ofHours(2)
        );

        return Response.ok(Map.of(
            "accessId", access.getId(),
            "expiresAt", access.getExpiresAt(),
            "message", "Emergency access granted. This access will be reviewed."
        )).build();
    }
}

5. 存取控制矩陣

5.1。醫療保健許可矩陣

資源醫生護士實驗室技術藥劑師病人管理員
病患人口統計讀/寫右右右擁有:R讀/寫
臨床筆記讀/寫右--擁有:R-
生命徵象讀/寫讀/寫--擁有:R-
實驗室訂單讀/寫右讀/寫右擁有:R右
實驗室結果右右讀/寫/視右擁有:R右
處方讀/寫右-研發擁有:R右
計費右---擁有:R讀/寫
稽核日誌-----右

R=讀取、W=寫入、D=分配、V=驗證、Own=僅自己的記錄

6. 總結

在本課中,我們有:

  • 比較 RBAC 與 ABAC 以及結合 4 層醫療保健的策略
  • 設定 Keycloak 授權服務(資源、策略、權限)
  • 在 Quarkus 中實作 RBAC @RolesAllowed
  • 使用自訂實作 ABAC SecurityIdentityAugmentor 和授權攔截器
  • 設計打破玻璃緊急通道程序

練習

1.實現完整的ABAC實驗室結果訪問:只有同一科的醫生才能查看 2. 建立包含審核追蹤和通知的「打破玻璃」工作流程 3. 編寫權限矩陣整合測試(醫生 vs 護理師 vs 病人訪問)



◀ 上一篇下一篇文章 ▶
第 5 課:根據醫療標準設計 Keycloak Realm - 醫院多租戶第 7 課:FHIR 上的 SMART - 用於醫療保健 API 的 OAuth2/OIDC