0
0
Lập trình
TT

🔐 Kiểm Soát Quyền Chi Tiết cho Logic App Standard với APIM

Đăng vào 3 ngày trước

• 6 phút đọc

Giới thiệu

Trong bài viết trước, tôi đã hướng dẫn cách kích hoạt Easy Auth cho một Logic App Standard. Việc này giúp bảo vệ điểm cuối của workflow bằng Azure Entra ID (Xác thực dịch vụ ứng dụng). Trong bài viết này, chúng ta sẽ đi sâu hơn vào việc sử dụng Azure API Management (APIM) để thực thi quyền truy cập dựa trên vai trò trước khi yêu cầu được gửi đến Logic App.

🔎 Lưu ý: Trong cấu hình này, chúng ta chỉ phơi bày Logic Apps qua APIM, không phơi bày chúng như một MCP Server thông qua APIM.

1. Đăng ký vai trò ứng dụng

Trong đăng ký ứng dụng Entra mà đại diện cho API của bạn:

  • Định nghĩa các vai trò như:
    • wf_arithmetic_add
    • wf_arithmetic_sub
  • Gán các vai trò này cho các service principal / người dùng mà bạn muốn cho phép.

Bạn sẽ thấy các vai trò này được phản ánh trong phần permissions API khi đã đồng ý.

2. Kết nối Logic App Standard vào APIM

Tính đến thời điểm hiện tại, APIM không cung cấp wizard nhập khẩu trực tiếp cho Logic App Standard (đã kích hoạt EasyAuth). Do đó, chúng ta sẽ tạo API một cách thủ công:

  • Trong APIM, tạo một API mới (ví dụ: LAStandardAPI).
  • Thêm các thao tác như Add, Sub,... để phản ánh các trigger/action của workflow của bạn.
  • Cấu hình mỗi thao tác backend để chỉ đến điểm cuối HTTPS của Logic App (đã được bảo vệ bởi EasyAuth).

💡 Mẹo:
Trong Logic App Standard, mỗi workflow được triển khai dưới một tên thư mục duy nhất và phơi bày một điểm cuối HTTP trigger với SAS token (sig=...) trong URL.

Ví dụ:

  • https://mcpblogdemo.azurewebsites.net:443/api/wf_arithmetic_add/triggers/RcvReq/invoke?api-version=2022-05-01&sp=%2Ftriggers%2FRcvReq%2Frun&sv=1.0&sig=Key
  • https://mcpblogdemo.azurewebsites.net:443/api/wf_arithmetic_sub/triggers/RcvReq/invoke?api-version=2022-05-01&sp=%2Ftriggers%2FRcvReq%2Frun&sv=1.0&sig=Key
  • https://mcpblogdemo.azurewebsites.net:443/api/wf_arithmetic_mul/triggers/RcvReq/invoke?api-version=2022-05-01&sp=%2Ftriggers%2FRcvReq%2Frun&sv=1.0&sig=Key

Những URL này hoạt động, nhưng chúng bỏ qua xác thực dựa trên vai trò và chỉ dựa vào SAS tokens.

Để đơn giản hóa cấu hình API Management và tăng cường bảo mật:

  • Sử dụng một tên nhất quán cho HTTP trigger trong mỗi workflow (ví dụ: RcvReq hoặc In).
  • Loại bỏ tham số truy vấn sig khi sử dụng APIM (Easy Auth + JWT thay thế cho SAS).
  • Xây dựng URL backend một cách động và áp dụng một chính sách viết lại thay vì mã hóa cứng mỗi đường dẫn workflow.

Ví dụ về mẫu URL backend:

Copy
https://<logicapp>.azurewebsites.net/api/wf_arithmetic_add/triggers/RcvReq/invoke?api-version=2022-05-01

Ví dụ về ánh xạ frontend APIM:

Copy
/api/wf_arithmetic_add → wf_arithmetic_add/triggers/RcvReq/invoke?api-version=2022-05-01
/api/wf_arithmetic_sub → wf_arithmetic_sub/triggers/RcvReq/invoke?api-version=2022-05-01
/api/wf_arithmetic_mul → wf_arithmetic_mul/triggers/RcvReq/invoke?api-version=2022-05-01

Thực hiện điều này với rewrite-uri hoặc set-backend-service trong APIM, để tất cả các cuộc gọi workflow đều được bảo vệ bởi vai trò và bạn không còn phụ thuộc vào SAS tokens.

3. Xác thực JWT trong APIM

Trong chính sách inbound (được áp dụng cho tất cả các thao tác), sử dụng validate-jwt với cấu hình OpenID của tenant của bạn và nhà phát hành. Cấu hình này:

  • Sử dụng cấu hình OpenID riêng cho tenant (điểm cuối v2).
  • Chấp nhận tokens từ nhà phát hành tenant (https://sts.windows.net/<TENANT_ID>/).
  • Trả về 401 Unauthorized khi xác thực không thành công (vấn đề về chữ ký / nhà phát hành).
  • (Tùy chọn nhưng được khuyến nghị) Bạn có thể thêm một khối <audiences> sau này để ràng buộc audience của API nếu cần.
xml Copy
<policies>
  <inbound>
    <base />

    <validate-jwt header-name="Authorization"
                  failed-validation-httpcode="401"
                  failed-validation-error-message="Unauthorized.">
      <openid-config url="https://login.microsoftonline.com/<TENANT_ID>/v2.0/.well-known/openid-configuration" />
      <issuers>
        <issuer>https://sts.windows.net/<TENANT_ID>/</issuer>
      </issuers>

    <!-- Tùy chọn bộ lọc thô: yêu cầu ít nhất một trong các vai trò workflow phải có.
           Điều này thu hẹp tokens sớm nhưng vẫn thực thi vai trò chính xác bên dưới. -->
      <!--
      <required-claims>
        <claim name="roles" match="any">
          <value>wf_arithmetic_add</value>
          <value>wf_arithmetic_sub</value>
          <value>wf_arithmetic_mul</value>
        </claim>
      </required-claims>
      -->
    </validate-jwt>

    <set-variable name="token" value="@{
      var authHeader = context.Request.Headers.GetValueOrDefault("Authorization", "");
      if (!string.IsNullOrEmpty(authHeader) && authHeader.StartsWith("Bearer "))
      {
        return authHeader.Substring(7);
      }
      return null;
    }" />

    <set-variable name="roles" value="@(
      context.Variables.GetValueOrDefault("token", "").AsJwt()?.Claims["roles"]?.FirstOrDefault() ?? ""
    )" />

    <set-variable name="roles_csv" value="@{
      var tok = (string)context.Variables.GetValueOrDefault("token", "");
      var jwt = string.IsNullOrEmpty(tok) ? null : tok.AsJwt();
      var arr = (jwt != null && jwt.Claims != null && jwt.Claims.ContainsKey("roles"))
        ? jwt.Claims["roles"]
        : new string[0];
      return string.Join(",", arr);   // e.g. "wf_arithmetic_add,wf_arithmetic_sub"
    }" />

    <set-variable name="wf" value="@{
      var p = context.Request.Url.Path ?? "";
      if (p.EndsWith("/")) { p = p.Substring(0, p.Length - 1); }
      var i = p.LastIndexOf("/");
      return (i >= 0 ? p.Substring(i + 1) : p).ToLower();
    }" />

    <set-variable name="isAuthorized" value="@{
      var wf = ((string)context.Variables.GetValueOrDefault("wf","")).ToLower();
      var roles = ((string)context.Variables.GetValueOrDefault("roles_csv","")).ToLower().Replace(" ", "");
      if (string.IsNullOrEmpty(wf) || string.IsNullOrEmpty(roles)) { return false; }

      // kiểm tra chứa danh sách phân cách bằng dấu phẩy để tránh các kết quả trùng khớp một phần
      var haystack = "," + roles + ",";
      var needle = "," + wf + ",";
      return haystack.Contains(needle);
    }" />

    <choose>
      <when condition="@((bool)context.Variables["isAuthorized"])">
        <!-- Đã được ủy quyền -->
      </when>
      <otherwise>
        <return-response>
          <set-status code="403" reason="Forbidden" />
          <set-body>@("{\"error\":\"Vai trò thiếu hoặc không hợp lệ\"}")</set-body>
        </return-response>
      </otherwise>
    </choose>

    <!-- Viết lại frontend /api/{workflow} thành backend trigger Logic App -->
    <rewrite-uri template="@{
        var wf = (string)context.Variables["wf"];
        return $"/api/{wf}/triggers/RcvReq/invoke?api-version=2022-05-01"; 
    }" />

  </inbound>
  <backend>
    <base />
  </backend>
  <outbound>
    <base />
  </outbound> 
  <on-error>
    <base />
  </on-error>
</policies>

4. Kiểm tra

Khi chính sách được áp dụng trong APIM, bạn có thể kiểm tra hành vi bằng cách gọi API với các vai trò khác nhau trong JWT.

✅ Trường hợp 1: Vai trò Được ủy quyền

Khi token chứa vai trò wf_arithmetic_add, yêu cầu đến /api/wf_arithmetic_add thành công với 200 OK.

❌ Trường hợp 2: Vai trò Không được ủy quyền

Khi cùng một token được sử dụng để gọi /api/wf_arithmetic_mul mà không có vai trò wf_arithmetic_mul, APIM từ chối yêu cầu với 403 Forbidden và trả về lỗi:

✅ Kết quả

Điều này chứng minh rằng:

  • APIM xác thực JWT và trích xuất các vai trò.
  • Quyền truy cập chỉ được cấp khi vai trò khớp với tên workflow.
  • Bất kỳ sự không khớp nào sẽ dẫn đến 403 Forbidden trước khi Logic App được thực thi.

✅ Xác thực dựa trên vai trò đã được thực thi thành công tại APIM, trong khi EasyAuth tiếp tục bảo vệ backend của Logic App.

Gợi ý câu hỏi phỏng vấn
Không có dữ liệu

Không có dữ liệu

Bài viết được đề xuất
Bài viết cùng tác giả

Bình luận

Chưa có bình luận nào

Chưa có bình luận nào