自定义角色提供程序在与角色一起应用授权属性时失败



我在 MVC4 ASP.net 自定义角色提供程序时遇到问题。我实现了一个非常轻量级的角色提供程序,它似乎工作正常,直到我改变

[Authorize]
public class BlahController:....
}

[Authorize(Roles="Administrator")]
public class BlahController:....
}

一旦我进行了更改,用户就不再进行身份验证,并且我会收到 401 错误。这很奇怪,因为我的 RoleProvider 基本上为 IsUSerInRole 返回 true,并且返回一个包含 GetUserRole 的"管理员"列表。 我在自定义 RoleProvider 中的每个方法上都设置了断点,发现没有调用任何断点。

接下来,我

实现了我自己的授权属性,该属性继承自授权属性。在这里,我放置了断点,以便我可以看到发生了什么。事实证明,由底层属性调用的User.IsInRole()返回了false。

我相信角色提供程序已正确设置。我的配置文件中有这个

<roleManager enabled="true" defaultProvider="SimplicityRoleProvider">
  <providers>
    <clear />
    <add name="SimplicityRoleProvider" type="Simplicity.Authentication.SimplicityRoleProvider" applicationName="Simplicity" />
  </providers>
</roleManager>

并使用此处所述的方法检查当前角色提供程序是当前角色提供程序:引用当前角色提供程序实例?,以产生正确的结果。但是,User.IsInRole 坚持返回 false。

我正在使用 Azure 访问控制服务,但我看不出这与自定义角色提供程序不兼容。

我该怎么做才能更正 IPrincipal 用户,以便 IsInRole 从我的自定义角色提供程序返回值?


角色提供程序源:

公共类 SimplicityRoleProvider : RoleProvider { private ILog log { get; set; }

    public SimplicityRoleProvider()
    {
        log = LogManager.GetLogger("ff");
    }        
    public override void AddUsersToRoles(string[] usernames, string[] roleNames)
    {
        log.Warn(usernames);
        log.Warn(roleNames);
    }
    public override string ApplicationName
    {
        get
        {
            return "Simplicity";
        }
        set
        {
        }
    }
    public override void CreateRole(string roleName)
    {
    }
    public override bool DeleteRole(string roleName, bool throwOnPopulatedRole)
    {
        return true;
    }
    public override string[] FindUsersInRole(string roleName, string usernameToMatch)
    {
        log.Warn(roleName);
        log.Warn(usernameToMatch);
        return new string[0];
    }
    public override string[] GetAllRoles()
    {
        log.Warn("all roles");
        return new string[0];
    }
    public override string[] GetRolesForUser(string username)
    {
        log.Warn(username);
        return new String[] { "Administrator" };
    }
    public override string[] GetUsersInRole(string roleName)
    {
        log.Warn(roleName);
        return new string[0];
    }
    public override bool IsUserInRole(string username, string roleName)
    {
        log.Warn(username);
        log.Warn(roleName);
        return true;
    }
    public override void RemoveUsersFromRoles(string[] usernames, string[] roleNames)
    {
    }
    public override bool RoleExists(string roleName)
    {
        log.Warn(roleName);
        return true;
    }
}

似乎System.Web.Security.Roles.GetRolesForUser(Username)在具有自定义AuthorizeAttribute和自定义RoleProvider时不会自动连接。

因此,在自定义 AuthorizeAttribute 中,您需要从数据源中检索角色列表,然后将它们与作为参数传递给 AuthorizeAttribute 的角色进行比较。

我在几篇博客文章中看到评论,暗示手动比较角色是不必要的,但是当我们覆盖 AuthorizeAttribute 时,似乎我们正在抑制这种行为,需要自己提供。


无论如何,我会介绍一下对我有用的方法。 希望它会有所帮助。

我欢迎就是否有更好的办法来做到这一点发表意见。

请注意,在我的情况下,AuthorizeAttribute 正在应用于 ApiController,尽管我不确定这是一条相关信息。

   public class RequestHashAuthorizeAttribute : AuthorizeAttribute
    {
        bool requireSsl = true;
        public bool RequireSsl
        {
            get { return requireSsl; }
            set { requireSsl = value; }
        }
        bool requireAuthentication = true;
        public bool RequireAuthentication
        {
            get { return requireAuthentication; }
            set { requireAuthentication = value; }
        }
        public override void OnAuthorization(System.Web.Http.Controllers.HttpActionContext ActionContext)
        {
            if (Authenticate(ActionContext) || !RequireAuthentication)
            {
                return;
            }
            else
            {
                HandleUnauthorizedRequest(ActionContext);
            }
        }
        protected override void HandleUnauthorizedRequest(HttpActionContext ActionContext)
        {
            var challengeMessage = new System.Net.Http.HttpResponseMessage(HttpStatusCode.Unauthorized);
            challengeMessage.Headers.Add("WWW-Authenticate", "Basic");
            throw new HttpResponseException(challengeMessage);
        }
        private bool Authenticate(System.Web.Http.Controllers.HttpActionContext ActionContext)
        {
            if (RequireSsl && !HttpContext.Current.Request.IsSecureConnection && !HttpContext.Current.Request.IsLocal)
            {
                //TODO: Return false to require SSL in production - disabled for testing before cert is purchased
                //return false;
            }
            if (!HttpContext.Current.Request.Headers.AllKeys.Contains("Authorization")) return false;
            string authHeader = HttpContext.Current.Request.Headers["Authorization"];
            IPrincipal principal;
            if (TryGetPrincipal(authHeader, out principal))
            {
                HttpContext.Current.User = principal;
                return true;
            }
            return false;
        }
        private bool TryGetPrincipal(string AuthHeader, out IPrincipal Principal)
        {
            var creds = ParseAuthHeader(AuthHeader);
            if (creds != null)
            {
                if (TryGetPrincipal(creds[0], creds[1], creds[2], out Principal)) return true;
            }
            Principal = null;
            return false;
        }
        private string[] ParseAuthHeader(string authHeader)
        {
            if (authHeader == null || authHeader.Length == 0 || !authHeader.StartsWith("Basic")) return null;
            string base64Credentials = authHeader.Substring(6);
            string[] credentials = Encoding.ASCII.GetString(Convert.FromBase64String(base64Credentials)).Split(new char[] { ':' });
            if (credentials.Length != 3 || string.IsNullOrEmpty(credentials[0]) || string.IsNullOrEmpty(credentials[1]) || string.IsNullOrEmpty(credentials[2])) return null;
            return credentials;
        }
        private bool TryGetPrincipal(string Username, string ApiKey, string RequestHash, out IPrincipal Principal)
        {
            Username = Username.Trim();
            ApiKey = ApiKey.Trim();
            RequestHash = RequestHash.Trim();
            //is valid username?
            IUserRepository userRepository = new UserRepository();
            UserModel user = null;
            try
            {
                user = userRepository.GetUserByUsername(Username);
            }
            catch (UserNotFoundException)
            {
                throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.Unauthorized));
            }
            //is valid apikey?
            IApiRepository apiRepository = new ApiRepository();
            ApiModel api = null;
            try
            {
                api = apiRepository.GetApi(new Guid(ApiKey));
            }
            catch (ApiNotFoundException)
            {
                throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.Unauthorized));
            }
            if (user != null)
            {
                //check if in allowed role
                bool isAllowedRole = false;
                string[] userRoles = System.Web.Security.Roles.GetRolesForUser(user.Username);
                string[] allowedRoles = Roles.Split(',');  //Roles is the inherited AuthorizeAttribute.Roles member
                foreach(string userRole in userRoles)
                {
                    foreach (string allowedRole in allowedRoles)
                    {
                        if (userRole == allowedRole)
                        {
                            isAllowedRole = true;
                        }
                    }
                }
                if (!isAllowedRole)
                {
                    throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.Unauthorized));
                }
                Principal = new GenericPrincipal(new GenericIdentity(user.Username), userRoles);                
                Thread.CurrentPrincipal = Principal;
                return true;
            }
            else
            {
                Principal = null;
                throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.Unauthorized));
            }
        }
    }

自定义授权属性控制以下控制器:

public class RequestKeyAuthorizeTestController : ApiController
{
    [RequestKeyAuthorizeAttribute(Roles="Admin,Bob,Administrator,Clue")]
    public HttpResponseMessage Get()
    {
        return Request.CreateResponse(HttpStatusCode.OK, "RequestKeyAuthorizeTestController");
    }
}

在自定义角色提供程序中,我有此方法:

public override string[] GetRolesForUser(string Username)
{
    IRoleRepository roleRepository = new RoleRepository();
    RoleModel[] roleModels = roleRepository.GetRolesForUser(Username);
    List<string> roles = new List<string>();
    foreach (RoleModel roleModel in roleModels)
    {
        roles.Add(roleModel.Name);
    }
    return roles.ToArray<string>();
}

因此,问题不在于如何实现角色提供程序,而在于如何配置应用程序以使用它。不过,我在您的配置中找不到任何问题。请确保这确实是您配置应用程序的方式。这篇文章可能会有所帮助:http://brianlegg.com/post/2011/05/09/Implementing-your-own-RoleProvider-and-MembershipProvider-in-MVC-3.aspx。如果您使用默认的 MVC 模板创建项目,请检查帐户控制器。根据该帖子,您可能需要进行一些修改才能使自定义会员资格提供程序正常工作。但这不会影响角色提供者。

此致敬意

徐明.

我不喜欢自定义授权属性,因为我必须提醒人们使用它。 我选择实现我自己的IIdentity/IPrincipal类,并在授权时将其连接起来。

调用默认RoleProvider的自定义UserIdentity

public class UserIdentity : IIdentity, IPrincipal
{
    private readonly IPrincipal _original;
    public UserIdentity(IPrincipal original){
        _original = original;
    }
    public string UserId
    {
        get
        {
            return _original.Identity.Name;
        }
    }
    public string AuthenticationType
    {
        get
        {
            return _original.Identity.AuthenticationType;
        }
    }
    public bool IsAuthenticated
    {
        get
        {
            return _original.Identity.IsAuthenticated;
        }
    }
    public string Name
    {
        get
        {
            return _original.Identity.Name;
        }
    }
    public IIdentity Identity
    {
        get
        {
            return this;
        }
    }
    public bool IsInRole(string role){
        return Roles.IsUserInRole(role);
    }
}

并将其添加到global.asax.cs:

void Application_PostAuthenticateRequest(object sender, EventArgs e)
{                                   
    if(false == HttpContext.Current.User is UserIdentity){
        HttpContext.Current.User = new UserIdentity(HttpContext.Current.User);
    }
}

stimms在他的评论中写道:"我所看到的是 IPrincipal 似乎没有正确的 RoleProvider 集"让我查看了从 AttributeIAuthenticationFilter 继承的自定义身份验证属性的实现。

using System.Web.Security;
....
protected override async Task<IPrincipal> AuthenticateAsync(string userName, string password, CancellationToken cancellationToken)
{
    if (string.IsNullOrWhiteSpace(userName) || string.IsNullOrWhiteSpace(password))
    {
        // No user with userName/password exists.
        return null;
    }
    var membershipProvider = Membership.Providers["CustomMembershipProvider"];
    if (membershipProvider != null && membershipProvider.ValidateUser(userName, password))
    {
         ClaimsIdentity identity = new GenericIdentity(userName, "Basic");
         return new RolePrincipal("CustomRoleProvider", identity);
    }
    return null;           
}

关键在于返回 RolePrincipal ,它指向自定义角色提供程序。

最初我返回new ClaimsPrincipal(identity),这给了我OP中描述的问题。

最新更新