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ặcIn
).- 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:
https://<logicapp>.azurewebsites.net/api/wf_arithmetic_add/triggers/RcvReq/invoke?api-version=2022-05-01
Ví dụ về ánh xạ frontend APIM:
/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ặcset-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
<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.