【问题标题】:`[Authorize(Roles = "admin")]` Infinite loop ASP.NET MVC and Azure Active Directory B2C`[Authorize(Roles = "admin")]` 无限循环 ASP.NET MVC 和 Azure Active Directory B2C
【发布时间】:2018-01-30 22:16:56
【问题描述】:

我试图只允许具有“全局管理员”角色的 Azure Active Directory B2C 用户访问以下类(这就是我包含Authorize 命令的原因):

[Authorize(Roles = "admin")]
public class UserProfileController : Controller
{
    ... controller class ...
}

我的 Startup 类看起来像这样:

public partial class Startup
{
    private static string clientId = ConfigurationManager.AppSettings["ida:ClientId"];
    private static string appKey = ConfigurationManager.AppSettings["ida:ClientSecret"];
    private static string aadInstance = ConfigurationManager.AppSettings["ida:AADInstance"];
    private static string tenantId = ConfigurationManager.AppSettings["ida:TenantId"];
    private static string postLogoutRedirectUri = ConfigurationManager.AppSettings["ida:PostLogoutRedirectUri"];
    // This is the resource ID of the AAD Graph API.  We'll need this to request a token to call the Graph API.
    private static string graphResourceId = "https://graph.microsoft.com";
    private static readonly string Authority = aadInstance + tenantId;
    public static GraphServiceClient graphClient = null;

    public static GraphServiceClient GetGraphServiceClient()
    {
        return graphClient;
    }

    public void ConfigureAuth(IAppBuilder app)
    {
        app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);

        app.UseCookieAuthentication(new CookieAuthenticationOptions()
        {
            CookieSecure = CookieSecureOption.Always
        });

        app.UseOpenIdConnectAuthentication(
            new OpenIdConnectAuthenticationOptions
            {
                ClientId = clientId,
                Authority = Authority,
                PostLogoutRedirectUri = postLogoutRedirectUri,

                Notifications = new OpenIdConnectAuthenticationNotifications()
                {
                    // If there is a code in the OpenID Connect response, redeem it for an access token and refresh token, and store those away.
                   AuthorizationCodeReceived = (context) => 
                   {
                       var code = context.Code;
                       ClientCredential credential = new ClientCredential(clientId, appKey);
                       string signedInUserID = context.AuthenticationTicket.Identity.FindFirst(ClaimTypes.NameIdentifier).Value;

                       TokenCache userTokenCache = new ADALTokenCache(signedInUserID);

                       AuthenticationContext authContext = new AuthenticationContext(Authority, userTokenCache);
                       AuthenticationResult result = authContext.AcquireTokenByAuthorizationCode(
                           code, new Uri(HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path)), credential, graphResourceId);

                       string token = result.AccessToken;

                       try
                       {
                           graphClient = new GraphServiceClient(
                               new DelegateAuthenticationProvider(
                                   (requestMessage) =>
                                   {
                                       requestMessage.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("bearer", token);

                                       return Task.FromResult(0);
                                   }));
                       }
                       catch (Exception e)
                       {
                           System.Diagnostics.Debug.WriteLine("Failed to create graph client: " + e.Message);
                       }

                       return Task.FromResult(0);
                   }
                }
            });
    }
}

问题是:当我单击实例化 UserProfileController 的按钮时,AuthorizationCodeReceived = (context) => 代码行内的代码会在无限循环中一次又一次地被调用。 我该怎么做修复无限循环,以便只有 Azure Active Directory B2C“全局管理员”可以实例化 UserProfileController?

【问题讨论】:

    标签: c# asp.net-mvc azure oauth-2.0


    【解决方案1】:

    [授权(角色=“管理员”)]

    由于您使用 Authorize 属性来检查用户的角色,因此您需要确保当前用户的声明具有有效的角色声明。您可以利用以下代码 sn-p 来检查您当前的用户声明:

    return Json((User.Identity as ClaimsIdentity).Claims.Select(c => new { key = c.Type, value = c.Value }),JsonRequestBehavior.AllowGet);
    

    问题是:当我单击实例化 UserProfileController 的按钮时,AuthorizationCodeReceived = (context) => 代码行内的代码会在无限循环中一次又一次地调用。

    您可以覆盖AuthorizeAttribute 下的HandleUnauthorizedRequest 方法,并按如下方式定义您的自定义授权属性:

    public class MyAuthorize : AuthorizeAttribute
    {
        protected override void HandleUnauthorizedRequest(System.Web.Mvc.AuthorizationContext filterContext)
        {
            filterContext.Result = new ContentResult() { Content = "You don't have rights to take actions" };
        }
    }
    

    然后,您可以如下装饰您的 UserProfileController 控制器:

    [MyAuthorize(Roles = "admin")]
    public class UserProfileController : Controller
    {
        //TODO:
    }
    

    我试图只允许具有“全局管理员”角色的 Azure Active Directory B2C 用户访问以下类

    AuthorizationCodeReceived 委托方法下,获取访问令牌后,您需要利用 Microsoft Graph 客户端库来检查当前用户是否为全局管理员/公司管理员。如果当前用户是全局管理员/公司管理员,则需要指定角色声明如下:

    context.AuthenticationTicket.Identity.AddClaim(new Claim(context.AuthenticationTicket.Identity.RoleClaimType, "admin"));
    

    注意:为了检查一个用户是否是全局管理员,你可以检索当前用户目录下的角色,然后使用getMemberObjects API 检索当前用户的组、角色是成员,然后检查全局管理员角色id是否在当前用户的MemberObjects中。

    //List directory roles, https://developer.microsoft.com/en-us/graph/docs/api-reference/v1.0/api/directoryrole_list
    var roles=await graphClient.DirectoryRoles.Request().GetAsync();
    
    //user: getMemberObjects ,https://developer.microsoft.com/en-us/graph/docs/api-reference/v1.0/api/user_getmemberobjects
    

    更新:

    我检查了我这边的实现。这是检查当前登录用户角色的代码。

    var directoryRoles = await graphClient.DirectoryRoles.Request().GetAsync();
    var userRoles = await graphClient.Me.MemberOf.Request().GetAsync();
    
    var adminRole=directoryRoles.Where(role => role.DisplayName== "Company Administrator" || role.DisplayName == "Global Administrator").FirstOrDefault();
    if (userRoles.Count(role => role.Id == adminRole.Id) > 0)
    {
        context.AuthenticationTicket.Identity.AddClaim(new Claim(context.AuthenticationTicket.Identity.RoleClaimType, "admin"));
    }
    else
    {
        context.AuthenticationTicket.Identity.AddClaim(new Claim(context.AuthenticationTicket.Identity.RoleClaimType, "user"));
    }
    

    注意:要添加多个用户角色,您可以添加多个new Claim(context.AuthenticationTicket.Identity.RoleClaimType, "<role-name>") 角色声明。

    这是我修改后的自定义AuthorizeAttribute

    public class MyAuthorize : AuthorizeAttribute
    {
        private bool noPermission = false;
    
        public string Permissions { get; set; }
    
        protected override bool AuthorizeCore(HttpContextBase httpContext)
        {
            if (!base.AuthorizeCore(httpContext))
                return false;
    
            var permissionArrs = Permissions.Trim().Split('|');
    
            if (permissionArrs.ToList().Exists(p=>httpContext.User.IsInRole(p)))
            {
                return true;
            }
            else
            {
                noPermission = true;
                return false;
            }
        }
    
        protected override void HandleUnauthorizedRequest(System.Web.Mvc.AuthorizationContext filterContext)
        {
            if (noPermission)
                filterContext.Result = new ContentResult() { Content = "You don't have rights to take actions" };
            else
                base.HandleUnauthorizedRequest(filterContext);
        }
    }
    

    如下装饰UserProfileController

    [MyAuthorize(Permissions = "admin|co-admin")]
    public class UsersController : Controller
    {
       //TODO:
    }
    

    【讨论】:

    • 非常感谢您的帮助!我无法让return Json((User.Identity as ClaimsIdentity).Claims.Select(c => new { key = c.Type, value = c.Value }), JsonRequestBehavior.AllowGet); sn-p 工作,因为“用户”没有属性“身份”。但是,我能够得到以下工作:ClaimsPrincipal principal = HttpContext.Current.User as ClaimsPrincipal; foreach (System.Security.Claims.Claim claim in principal.Claims) {var type = claim.Type; var value = claim.Value;} 但是,问题是claim 始终是null。你知道这是为什么吗?再次感谢您。
    • Controller.User 继承自 Controller。另外,我在我的 MVC 控制器下检查了您的代码HttpContext.Current.User as ClaimsPrincipal,它可以按预期工作。请尝试调用ClaimsPrincipal.Current.Claims 在您的操作中获得索赔。此外,您是否在AuthorizationCodeReceived 委托方法中检查了context.AuthenticationTicket.Identity.Claims
    • 我想我快到了。就像您建议的原始答案一样,我使用var directoryRoles = await graphClient.DirectoryRoles.Request().GetAsync(); 列出了目录角色,遍历了这些角色,并找到了“公司管理员”角色的 ID。然后,我与var userRoles = await graphClient.Me.MemberOf.Request().GetAsync(); 的结果进行了比较。如果“公司管理员”角色的 ID 在 userRoles 中,那么我添加了这样的声明:context.AuthenticationTicket.Identity.AddClaim(new Claim(context.AuthenticationTicket.Identity.RoleClaimType, "admin"));。我还添加了MyAuthorize 类。
    • (继续上面的评论)。这在当前登录的用户是“公司管理员”时有效!但是,当当前登录的用户只是“访客”时,我看到了无限循环问题。是否缺少重定向到MyAuthorize 类以便显示错误消息的额外步骤?
    • 我测试了我这边的代码,您可以按照我的答案中的更新部分进行操作。
    猜你喜欢
    • 2016-08-13
    • 1970-01-01
    • 1970-01-01
    • 2023-03-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-06-26
    • 2021-01-11
    相关资源
    最近更新 更多