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

第 15 課:授權服務 - 詳細授權

授權服務深入研究:資源伺服器、資源、範圍、權限、策略(基於角色、基於使用者、基於群組、基於客戶端、基於時間、JavaScript、聚合)。 UMA 2.0 支援、權限 API、策略執行器、推送聲明、資源屬性、聲明資訊點、評估 API 和授權整合到 Spring Boot / Node.js 應用程式中。

🔒 DevSecOps — 第 15 課 第 15 課:授權服務 - 部門 詳細權限

從基礎到進階的鑰匙斗篷__HTMLTAG_59___

第 4 部分:使用者聯盟、組織與授權__HTMLTAG_62___

xdev.asia

HTMLTAG_67__HTMLTAG_68___1。授權服務 — 概論___HTMLTAG_69__HTMLTAG_70

Keycloak 授權服務提供 細粒度授權,允許在資源和範圍層級進行存取控制,而不僅僅是依賴角色。本系統符合 UMA 2.0(使用者管理存取)標準並支援多種策略類型。

HTMLTAG_77__HTMLTAG_78___1.1 主要概念___HTMLTAG_79__HTMLTAG_80

___HTMLTAG_83__HTMLTAG_84___概念___HTMLTAG_85__HTMLTAG_86___說明____HTMLTAG_87__HTMLTAG_88___範例____HTMLTAG_89__HTMLTAG_90___ ___HTMLTAG_93__HTMLTAG_94__HTMLTAG_95___資源伺服器___HTMLTAG_96__HTMLTAG_97__HTMLTAG_98___應用程式需要保護資源(是Keycloak客戶端)___HTMLTAG_99__HTMLTAG_100_____10100___ ___HTMLTAG_103__HTMLTAG_104__HTMLTAG_105___資源___HTMLTAG_106__HTMLTAG_107__HTMLTAG_108___要保護的物件____HTMLTAG_109__HTMLTAG_110___、API 端點、頁數 ___HTMLTAG_113__HTMLTAG_114__HTMLTAG_115___範圍___HTMLTAG_116__HTMLTAG_117__HTMLTAG_118___可以對資源執行的操作___HTMLTAG_119__HTMLTAG_120__HTMLTAG_121___HTMLTAG_119__HTMLTAG_120__HTMLTAG_121___ 編輯、刪除、發佈___HTMLTAG_128__HTMLTAG_129__HTMLTAG_130___HTMLTAG_128__HTMLTAG_129__HTMLTAG_130___ ___HTMLTAG_131__HTMLTAG_132__HTMLTAG_133___權限___HTMLTAG_134__HTMLTAG_135__HTMLTAG_136___將資源/範圍與策略結合____HTMLTAG_137__HTMLTAG_138___「誰可以檢視文件G」___13135____ML_138___ ___HTMLTAG_141__HTMLTAG_142__HTMLTAG_143___政策___HTMLTAG_144__HTMLTAG_145__HTMLTAG_146___必須滿足允許存取的條件____HTMLTAG_147__HTMLTAG_148___「使用者必須有編輯者____HTMLTAG_147__HTMLTAG_148___「使用者」必須具有編輯者____HTMLTAG_147__HTMLTAG_148___「使用者」必須有編輯者____HTML410G1047____ML_148___「使用者」。

HTMLTAG_153__HTMLTAG_154___1.2 授權流程___HTMLTAG_155__HTMLTAG_156

預編碼_0

HTMLTAG_157__HTMLTAG_158___2。啟用授權服務___HTMLTAG_159__HTMLTAG_160

在 客戶端層級啟用(非領域):

HTMLTAG_165__HTMLTAG_166___2.1 透過管理控制台___HTMLTAG_167__HTMLTAG_168

預編碼_1

HTMLTAG_169__HTMLTAG_170___2.2 透過 kcadm.sh___HTMLTAG_171__HTMLTAG_172

預編碼_2

啟用後,選項卡 授權 將顯示在客戶端設定上,並附有子選項卡:設定、資源、範圍、策略、權限、評估.

HTMLTAG_177__HTMLTAG_178___3。資源___HTMLTAG_179__HTMLTAG_180

資源代表 要保護的物件。每個資源都可以有 URI、類型、範圍和屬性。

HTMLTAG_185__HTMLTAG_186___3.1 建立資源___HTMLTAG_187__HTMLTAG_188

預編碼_3

HTMLTAG_189__HTMLTAG_190___3.2 資源屬性___HTMLTAG_191__HTMLTAG_192

屬性允許向資源添加元數據,可在策略中使用:

預編碼_4

HTMLTAG_195__HTMLTAG_196___4。範圍___HTMLTAG_197__HTMLTAG_198

作用域定義了 actions__HTMLTAG_201___ 可以對資源執行:

預編碼_5

常見範圍模式:

___HTMLTAG_207__HTMLTAG_208___模式___HTMLTAG_209__HTMLTAG_210___範圍___HTMLTAG_211__HTMLTAG_212___ ___HTMLTAG_215__HTMLTAG_216__HTMLTAG_217____CRUD___HTMLTAG_218__HTMLTAG_219__HTMLTAG_220__HTMLTAG_2 21___建立、讀取、更新、 刪除___HTMLTAG_228__HTMLTAG_229__HTMLTAG_230___ ___HTMLTAG_231__HTMLTAG_232__HTMLTAG_233___內容管理___HTMLTAG_234__HTMLTAG_235__HTMLTAG_236__HTMLTAG_2 37___查看、編輯、發佈、 存檔___HTMLTAG_244__HTMLTAG_245__HTMLTAG_246___ ___HTMLTAG_247__HTMLTAG_248__HTMLTAG_249___API 存取___HTMLTAG_250__HTMLTAG_251__HTMLTAG_252__HTMLTAG_253___讀取____MLTAG_254____ 管理員___HTMLTAG_258__HTMLTAG_259__HTMLTAG_260___ ___HTMLTAG_261__HTMLTAG_262__HTMLTAG_263___檔案操作___HTMLTAG_264__HTMLTAG_265__HTMLTAG_266__HTMLTAG_2 67___下載、上傳、共享、 刪除___HTMLTAG_274__HTMLTAG_275__HTMLTAG_276___

HTMLTAG_279__HTMLTAG_280___5。政策___HTMLTAG_281__HTMLTAG_282

策略是決定允許或拒絕存取的條件。 Keycloak 支援多種類型的策略:

HTMLTAG_287__HTMLTAG_288___5.1 基於角色的策略___HTMLTAG_289__HTMLTAG_290

預編碼_6

HTMLTAG_291__HTMLTAG_292___5.2 基於使用者的政策___HTMLTAG_293__HTMLTAG_294

預編碼_7

HTMLTAG_295__HTMLTAG_296___5.3 基於群組的策略___HTMLTAG_297__HTMLTAG_298

預編碼_8

HTMLTAG_299__HTMLTAG_300___5.4 基於客戶端的政策___HTMLTAG_301__HTMLTAG_302

預編碼_9

HTMLTAG_303__HTMLTAG_304___5.5 基於時間的策略___HTMLTAG_305__HTMLTAG_306

預編碼_10

HTMLTAG_307__HTMLTAG_308___5.6 JavaScript 政策___HTMLTAG_309__HTMLTAG_310

___HTMLTAG_311__HTMLTAG_312___注意: JavaScript 策略需要透過 --features=scripts 或上傳 JAR.

啟用
或上傳 JAR.

啟用

// Script-based policy (upload dưới dạng JAR provider)
// Filename: my-policy.js
var context = $evaluation.getContext();
var identity = context.getIdentity();
var attributes = identity.getAttributes();

// Kiểm tra custom attribute
var department = attributes.getValue('department');
if (department && department.asString(0) === 'engineering') {
    $evaluation.grant();
} else {
    $evaluation.deny();
}

將 JavaScript 原則部署為 JAR:

# Tạo cấu trúc thư mục
mkdir -p META-INF/keycloak-scripts/

# Tạo file descriptor
cat > META-INF/keycloak-scripts/keycloak-scripts.json << 'EOF'
{
  "policies": [
    {
      "name": "Engineering Department Policy",
      "fileName": "engineering-policy.js",
      "description": "Allow only engineering department"
    }
  ]
}
EOF

# Package JAR
jar cf my-policies.jar META-INF/ engineering-policy.js

# Deploy
cp my-policies.jar /opt/keycloak/providers/
/opt/keycloak/bin/kc.sh build

HTMLTAG_319__HTMLTAG_320___5.7 綜合政策___HTMLTAG_321__HTMLTAG_322

透過決策策略將多個策略合併為單一策略:

{
  "name": "Full Access Policy",
  "description": "Kết hợp Role + Group + Time policies",
  "type": "aggregate",
  "logic": "POSITIVE",
  "decisionStrategy": "UNANIMOUS",
  "policies": [
    "Editor Role Policy",
    "Engineering Group Policy",
    "Business Hours Policy"
  ]
}

HTMLTAG_325__HTMLTAG_326___6。決策策略___HTMLTAG_327__HTMLTAG_328

___HTMLTAG_331__HTMLTAG_332___策略____HTMLTAG_333__HTMLTAG_334___說明____HTMLTAG_335__HTMLTAG_336___何時使用____HTMLTAG_337__HTMLTAG_338___ ___HTMLTAG_341__HTMLTAG_342__HTMLTAG_343___一致___HTMLTAG_344__HTMLTAG_345__HTMLTAG_346___所有政策必須允許___HTMLTAG_347__HTMLTAG_348______ML] 所有政策必須允許___HTMLTAG_347__HTMLTAG_348______ML — 所有條件必須符合嚴格限制 ___HTMLTAG_351__HTMLTAG_352__HTMLTAG_353___肯定___HTMLTAG_354__HTMLTAG_355__HTMLTAG_356___至少一項政策允許___HTMLTAG_357__HTMLTAG_358_____ 只滿足滿足一個條件___MLTAGMLTAGMLTAGMLTAGML_4141. ___HTMLTAG_361__HTMLTAG_362__HTMLTAG_363___共識___HTMLTAG_364__HTMLTAG_365__HTMLTAG_366___允許數字>拒絕___HTMLTAG_367__HTMLTAG_368___投票 - ___ML_MLTAGMLTAGMLTAGMLTAG_368___投票 - 7_ML

HTMLTAG_373__HTMLTAG_374___7。權限___HTMLTAG_375__HTMLTAG_376

權限將 資源/範圍與政策結合以建立授權規則。

HTMLTAG_381__HTMLTAG_382___7.1 基於資源的權限___HTMLTAG_383__HTMLTAG_384

# Tạo resource-based permission
curl -X POST "http://localhost:8080/admin/realms/my-realm/clients/${CLIENT_ID}/authz/resource-server/permission/resource" \
  -H "Authorization: Bearer ${ACCESS_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Document Access Permission",
    "description": "Ai có thể truy cập documents",
    "type": "resource",
    "logic": "POSITIVE",
    "decisionStrategy": "UNANIMOUS",
    "resources": ["Document Resource"],
    "policies": ["Editor Role Policy", "Business Hours Policy"]
  }'

HTMLTAG_385__HTMLTAG_386___7.2 基於範圍的權限___HTMLTAG_387__HTMLTAG_388

# Tạo scope-based permission
curl -X POST "http://localhost:8080/admin/realms/my-realm/clients/${CLIENT_ID}/authz/resource-server/permission/scope" \
  -H "Authorization: Bearer ${ACCESS_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Document Delete Permission",
    "description": "Chỉ admin mới được xóa documents",
    "type": "scope",
    "logic": "POSITIVE",
    "decisionStrategy": "UNANIMOUS",
    "resources": ["Document Resource"],
    "scopes": ["delete"],
    "policies": ["Admin Role Policy"]
  }'

# Permission cho publish scope
curl -X POST "http://localhost:8080/admin/realms/my-realm/clients/${CLIENT_ID}/authz/resource-server/permission/scope" \
  -H "Authorization: Bearer ${ACCESS_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Document Publish Permission",
    "description": "Editor và Admin được publish",
    "type": "scope",
    "logic": "POSITIVE",
    "decisionStrategy": "AFFIRMATIVE",
    "resources": ["Document Resource"],
    "scopes": ["publish"],
    "policies": ["Editor Role Policy", "Admin Role Policy"]
  }'

HTMLTAG_389__HTMLTAG_390___8。 UMA 2.0___HTMLTAG_391__HTMLTAG_392

使用者管理的存取 (UMA) 2.0 允許 資源擁有者管理 對其資源的存取。用戶可以與其他用戶共享資源,無需管理員幹預。

HTMLTAG_397__HTMLTAG_398___8.1 啟用 UMA___HTMLTAG_399__HTMLTAG_400

Clients → my-app → Authorization → Settings:
  Resource server settings:
    Policy Enforcement Mode: ENFORCING
    Decision Strategy: UNANIMOUS

Resources:
  Resource → Owner Managed Access: ON

HTMLTAG_401__HTMLTAG_402___8.2 UMA 撥款流程___HTMLTAG_403__HTMLTAG_404

# 1. Client gọi Resource Server → bị deny → nhận permission ticket
# Response 401 với header:
# WWW-Authenticate: UMA realm="my-realm",
#   as_uri="http://localhost:8080/realms/my-realm",
#   ticket="permission-ticket-value"

# 2. Client exchange permission ticket lấy RPT (Requesting Party Token)
curl -X POST "http://localhost:8080/realms/my-realm/protocol/openid-connect/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=urn:ietf:params:oauth:grant-type:uma-ticket" \
  -d "ticket=permission-ticket-value" \
  -d "client_id=my-app" \
  -d "client_secret=my-secret"

# Response chứa RPT (access token with authorization data)
{
  "access_token": "eyJhbGciOi...",
  "token_type": "Bearer",
  "authorization": {
    "permissions": [
      {
        "rsid": "resource-uuid",
        "rsname": "Document Resource",
        "scopes": ["view", "edit"]
      }
    ]
  }
}

HTMLTAG_405__HTMLTAG_406___9。權限API___HTMLTAG_407__HTMLTAG_408

權限 API 允許 以程式設計方式檢查權限,無需 UMA 流程:

# Kiểm tra quyền truy cập cho user hiện tại
curl -X POST "http://localhost:8080/realms/my-realm/protocol/openid-connect/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=urn:ietf:params:oauth:grant-type:uma-ticket" \
  -d "audience=my-app" \
  -d "permission=Document Resource#view" \
  -d "response_mode=decision" \
  -d "client_id=my-frontend" \
  -d "subject_token=${USER_ACCESS_TOKEN}"

# Response:
# { "result": true }  → PERMIT
# { "result": false } → DENY

# Kiểm tra nhiều permissions cùng lúc
curl -X POST "http://localhost:8080/realms/my-realm/protocol/openid-connect/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=urn:ietf:params:oauth:grant-type:uma-ticket" \
  -d "audience=my-app" \
  -d "permission=Document Resource#view" \
  -d "permission=Document Resource#edit" \
  -d "permission=Admin Panel#access" \
  -d "response_mode=permissions" \
  -d "client_id=my-frontend" \
  -d "subject_token=${USER_ACCESS_TOKEN}"

HTMLTAG_413__HTMLTAG_414___10。推送的聲明___HTMLTAG_415__HTMLTAG_416

推送聲明允許客戶 在請求授權時發送額外的上下文資訊,幫助政策擁有更多資料來做出決策:

# Request với pushed claims
curl -X POST "http://localhost:8080/realms/my-realm/protocol/openid-connect/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=urn:ietf:params:oauth:grant-type:uma-ticket" \
  -d "audience=my-app" \
  -d "permission=Document Resource#edit" \
  -d 'claim_token={"ip_address":["10.0.0.5"],"device_type":["desktop"],"risk_score":["low"]}' \
  -d "claim_token_format=urn:ietf:params:oauth:token-type:jwt" \
  -d "client_id=my-frontend" \
  -d "subject_token=${USER_ACCESS_TOKEN}"

HTMLTAG_421__HTMLTAG_422___11。聲明資訊點___HTMLTAG_423__HTMLTAG_424

聲明資訊點允許 自動從多個來源收集聲明__HTMLTAG_427___(HTTP 請求、外部服務)以在策略中使用:

預編碼_20

HTMLTAG_429__HTMLTAG_430___12。評估 API___HTMLTAG_431__HTMLTAG_432

Keycloak 管理控制台提供 評估工具,用於在部署之前測試權限。

HTMLTAG_437__HTMLTAG_438___12.1 使用評估工具___HTMLTAG_439__HTMLTAG_440

Clients → my-app → Authorization → Evaluate:
1. Identity Information:
   - User: chọn user cần test
   - Roles: chọn roles (hoặc tự động từ user)
2. Resources:
   - Thêm resources cần evaluate
3. Contextual Information:
   - Pushed Claims (JSON)
4. Click "Evaluate"

Results:
  ┌─────────────────────────────┬────────┐
  │ Permission                  │ Result │
  ├─────────────────────────────┼────────┤
  │ Document Access Permission  │ PERMIT │
  │ Document Delete Permission  │ DENY   │
  │ Admin Panel Permission      │ DENY   │
  └─────────────────────────────┴────────┘

HTMLTAG_441__HTMLTAG_442___12.2 透過 API 進行評估___HTMLTAG_443__HTMLTAG_444

# Evaluate permissions cho user cụ thể
curl -X POST "http://localhost:8080/admin/realms/my-realm/clients/${CLIENT_ID}/authz/resource-server/policy/evaluate" \
  -H "Authorization: Bearer ${ACCESS_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{
    "userId": "user-uuid",
    "roleIds": [],
    "resources": [
      {
        "name": "Document Resource",
        "scopes": ["view", "edit", "delete"]
      }
    ],
    "context": {
      "attributes": {
        "ip_address": ["10.0.0.5"]
      }
    },
    "entitlements": false
  }'

HTMLTAG_445__HTMLTAG_446___13。政策執行者___HTMLTAG_447__HTMLTAG_448

策略強制執行器是 Java 程式庫 整合到應用程式中以自動執行授權策略:

HTMLTAG_453__HTMLTAG_454___13.1 Spring Boot 整合___HTMLTAG_455__HTMLTAG_456

<!-- pom.xml -->
<dependency>
    <groupId>org.keycloak</groupId>
    <artifactId>keycloak-authz-client</artifactId>
    <version>26.0.0</version>
</dependency>
<dependency>
    <groupId>org.keycloak</groupId>
    <artifactId>keycloak-policy-enforcer</artifactId>
    <version>26.0.0</version>
</dependency>

預編碼_24

HTMLTAG_457__HTMLTAG_458___13.2 keycloak.json 設定___HTMLTAG_459__HTMLTAG_460

預編碼_25

HTMLTAG_461__HTMLTAG_462___13.3 Node.js 整合___HTMLTAG_463__HTMLTAG_464

// authorization.ts
import axios from 'axios';

interface PermissionResult {
  rsid: string;
  rsname: string;
  scopes: string[];
}

class KeycloakAuthzClient {
  private readonly baseUrl: string;
  private readonly realm: string;
  private readonly clientId: string;
  private readonly clientSecret: string;

  constructor(config: {
    baseUrl: string;
    realm: string;
    clientId: string;
    clientSecret: string;
  }) {
    this.baseUrl = config.baseUrl;
    this.realm = config.realm;
    this.clientId = config.clientId;
    this.clientSecret = config.clientSecret;
  }

  async checkPermission(
    userToken: string,
    resource: string,
    scope: string
  ): Promise<boolean> {
    try {
      const response = await axios.post(
        `${this.baseUrl}/realms/${this.realm}/protocol/openid-connect/token`,
        new URLSearchParams({
          grant_type: 'urn:ietf:params:oauth:grant-type:uma-ticket',
          audience: this.clientId,
          permission: `${resource}#${scope}`,
          response_mode: 'decision',
          subject_token: userToken,
        }),
        {
          headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
          auth: {
            username: this.clientId,
            password: this.clientSecret,
          },
        }
      );
      return response.data.result === true;
    } catch {
      return false;
    }
  }

  async getPermissions(userToken: string): Promise<PermissionResult[]> {
    const response = await axios.post(
      `${this.baseUrl}/realms/${this.realm}/protocol/openid-connect/token`,
      new URLSearchParams({
        grant_type: 'urn:ietf:params:oauth:grant-type:uma-ticket',
        audience: this.clientId,
        response_mode: 'permissions',
        subject_token: userToken,
      }),
      {
        headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
        auth: {
          username: this.clientId,
          password: this.clientSecret,
        },
      }
    );
    return response.data;
  }
}

// Express middleware
import { Request, Response, NextFunction } from 'express';

const authzClient = new KeycloakAuthzClient({
  baseUrl: 'http://localhost:8080',
  realm: 'my-realm',
  clientId: 'my-app',
  clientSecret: 'secret',
});

function enforcePermission(resource: string, scope: string) {
  return async (req: Request, res: Response, next: NextFunction) => {
    const token = req.headers.authorization?.replace('Bearer ', '');
    if (!token) {
      return res.status(401).json({ error: 'No token provided' });
    }

    const allowed = await authzClient.checkPermission(token, resource, scope);
    if (!allowed) {
      return res.status(403).json({
        error: `Permission denied: ${resource}#${scope}`,
      });
    }
    next();
  };
}

// Usage
app.get('/api/documents',
  enforcePermission('Document Resource', 'view'),
  documentsController.list
);

app.put('/api/documents/:id',
  enforcePermission('Document Resource', 'edit'),
  documentsController.update
);

app.delete('/api/documents/:id',
  enforcePermission('Document Resource', 'delete'),
  documentsController.delete
);

HTMLTAG_465__HTMLTAG_466___14。 kcadm.sh — 授權管理___HTMLTAG_467__HTMLTAG_468

預編碼_27

HTMLTAG_469__HTMLTAG_470___15。最佳實務___HTMLTAG_471__HTMLTAG_472

    ___HTMLTAG_474__HTMLTAG_475___從粗粒度開始→細粒度 — 先使用基於角色的,根據需要添加基於資源的__HTMLTAG_477___ ___HTMLTAG_478__HTMLTAG_479___使用資源類型__HTMLTAG_480___ — 將相同類型的資源分組,而不是為每個單獨的資源建立權限 ___HTMLTAG_482__HTMLTAG_483___使用評估 API 進行測試 — 在部署到生產環境之前始終測試權限 ___HTMLTAG_486__HTMLTAG_487___謹慎決策策略 — 一致更安全,但更限制性 肯定更安全,但更限制性 肯定___HTMLTAG_492__HT ___HTMLTAG_494__HTMLTAG_495___限制 JavaScript 策略 — 優先考慮內建策略類型,僅在真正需要時才使用 JavaScript ___HTMLTAG_498__HTMLTAG_499___監控權限評估效能 — 太多巢狀策略可能會減慢 ___HTMLTAG_502__HTMLTAG_503___匯出/匯入授權配置 — 使用 kcadm.sh 進行版本控制授權設定 ___HTMLTAG_506__HTMLTAG_507___將權限檢查與業務邏輯分開 - 在中間件/攔截器層級強制