问题
I have created a custom role provider for my MVC4 application where I have been successfully able to override CreateRole, GetAllRoles and RoleExists methods and link them to my existing database as follows:
namespace Project.Providers
{
public class MyProvider : System.Web.Security.SqlRoleProvider
{
private MyContext dbcontext = new MyContext(System.Configuration.ConfigurationManager.ConnectionStrings["MyContext"].ConnectionString);
private Repository<MyUser> userRepository;
private Repository<Role> roleRepository;
public MyProvider()
{
this.userRepository = new Repository<MyUser>(dbcontext);
this.roleRepository = new Repository<Role>(dbcontext);
}
public override string[] GetAllRoles()
{
IEnumerable<Role> dbRoles = roleRepository.GetAll();
int dbRolesCount = roleRepository.GetAll().Count();
string[] roles = new string[dbRolesCount];
int i = 0;
foreach(var role in dbRoles)
{
roles[i] = role.Name;
i++;
}
return roles;
}
public override bool RoleExists(string roleName)
{
string[] roles = { "Admin", "User", "Business" };
if(roles.Contains(roleName))
return true;
else
return false;
}
public override void CreateRole(string roleName)
{
Role newRole = new Role();
newRole.Name = roleName;
roleRepository.Add(newRole);
roleRepository.SaveChanges();
}
public override bool IsUserInRole(string userName, string roleName)
{
MyUser user = userRepository.Get(u => u.Username == userName).FirstOrDefault();
Role role = roleRepository.Get(r => r.Name == roleName).FirstOrDefault();
if (user.RoleID == role.RoleID)
return true;
else
return false;
}
}
}
I have been unable to find a way to override the
User.IsInRole(string roleName)
What else must I do so that When I use:
[Authorize(Roles = "Admin")]
It will be based on the role provider that I have set up and not the asp default.
My user class is now as follows:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.ComponentModel.DataAnnotations;
using System.Collections;
using System.Security.Principal;
namespace Project.Data
{
public class MyUser : IPrincipal
{
[Key]
public int UserID { get; set; }
[StringLength(128)]
public string Username { get; set; }
.....other properties
public IIdentity Identity { get; set; }
public bool IsInRole(string role)
{
if (this.Role.Name == role)
{
return true;
}
return false;
}
public IIdentity Identity
{
get { throw new NotImplementedException(); }
}
}
}
My stack trace seems to be following over at:
System.Web.Security.RolePrincipal.IsInRole(String role)
So I have tried to implement a custom RolePrincipal in the same manner I set the custom Provider any ideas how I can do this? Not sure what constructor params it takes. Here is my attempt:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Configuration.Provider;
using Project.Data;
using System.Web.Security;
using System.Security.Principal.IIdentity;
namespace Project.Principal
{
public class MyPrincipal : System.Web.Security.RolePrincipal
{
private MyContext dbcontext = new MyContext(System.Configuration.ConfigurationManager.ConnectionStrings["MyContext"].ConnectionString);
private Repository<MyUser> userRepository;
private Repository<Role> roleRepository;
public MyPrincipal()
{
this.userRepository = new Repository<MyUser>(dbcontext);
this.roleRepository = new Repository<Role>(dbcontext);
}
public override bool IsInRole(string role)
{
//code to be added
return true;
}
}
}
回答1:
You just need to override method GetRolesForUser in your custom role provider, instead of the more logical IsUserInRole, because that is what is called by the default implementation that does some unwanted caching.
回答2:
You override IsInRole in your IPrincipal class, mine in EF looks like this:
public class MyUser : IPrincipal {
//Properties
...
public bool IsInRole(string role) {
if (Roles.Any(m=>m.NameKey.ToLower().Equals(role.ToLower()))) {
return true;
}
return false;
}
}
Then once you add the appropriate sections to your webconfig for both RoleProvider and MembershipProvider you should be good for Authorize attribute.
UPDATE in response to your comments
web Config should look like:
...
<authentication mode="Forms">
<forms loginUrl="~/Login" timeout="2880"></forms>
</authentication>
<authorization>
</authorization>
..
<membership defaultProvider="MyMembershipProvider">
<providers>
<add name="MyMembershipProvider" type="MyApp.Infrastructure.MyMembershipProvider" connectionStringName="connectionstring" enablePasswordRetrieval="false" enablePasswordReset="true" requiresQuestionAndAnswer="false" requiresUniqueEmail="true" />
</providers>
</membership>
<roleManager defaultProvider="MyRoleProvider" enabled="true" cacheRolesInCookie="true">
<providers>
<clear />
<add name="MyRoleProvider" type="MyApp.Infrastructure.MyRoleProvider" />
</providers>
</roleManager>
...
In the provider, is the User your IPrincipal
?
public MyUser User { get; private set; }
User should have and IIdentity
in MyUser.cs:
...
public virtual ICollection<Role> Roles { get; set; }
public IIdentity Identity { get; set; }
I don't have much additional advice to help troubleshoot from your comments.
UPDATE
Some examples I have been through and found helpful when setting mine up: http://www.brianlegg.com/post/2011/05/09/Implementing-your-own-RoleProvider-and-MembershipProvider-in-MVC-3.aspx
http://www.mattwrock.com/post/2009/10/14/Implementing-custom-MembershipProvider-and-Role-Provider-for-Authinticating-ASPNET-MVC-Applications.aspx
http://blogs.msdn.com/b/rickandy/archive/2012/03/23/securing-your-asp-net-mvc-4-app-and-the-new-allowanonymous-attribute.aspx?Redirected=true
I read many other articles and SO posts on my first run through, but these were things I bothered to bookmark. I took a roles/rights approach to authorization, which is why one of them is geared that way.
回答3:
To fix this, you need to do 4 updates to your application.
1. Create a class that extends RoleProvider.
namespace MyApp
{
public class MyRoleProvider : RoleProvider
{
public override string ApplicationName
{
get
{
throw new NotImplementedException();
}
set
{
throw new NotImplementedException();
}
}
public override void AddUsersToRoles(string[] usernames, string[] roleNames)
{
throw new NotImplementedException();
}
public override void CreateRole(string roleName)
{
throw new NotImplementedException();
}
public override bool DeleteRole(string roleName, bool throwOnPopulatedRole)
{
throw new NotImplementedException();
}
public override string[] FindUsersInRole(string roleName, string usernameToMatch)
{
throw new NotImplementedException();
}
public override string[] GetAllRoles()
{
throw new NotImplementedException();
}
public override string[] GetRolesForUser(string username)
{
using (ApplicationDbContext db = new ApplicationDbContext())
{
// get user roles here using user name.
}
}
public override string[] GetUsersInRole(string roleName)
{
throw new NotImplementedException();
}
public override bool IsUserInRole(string username, string roleName)
{
return GetRolesForUser(username).Contains(roleName);
}
public override void RemoveUsersFromRoles(string[] usernames, string[] roleNames)
{
throw new NotImplementedException();
}
public override bool RoleExists(string roleName)
{
throw new NotImplementedException();
}
}
}
2. Create a custom filter that extends AuthorizeAttribute and overwrite its methods.
public class MyAuthFilter : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
base.OnAuthorization(filterContext);
}
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
var routeValues = new RouteValueDictionary(new
{
controller = "Account",
action = "Login",
});
filterContext.Result = new RedirectToRouteResult(routeValues);
base.HandleUnauthorizedRequest(filterContext);
}
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
string[] roles = Roles.Split(',');
string userName = HttpContext.Current.User.Identity.Name;
MyRoleProvider myProvider = new MyRoleProvider();
foreach (string role in roles)
{
bool success = myProvider.IsUserInRole(userName, role);
if (success == true)
{
return true;
}
}
return false;
}
3. Configure your custom role provider in your web.config.
<system.web>
<roleManager defaultProvider="MyRoleProvider" enabled="true" cacheRolesInCookie="true">
<providers>
<clear />
<add name="MyRoleProvider" type="MyApp.MyRoleProvider" />
</providers>
</roleManager>
</system.web>
Note: The type here uses the fully qualified namespace and your class name = MyApp.MyRoleProvider. Yours can be different
4. Use your custom filter instead of the default Authorize attribute for your controllers and actions. E.g
[MyAuthFilter]
public class HomeController : Controller
{
public ActionResult Index()
{
ViewBag.Title = "Home Page";
return View();
}
}
来源:https://stackoverflow.com/questions/18413036/override-the-user-isinrole-and-authorizeroles-admin-for-mvc4-application