使用 Curl 访问受 Microsoft Identity Platform (Azure Ad) 保护的 API

bra*_*buz 6 curl azure azure-active-directory .net-core azure-ad-msal

我们有一个 .net core 应用程序,它使用 Azure AD 进行身份验证 (MSAL/v2.0)。我们希望 Linux 应用程序能够从第一个应用程序访问 API。第二个应用程序没有用户上下文,并且将像curl 脚本一样进行交互。

通过阅读文档,我认为我应该使用 azure ad 注册第二个应用程序。我可以获得将目标应用程序作为受众的 JWT 令牌,但无法访问 api。我们已经能够使用从登录用户捕获的访问令牌从脚本访问(真实)api。

我创建了一个测试环境来寻找解决方案。

创建了一个由azure ad验证的.net core项目。它从我的工作站本地运行,未部署到天蓝色。Azure 身份验证适用于交互式用户。

注册了第二个应用程序并为其创建了一个秘密。

第一个应用程序配置了 id 和访问令牌以进行隐式授予,但未将其设置为公共客户端。

我在清单中定义了一个 approle“access_as_application”。

在“公开 API”下,我创建了一个范围“api”,并将另一个应用程序添加为授权客户端应用程序。

在 API 权限下,我添加了一个权限,选择了应用程序权限并检查了我之前创建的 approle。

我可以运行一个curl脚本并检索一个不记名令牌,该令牌在解码时会显示与我的应用程序匹配的受众。当我在curl 脚本中使用该令牌时,它会被重定向以登录。

显现:

{
    "id": "33b*******************************",
    "acceptMappedClaims": null,
    "accessTokenAcceptedVersion": 2,
    "addIns": [],
    "allowPublicClient": null,
    "appId": "3d8*******************************",
    "appRoles": [
        {
            "allowedMemberTypes": [
                "Application"
            ],
            "description": "Access webapp as an application.",
            "displayName": "access_as_application",
            "id": "ff5ea9b2*******************************",",
            "isEnabled": true,
            "lang": null,
            "origin": "Application",
            "value": "access_as_application"
        }
    ],
    "oauth2AllowUrlPathMatching": false,
    "createdDateTime": "2019-10-29T16:49:37Z",
    "groupMembershipClaims": null,
    "identifierUris": [
        "api://3d8*******************************"
    ],
    "informationalUrls": {
        "termsOfService": null,
        "support": null,
        "privacy": null,
        "marketing": null
    },
    "keyCredentials": [],
    "knownClientApplications": [],
    "logoUrl": null,
    "logoutUrl": "https://localhost:44321/signout-callback-oidc",
    "name": "WebApp",
    "oauth2AllowIdTokenImplicitFlow": true,
    "oauth2AllowImplicitFlow": true,
    "oauth2Permissions": [
        {
            "adminConsentDescription": "consent for api",
            "adminConsentDisplayName": "consent for api",
            "id": "a4b2*******************************",",
            "isEnabled": true,
            "lang": null,
            "origin": "Application",
            "type": "Admin",
            "userConsentDescription": null,
            "userConsentDisplayName": null,
            "value": "api"
        }
    ],
    "oauth2RequirePostResponse": false,
    "optionalClaims": null,
    "orgRestrictions": [],
    "parentalControlSettings": {
        "countriesBlockedForMinors": [],
        "legalAgeGroupRule": "Allow"
    },
    "passwordCredentials": [
        {
            "customKeyIdentifier": null,
            "endDate": "2299-12-31T05:00:00Z",
            "keyId": "e03c4*******************************",",
            "startDate": "2019-10-31T20:05:42.56Z",
            "value": null,
            "createdOn": "2019-10-31T20:05:42.7555795Z",
            "hint": "00_",
            "displayName": "webappsecret"
        }
    ],
    "preAuthorizedApplications": [
        {
            "appId": "a4b*******************************",
            "permissionIds": [
                "23b*******************************"
            ]
        },
        {
            "appId": "3d8*******************************",
            "permissionIds": [
                "23b*******************************"
            ]
        }
    ],
    "publisherDomain": "brainbuzgmail.onmicrosoft.com",
    "replyUrlsWithType": [
        {
            "url": "https://localhost:44321/signin-oidc",
            "type": "Web"
        },
        {
            "url": "https://localhost:44321/",
            "type": "Web"
        }
    ],
    "requiredResourceAccess": [
        {
            "resourceAppId": "3d8*******************************",
            "resourceAccess": [
                {
                    "id": "23b*******************************",
                    "type": "Scope"
                },
                {
                    "id": "ff5ea*******************************",",
                    "type": "Role"
                }
            ]
        },
        {
            "resourceAppId": "a4b*******************************",
            "resourceAccess": [
                {
                    "id": "a37a*******************************",",
                    "type": "Scope"
                },
                {
                    "id": "ccf78*******************************",",
                    "type": "Role"
                }
            ]
        },
        {
            "resourceAppId": "00000003-0000-0000-c000-000000000000",
            "resourceAccess": [
                {
                    "id": "e1fe*******************************",",
                    "type": "Scope"
                }
            ]
        }
    ],
    "samlMetadataUrl": null,
    "signInUrl": null,
    "signInAudience": "AzureADMyOrg",
    "tags": [],
    "tokenEncryptionKeyId": null
}
Run Code Online (Sandbox Code Playgroud)

变量 $ 在环境中设置。令牌从成功的请求中捕获并设置为 $TOKEN。由于本地应用程序使用自签名证书,因此在curl中使用了-k不安全标志。

curl -X POST -d "grant_type=client_credentials&client_id=$CLIENTID&client_secret=$SECRET&resource=$SCOPE" https://login.microsoftonline.com/$TENANT/oauth2/token

curl -k 'https://localhost:44321/api/' \
-H 'Accept: application/json' \
-H "Authorization: Bearer $TOKEN" \
-H 'Sec-Fetch-Mode: cors' -H 'Content-Type: application/json' --compressed
Run Code Online (Sandbox Code Playgroud)

部分响应重定向到登录页面,看起来客户端被要求获取 id 令牌,即使它具有有效的访问令牌:

< HTTP/2 302
< location: https://login.microsoftonline.com/****/oauth2/v2.0/authorize?client_id=***&redirect_uri=https%3A%2F%2Flocalhost%3A44321%2Fsignin-oidc&response_type=id_token
Run Code Online (Sandbox Code Playgroud)

Sta*_*ong 1

如果您只想让 Linux 应用程序调用受 Azure AD 保护的 .net core 应用程序的 API,这是服务调用流程的服务,无需重定向到/authorize端点,因为通常此端点是用户的步骤之一登录。

根据您的描述,您已成功获取访问令牌,您可以在 API 请求中使用此令牌作为 Authorization Bearer header 直接调用您的 .net core 应用程序。

TodoListService该演示的 peoject是一个示例 API 端演示,将对您有所帮助。在您的情况下,您应该做一些修改以使服务到服务调用工作。

Controllers/TodoListController.cs1.将其中的内容替换TodoListService 为以下代码:

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Identity.Web.Resource;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Security.Claims;
using TodoListService.Models;

namespace TodoListService.Controllers
{
    [Authorize]
    [Route("api/[controller]")]
    public class TodoListController : Controller
    {
        static readonly ConcurrentBag<TodoItem> TodoStore = new ConcurrentBag<TodoItem>();

        /// <summary>
        /// The Web API will only accept tokens 1) for users, and 
        /// 2) having the access_as_user scope for this API
        /// </summary>
        static readonly string[] scopeRequiredByApi = new string[] { "access_as_application" };

        // GET: api/values
        [HttpGet]
        public IEnumerable<TodoItem> Get()
        {
         //   HttpContext.VerifyUserHasAnyAcceptedScope(scopeRequiredByApi);
            string owner = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
            return TodoStore.Where(t => t.Owner == owner).ToList();
        }

        // POST api/values
        [HttpPost]
        public void Post([FromBody]TodoItem todo)
        {
            //check roles claim in token start
            Claim scopeClaim = HttpContext.User?.FindFirst("http://schemas.microsoft.com/ws/2008/06/identity/claims/role");
            if (scopeClaim == null || !scopeClaim.Value.Split(' ').Intersect(scopeRequiredByApi).Any())
            {
                HttpContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
                string message = $"The 'roles' claim does not contain scopes '{string.Join(",", scopeRequiredByApi)}' or was not found";
                throw new HttpRequestException(message);
            }
            //check roles claim end
            string owner = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
            TodoStore.Add(new TodoItem { Owner = owner, Title = "test!!" });
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

在此输入图像描述

  1. 在项目WebApiServiceCollectionExtensions.csMicrosoft.Identity.Web第 80 行中,替换为以下代码以确保检查您的角色声明:

    && !context.Principal.Claims.Any(y => y.Type == " http://schemas.microsoft.com/ws/2008/06/identity/claims/role "))

在此输入图像描述

在这里进行测试,发布请求: 在此输入图像描述

获取记录: 在此输入图像描述

希望能帮助到你 。