[英]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.