Comment créer une adhésion personnalisée pour ASP.NET MVC 2 basée sur le fournisseur d'adhésion ASP.NET ?
Réponses
Trop de publicités?J'ai créé un nouveau projet contenant un fournisseur de services d'adhésion personnalisé et j'ai surchargé l'option ValidateUser
de la méthode MembershipProvider
classe abstraite :
public class MyMembershipProvider : MembershipProvider
{
public override bool ValidateUser(string username, string password)
{
// this is where you should validate your user credentials against your database.
// I've made an extra class so i can send more parameters
// (in this case it's the CurrentTerritoryID parameter which I used as
// one of the MyMembershipProvider class properties).
var oUserProvider = new MyUserProvider();
return oUserProvider.ValidateUser(username,password,CurrentTerritoryID);
}
}
J'ai ensuite connecté ce fournisseur à mon projet ASP.NET MVC 2 en ajoutant une référence et en le pointant depuis mon web.config :
<membership defaultProvider="MyMembershipProvider">
<providers>
<clear />
<add name="MyMembershipProvider"
applicationName="MyApp"
Description="My Membership Provider"
passwordFormat="Clear"
connectionStringName="MyMembershipConnection"
type="MyApp.MyMembershipProvider" />
</providers>
</membership>
J'ai besoin de créer une classe personnalisée qui hérite de la classe RoleProvider
et remplace la classe abstraite GetRolesForUser
méthode. Le système d'autorisation ASP.NET MVC utilise cette méthode pour savoir quels rôles sont attribués à l'utilisateur connecté et s'assure que l'utilisateur est autorisé à accéder à l'action du contrôleur.
Voici les mesures que nous devons prendre :
1) Créez une classe personnalisée qui hérite de la classe abstraite RoleProvider et surcharge la méthode GetRolesForUser :
public override string[] GetRolesForUser(string username)
{
SpHelper db = new SpHelper();
DataTable roleNames = null;
try
{
// get roles for this user from DB...
roleNames = db.ExecuteDataset(ConnectionManager.ConStr,
"sp_GetUserRoles",
new MySqlParameter("_userName", username)).Tables[0];
}
catch (Exception ex)
{
throw ex;
}
string[] roles = new string[roleNames.Rows.Count];
int counter = 0;
foreach (DataRow row in roleNames.Rows)
{
roles[counter] = row["Role_Name"].ToString();
counter++;
}
return roles;
}
2) Connectez le fournisseur de rôles avec l'application ASP.NET MVC 2 via notre web.config :
<system.web>
...
<roleManager enabled="true" defaultProvider="MyRoleProvider">
<providers>
<clear />
<add name="MyRoleProvider"
applicationName="MyApp"
type="MyApp.MyRoleProvider"
connectionStringName="MyMembershipConnection" />
</providers>
</roleManager>
...
</system.web>
3) Définissez le paramètre Authorize(Roles="xxx,yyy") au-dessus du contrôleur/de l'action souhaité :
[Authorization(Roles = "Customer Manager,Content Editor")]
public class MyController : Controller
{
......
}
Voilà, c'est fait ! Maintenant ça marche !
4) Facultatif : définir un Authorize
afin de pouvoir rediriger un rôle indésirable vers une page AccessDenied :
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
public class MyAuthorizationAttribute : AuthorizeAttribute
{
/// <summary>
/// The name of the master page or view to use when rendering the view on authorization failure. Default
/// is null, indicating to use the master page of the specified view.
/// </summary>
public virtual string MasterName { get; set; }
/// <summary>
/// The name of the view to render on authorization failure. Default is "Error".
/// </summary>
public virtual string ViewName { get; set; }
public MyAuthorizationAttribute ()
: base()
{
this.ViewName = "Error";
}
protected void CacheValidateHandler(HttpContext context, object data, ref HttpValidationStatus validationStatus)
{
validationStatus = OnCacheAuthorization(new HttpContextWrapper(context));
}
public override void OnAuthorization(AuthorizationContext filterContext)
{
if (filterContext == null)
{
throw new ArgumentNullException("filterContext");
}
if (AuthorizeCore(filterContext.HttpContext))
{
SetCachePolicy(filterContext);
}
else if (!filterContext.HttpContext.User.Identity.IsAuthenticated)
{
// auth failed, redirect to login page
filterContext.Result = new HttpUnauthorizedResult();
}
else if (filterContext.HttpContext.User.IsInRole("SuperUser"))
{
// is authenticated and is in the SuperUser role
SetCachePolicy(filterContext);
}
else
{
ViewDataDictionary viewData = new ViewDataDictionary();
viewData.Add("Message", "You do not have sufficient privileges for this operation.");
filterContext.Result = new ViewResult { MasterName = this.MasterName, ViewName = this.ViewName, ViewData = viewData };
}
}
protected void SetCachePolicy(AuthorizationContext filterContext)
{
// ** IMPORTANT **
// Since we're performing authorization at the action level, the authorization code runs
// after the output caching module. In the worst case this could allow an authorized user
// to cause the page to be cached, then an unauthorized user would later be served the
// cached page. We work around this by telling proxies not to cache the sensitive page,
// then we hook our custom authorization code into the caching mechanism so that we have
// the final say on whether a page should be served from the cache.
HttpCachePolicyBase cachePolicy = filterContext.HttpContext.Response.Cache;
cachePolicy.SetProxyMaxAge(new TimeSpan(0));
cachePolicy.AddValidationCallback(CacheValidateHandler, null /* data */);
}
}
Maintenant nous pouvons utiliser notre propre attribut pour rediriger nos utilisateurs vers la vue refusée :
[MyAuthorization(Roles = "Portal Manager,Content Editor", ViewName = "AccessDenied")]
public class DropboxController : Controller
{
.......
}
C'est ça ! Super duper !
Voici quelques-uns des liens que j'ai utilisés pour obtenir toutes ces informations :
Fournisseur de rôle personnalisé : http://davidhayden.com/blog/dave/archive/2007/10/17/CreateCustomRoleProviderASPNETRolePermissionsSecurity.aspx
J'espère que ces informations vous aideront !
Il est également possible d'utiliser cette méthode avec une quantité de code beaucoup plus petite, je ne suis pas tout à fait sûr que cette méthode soit aussi sûre mais elle fonctionne très bien avec n'importe quelle base de données que vous utilisez.
dans le fichier global.asax
protected void Application_AuthenticateRequest(object sender, EventArgs e)
{
if (HttpContext.Current.User != null)
{
if (HttpContext.Current.User.Identity.IsAuthenticated)
{
if (HttpContext.Current.User.Identity is FormsIdentity)
{
FormsIdentity id =
(FormsIdentity)HttpContext.Current.User.Identity;
FormsAuthenticationTicket ticket = id.Ticket;
// Get the stored user-data, in this case, our roles
string userData = ticket.UserData;
string[] roles = userData.Split(',');
HttpContext.Current.User = new GenericPrincipal(id, roles);
}
}
}
}
Ce que cela fait, c'est qu'il lit les rôles à partir de l'authCookie qui a été fait à partir de FormsAuthenticationTicket.
et la logique d'ouverture de session ressemble à ceci
public class dbService
{
private databaseDataContext db = new databaseDataContext();
public IQueryable<vwPostsInfo> AllPostsAndDetails()
{
return db.vwPostsInfos;
}
public IQueryable<role> GetUserRoles(int userID)
{
return (from r in db.roles
join ur in db.UsersRoles on r.rolesID equals ur.rolesID
where ur.userID == userID
select r);
}
public IEnumerable<user> GetUserId(string userName)
{
return db.users.Where(u => u.username.ToLower() == userName.ToLower());
}
public bool logOn(string username, string password)
{
try
{
var userID = GetUserId(username);
var rolesIQueryable = GetUserRoles(Convert.ToInt32(userID.Select(x => x.userID).Single()));
string roles = "";
foreach (var role in rolesIQueryable)
{
roles += role.rolesName + ",";
}
roles.Substring(0, roles.Length - 2);
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(
1, // Ticket version
username, // Username associated with ticket
DateTime.Now, // Date/time issued
DateTime.Now.AddMinutes(30), // Date/time to expire
true, // "true" for a persistent user cookie
roles, // User-data, in this case the roles
FormsAuthentication.FormsCookiePath);// Path cookie valid for
// Encrypt the cookie using the machine key for secure transport
string hash = FormsAuthentication.Encrypt(ticket);
HttpCookie cookie = new HttpCookie(
FormsAuthentication.FormsCookieName, // Name of auth cookie
hash); // Hashed ticket
// Set the cookie's expiration time to the tickets expiration time
if (ticket.IsPersistent) cookie.Expires = ticket.Expiration;
// Add the cookie to the list for outgoing response
HttpContext.Current.Response.Cookies.Add(cookie);
return true;
}
catch
{
return (false);
}
}
}
Je stocke les rôles dans ma base de données avec deux tables : table : Role qui a les colonnes : roleID et roleName et la table : UsersRoles qui a les colonnes : userID et roleID, cela permet d'avoir plusieurs rôles pour plusieurs utilisateurs et il est facile de faire votre propre logique pour ajouter/supprimer les rôles des utilisateurs et ainsi de suite. Cela vous permet d'utiliser [Authorize(Roles="Super Admin")] par exemple. J'espère que cela vous aidera.
edit : j'ai oublié de faire la vérification du mot de passe mais il suffit d'ajouter un if dans la méthode logOn qui vérifie si le nom d'utilisateur et le mot de passe fournis sont corrects et si non il renvoie false