Vado attraverso più o meno lo stesso scenario degli ultimi due settimane, quindi questo potrebbe aiutare qualcun altro nella stessa barca. Il mio scenario è un'applicazione MVC4 su una intranet aziendale con gli utenti archiviati in Active Directory. Ciò consente all'autenticazione di Windows che fornisce il Single Sign-On, quindi non è necessaria l'autenticazione basata su form. I ruoli sono memorizzati in un database Oracle. Ho 3 ruoli:
- Readonly: Tutti gli utenti hanno bisogno di essere un membro di questo per accedere all'applicazione
- dell'utente: Creare nuove resords
- Amministrazione: Modifica ed eliminare record
I ho deciso di utilizzare il API del provider di ruoli asp.net per creare il mio AccountRoleProvider. Finora ho solo bisogno di utilizzare 2 metodi in questo, GetRolesForUser e IsUserInRole:
public class AccountRoleProvider : RoleProvider // System.Web.Security.RoleProvider
{
private readonly IAccountRepository _accountRepository;
public AccountRoleProvider(IAccountRepository accountRepository)
{
this._accountRepository = accountRepository;
}
public AccountRoleProvider() : this (new AccountRepository())
{}
public override string[] GetRolesForUser(string user521)
{
var userRoles = this._accountRepository.GetRoles(user521).ToArray();
return userRoles;
}
public override bool IsUserInRole(string username, string roleName)
{
var userRoles = this.GetRolesForUser(username);
return Utils.IndexOfString(userRoles, roleName) >= 0;
}
}
ho aggiornato il web.config di usare il mio provider di ruoli:
<authentication mode="Windows" />
<roleManager enabled="true" defaultProvider="AccountRoleProvider">
<providers>
<clear/>
<add name="AccountRoleProvider"
type="MyApp.Infrastructure.AccountRoleProvider" />
</providers>
</roleManager>
Poi ho creato attributi 2 personalizzato da AuthorizeAttribute , ReadOnlyAuthorize e CustomAuthorize.
ReadonlyAuthorize:
public class ReadonlyAuthorize : AuthorizeAttribute
{
private IAccountRepository _accountRepository;
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
var user = httpContext.User;
this._accountRepository = new AccountRepository();
if (!user.Identity.IsAuthenticated)
{
return false;
}
// Get roles for current user
var roles = this._accountRepository.GetRoles(user.Identity.Name);
if (!roles.Contains("readonly"))
{
return false;
}
return base.AuthorizeCore(httpContext);
}
public override void OnAuthorization(AuthorizationContext filterContext)
{
base.OnAuthorization(filterContext);
if (filterContext.HttpContext.User.Identity.IsAuthenticated && filterContext.Result is HttpUnauthorizedResult)
{
filterContext.Result = new ViewResult { ViewName = "AccessDenied" };
}
}
}
CustomAuthorize:
public class CustomAuthorizeAttribute : AuthorizeAttribute
{
public string RedirectActionName { get; set; }
public string RedirectControllerName { get; set; }
private IAccountRepository _accountRepository;
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
var user = httpContext.User;
this._accountRepository = new AccountRepository();
var accessAllowed = false;
// Get the roles passed in with the (Roles = "...") on the attribute
var allowedRoles = this.Roles.Split(',');
if (!user.Identity.IsAuthenticated)
{
return false;
}
// Get roles for current user
var roles = this._accountRepository.GetRoles(user.Identity.Name);
foreach (var allowedRole in allowedRoles)
{
if (roles.Contains(allowedRole))
{
accessAllowed = true;
}
}
if (!accessAllowed)
{
return false;
}
return base.AuthorizeCore(httpContext);
}
public override void OnAuthorization(AuthorizationContext filterContext)
{
base.OnAuthorization(filterContext);
if (filterContext.HttpContext.User.Identity.IsAuthenticated && filterContext.Result is HttpUnauthorizedResult)
{
var values = new RouteValueDictionary(new
{
action = this.RedirectActionName == string.Empty ? "AccessDenied" : this.RedirectActionName,
controller = this.RedirectControllerName == string.Empty ? "Home" : this.RedirectControllerName
});
filterContext.Result = new RedirectToRouteResult(values);
}
}
}
La ragione per 2 diversi attributi è che uso uno per il ruolo Readonly che tutti gli utenti devono essere membri per accedere al app. Posso aggiungere questo nel metodo RegisterGlobalFilters in Global.asax che significa che è applicato automaticamente a tutti i controller:
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
filters.Add(new ReadonlyAuthorize());
}
Poi nel CustomAuthorize posso adottare un approccio più granulare e specificare i ruoli che voglio e si applicano a una Controller o una singola azione es di seguito posso limitare l'accesso al metodo Delete per gli utenti nel ruolo di amministratore:
[AccessDeniedAuthorize(RedirectActionName = "AccessDenied", RedirectControllerName = "Home", Roles = "Admin")]
public ActionResult Delete(int id = 0)
{
var batch = myDBContext.Batches.Find(id);
if (batch == null)
{
return HttpNotFound();
}
return View(batch);
}
Non ci sono ulteriori passi che devo prendere come l'aggiornamento l'oggetto Utente con i ruoli che l'utente corrente è un membro di. Ciò recupererà i ruoli per l'utente una volta invece che ogni volta nei miei attributi personalizzati e utilizzerà anche User.IsInRole. Qualcosa di simile dovrebbe essere possibile in Application_AuthenticateRequest in Gloal.asax:
var roles = "get roles for this user from respository";
if (Context.User != null)
Context.User = new GenericPrincipal(Context.User.Identity, roles);