繁体   English   中英

Azure B2c 安全组授权通过自定义策略使用 Rest 后调用

[英]Azure B2c security groups authorization through custom policy using Rest Post call

我正在尝试使用能够部署 REST API 的自定义策略来获取组声明


    <ClaimsSchema>
          <ClaimType Id="groups">
            <DisplayName>B2C-user-test,B2C-admin-test</DisplayName>
            <DataType>stringCollection</DataType>
          </ClaimType>
    
        <!--Demo: List of permitted  security groups user can sign-in.
            Null or empty means, user any user can sign-in.
            This claim sends to REST API-->
        <ClaimType Id="onlyMembersOf">
            <DisplayName>onlyMembersOf</DisplayName>
            <DataType>string</DataType>
          </ClaimType>
    
        </ClaimsSchema>
        </BuildingBlocks>
          
      <ClaimsProviders>
        <ClaimsProvider>
          <DisplayName>Local Account</DisplayName>
          <TechnicalProfiles>
            <TechnicalProfile Id="SelfAsserted-LocalAccountSignin-Email">
              <OutputClaims>
                <!--Demo: Add the groups claim type to the output claim collection-->
                <OutputClaim ClaimTypeReferenceId="groups" />
              </OutputClaims>
              <ValidationTechnicalProfiles>
                <!-- Demo: Make sure you first call the login-NonInteractive technical profile, to get the user ID.
                     Then call the role-based access control REST API to get adn validate user's groups -->
                <ValidationTechnicalProfile ReferenceId="login-NonInteractive" />
                <ValidationTechnicalProfile ReferenceId="REST-RBAC" />
              </ValidationTechnicalProfiles>        
            </TechnicalProfile>
          </TechnicalProfiles>
        </ClaimsProvider>
    
        <!-- Local account Sign-In claims provider -->
        <ClaimsProvider>
          <DisplayName>Local Account SignIn</DisplayName>
          <TechnicalProfiles>
             <TechnicalProfile Id="login-NonInteractive">
              <Metadata>
                <Item Key="client_id">444b09a2-0f8b-4f05-b454-54495b5ef601</Item>
                <Item Key="IdTokenAudience">bd80807b-81d0-4732-a517-1132b128206c</Item>
              </Metadata>
              <InputClaims>
                <InputClaim ClaimTypeReferenceId="client_id" DefaultValue="444b09a2-0f8b-4f05-b454-54495b5ef601" />
                <InputClaim ClaimTypeReferenceId="resource_id" PartnerClaimType="resource" DefaultValue="bd80807b-81d0-4732-a517-1132b128206c" />
              </InputClaims>
            </TechnicalProfile>
          </TechnicalProfiles>
        </ClaimsProvider>
    
    <ClaimsProvider>
          <DisplayName>REST APIs</DisplayName>
          <TechnicalProfiles>
            <TechnicalProfile Id="REST-RBAC">
              <DisplayName>Read and validate user's groups</DisplayName>
              <Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.RestfulProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
              <Metadata>
                <!--Demo: Change the service URL with your REST API location-->
                <Item Key="ServiceUrl">https://test.azurewebsites.net/api/Identity/IsMemberOf</Item>
                
                <!--Demo: Change the AuthenticationType to basic or ClientCertificate.
                For more information, see: https://docs.microsoft.com/en-us/azure/active-directory-b2c/active-directory-b2c-custom-rest-api-netfw-secure-cert-->
                <Item Key="AuthenticationType">None</Item>
                <Item Key="SendClaimsIn">Body</Item>
                <Item Key="AllowInsecureAuthInProduction">true</Item>
              </Metadata>
              <CryptographicKeys>
                <Key Id="BasicAuthenticationUsername" StorageReferenceId="B2C_1A_RestApiUsername" />
                <Key Id="BasicAuthenticationPassword" StorageReferenceId="B2C_1A_RestApiPassword" />
              </CryptographicKeys>
              <InputClaims>
                <InputClaim ClaimTypeReferenceId="objectId" />
                <!--Demo: set the DefaultValue to empty string or comma delimiter list 
                of security groups to validate-->
                <InputClaim ClaimTypeReferenceId="onlyMembersOf" DefaultValue="B2C-user-test" />
              </InputClaims>
              <OutputClaims>
                <OutputClaim ClaimTypeReferenceId="groups" />
              </OutputClaims>
              <UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" />
            </TechnicalProfile>
          </TechnicalProfiles>
        </ClaimsProvider>
      </ClaimsProviders>

所以上面的 TrustFrameworkExtensions 策略调用 Rest 方法,但它返回 catch 语句异常


    catch (Exception ex)
                {
                    if (ex.Message.Contains("Request_ResourceNotFound"))
                    {
                        return StatusCode((int)HttpStatusCode.Conflict, new B2CResponseModel("Can not read user groups, user not found", HttpStatusCode.Conflict));
                    }
    
                    return StatusCode((int)HttpStatusCode.Conflict, new B2CResponseModel("Can not read user groups", HttpStatusCode.Conflict));
                }"

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using AADB2C.RBAC.Sample.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using System.Net.Http;


namespace AADB2C.RBAC.Sample.Controllers
{
    [Route("api/[controller]/[action]")]
    public class IdentityController : Controller
    {
        private readonly AppSettingsModel AppSettings;

        // Demo: Inject an instance of an AppSettingsModel class into the constructor of the consuming class, 
        // and let dependency injection handle the rest
        public IdentityController(IOptions<AppSettingsModel> appSettings)
        {
            this.AppSettings = appSettings.Value;
        }

        [HttpPost(Name = "IsMemberOf")]
        public async Task<ActionResult> IsMemberOf()
        {
            string input = null;

            // If not data came in, then return
            if (this.Request.Body == null)
            {
                return StatusCode((int)HttpStatusCode.Conflict, new B2CResponseModel("Request content is null", HttpStatusCode.Conflict));
            }

            //Read the input claims from the request body
            using (StreamReader reader = new StreamReader(Request.Body, Encoding.UTF8))
            {
                input = await reader.ReadToEndAsync();
            }

            //string input = Request.Content.ReadAsStringAsync().Result;

            //string content = "";
            //System.Web.HttpContext.Current.Request.InputStream.Position = 0;
            //using (var reader = new StreamReader(
            //         Request.InputStream, System.Text.Encoding.UTF8, true, 4096, true))
            //{
            //    content = reader.ReadToEnd();
            //}
            ////Rest
            //System.Web.HttpContext.Current.Request.InputStream.Position = 0;

            // Check input content value
            if (string.IsNullOrEmpty(input))
            {
                return StatusCode((int)HttpStatusCode.Conflict, new B2CResponseModel("Request content is empty", HttpStatusCode.Conflict));
            }

            // Convert the input string into InputClaimsModel object
            InputClaimsModel inputClaims = InputClaimsModel.Parse(input);

            if (inputClaims == null)
            {
                return StatusCode((int)HttpStatusCode.Conflict, new B2CResponseModel("Can not deserialize input claims", HttpStatusCode.Conflict));
            }

            if (string.IsNullOrEmpty(inputClaims.objectId))
            {
                return StatusCode((int)HttpStatusCode.Conflict, new B2CResponseModel("User 'objectId' is null or empty", HttpStatusCode.Conflict));
            }


            try
            {
                AzureADGraphClient azureADGraphClient = new AzureADGraphClient(this.AppSettings.Tenant, this.AppSettings.ClientId, this.AppSettings.ClientSecret);

                // Demo: Get user's groups
                GraphGroupsModel groups = await azureADGraphClient.GetUserGroup(inputClaims.objectId);

                // Demo: Add the groups to string collections
                List<string> groupsList = new List<string>();
                foreach (var item in groups.value)
                {
                    groupsList.Add(item.displayName);
                }

                // Demo: Set the output claims
                OutputClaimsModel output = new OutputClaimsModel() { groups = groupsList };

                // Demo: Check if user needs to be a member of a security group
                if (!string.IsNullOrEmpty(inputClaims.onlyMembersOf))
                {
                    List<string> onlyMembersOf = inputClaims.onlyMembersOf.ToLower().Split(',').ToList<string>();
                    bool isMemberOf = false;
                    foreach (var item in output.groups)
                    {
                        if (onlyMembersOf.Contains(item.ToLower()))
                        {
                            isMemberOf = true;
                            break;
                        }
                    }

                    // Demo: Throw error if user is not member of one of the security groups
                    if (isMemberOf == false)
                    {
                        return StatusCode((int)HttpStatusCode.Conflict, new B2CResponseModel("You are not authorized to sign-in to this application.", HttpStatusCode.Conflict));
                    }
                }

                // Demo: Return the groups collection
                return Ok(output);
            }
            catch (Exception ex)
            {
                if (ex.Message.Contains("Request_ResourceNotFound"))
                {
                    return StatusCode((int)HttpStatusCode.Conflict, new B2CResponseModel("Can not read user groups, user not found", HttpStatusCode.Conflict));
                }

                return StatusCode((int)HttpStatusCode.Conflict, new B2CResponseModel("Can not read user groups", HttpStatusCode.Conflict));
            }

        }

    }
}

所以我已经达到了自定义策略使用 REST POST 方法来获取组详细信息的地步,但是提到的这段代码没有提供组详细信息,因为它去 catch 语句并且抛出无法读取用户组。这里的问题是我不能使用 localhost运行 Rest API 并通过自定义策略命中 API 的断点,因为它不受支持,我尝试使用返回。

任何帮助或示例都会非常有帮助

我的做法有点不同,但我的方法比使用与你的类似代码更幸运,我在许多示例中也发现了这种代码。 我也更喜欢这种方法,而不是 mrochon.azurewebsites.net 上经常引用的指南,因为这不需要设置应用程序注册,也不需要在任何地方存储机密,并且对于多租户应用程序效果更好。

我所做的是将以下 OutputClaim 添加到针对 login.microsoftonline.com 进行登录的技术配置文件中。 这为我们提供了身份提供者提供的用户令牌。 然后我们以后可以使用这个token直接查询Graph API。

<OutputClaim ClaimTypeReferenceId="identityProviderAccessToken" PartnerClaimType="{oauth2:access_token}" />

然后,我使用另一个 OrchestrationStep 调用我的 TechnicalProfile,它执行 REST POST。 我的 TechnicalProfile 看起来像这样:

<TechnicalProfile Id="GetUserGroups">
  <DisplayName>Retrieves security groups assigned to the user</DisplayName>
  <Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.RestfulProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
  <Metadata>
    <Item Key="ServiceUrl">https://PATH-TO-YOUR-REST-API/groups</Item>
    <Item Key="AuthenticationType">None</Item>
    <Item Key="SendClaimsIn">Body</Item>
  </Metadata>
  <InputClaims>
    <InputClaim ClaimTypeReferenceId="objectId" /><!-- optional -->
    <InputClaim ClaimTypeReferenceId="tenantId" /><!-- optional -->
    <InputClaim ClaimTypeReferenceId="identityProviderAccessToken" />
  </InputClaims>
  <OutputClaims>
    <OutputClaim ClaimTypeReferenceId="groups" />
  </OutputClaims>
  <UseTechnicalProfileForSessionManagement ReferenceId="SM-Noop" />
</TechnicalProfile>

用户令牌将传送到您的 REST API 同名: identityProviderAccessToken

这个token可以用来直接查找Graph API,所以只需要这样的代码(roleGroups.Keys用来限制我们想要返回的组):

var graphService = await GraphService.CreateOnBehalfOfUserAsync(inputClaims.identityProviderAccessToken);
var memberGroups = await graphService.CheckMemberGroupsDelegateAsync(roleGroups.Keys);

这是我在上面使用的 GraphService:

public class GraphService
{
    private readonly IGraphServiceClient _client;

    private GraphService(IGraphServiceClient client)
    {
        _client = client;
    }

    public static async Task<GraphService> CreateOnBehalfOfUserAsync(string userToken)
    {

        GraphServiceClient graphClient = new GraphServiceClient(
            "https://graph.microsoft.com/v1.0",
            new DelegateAuthenticationProvider(async (requestMessage) =>
            {
            requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", userToken);
            }));

        return new GraphService(graphClient);
    }

    public async Task<IEnumerable<string>> CheckMemberGroupsDelegateAsync(IEnumerable<string> groupIds)
    {
        //You can check up to a maximum of 20 groups per request (see graph api doc).
        var batchSize = 20;

        var tasks = new List<Task<IDirectoryObjectCheckMemberGroupsCollectionPage>>();
        foreach (var groupsBatch in groupIds.Batch(batchSize))
        {
            tasks.Add(_client.Me.CheckMemberGroups(groupsBatch).Request().PostAsync());
        }
        await Task.WhenAll(tasks);

        return tasks.SelectMany(x => x.Result.ToList());
    }
}

如果您需要扩展来对结果进行批处理,这也是:

public static IEnumerable<IEnumerable<T>> Batch<T>(this IEnumerable<T> items,
    int maxItems)
{
    return items.Select((item, inx) => new { item, inx })
        .GroupBy(x => x.inx / maxItems)
        .Select(g => g.Select(x => x.item));
}

祝你好运。 在这些自定义政策中,这是一次疯狂的旅程。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM