使用LINQ to SQL的ASP.NET MVC:如何实现对存储库中实体的授权?
-
07-07-2019 - |
题
考虑到域驱动设计,您将如何在存储库中实现用户授权?具体来说,您如何限制用户提供的登录信息?
假设我们有一个存储产品的电子商务商城,其中只有一些产品由任何给定的商店经理维护。在这种情况下,任何给定的登录都不应该看到所有产品。
问题:
- 您是否会选择Repo中的所有产品,然后使用过滤器来限制返回的产品?
像GetProducts(" keyword:boat")。restrictBy(" myusername")?
- 您是否会从存储库中的controllercontext读取登录并被动地过滤结果?
- 您如何存储用户角色与其可以访问的实体之间的关系?您是否只需将实体密钥和角色存储在多对多表中,每个商店经理可以访问的每个产品都有一条记录? 醇>
代码示例或代码示例链接非常棒。谢谢。
解决方案
我采取的策略是使用控制器操作上的属性来检查当前用户与被请求实体之间的关系,然后根据查找结果允许或禁止操作。我有几个不同的属性,具体取决于它是通过连接表还是有直接关系。它使用反射,在我的情况下是数据上下文,但在你的存储库中,以获取并检查值是否匹配。我将包含下面的代码(我已经做了一些通用化的努力,所以它可能无法编译)。请注意,您可以将其扩展为包含一些权限概念(在连接表中)。
直接关系属性的代码。它验证当前用户是记录的所有者(路由参数中指定的“id”属性与用户表中当前用户的id匹配)。
[AttributeUsage( AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false )]
public class RoleOrOwnerAuthorizationAttribute : AuthorizationAttribute
{
private IDataContextFactory ContextFactory { get; set; }
private string routeParameter = "id";
/// <summary>
/// The name of the routing parameter to use to identify the owner of the data (participant id) in question. Default is "id".
/// </summary>
public string RouteParameter
{
get { return this.routeParameter; }
set { this.routeParameter = value; }
}
public RoleOrOwnerAuthorizationAttribute()
: this( null )
{
}
// this is for unit testing support
public RoleOrOwnerAuthorizationAttribute( IDataContextFactory factory )
{
this.ContextFactory = factory ?? DefaultFactory();
}
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" ) || IsOwner( filterContext ))
{
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 };
}
}
private bool IsOwner( AuthorizationContext filterContext )
{
using (var dc = this.ContextFactory.GetDataContextWrapper())
{
int id = -1;
if (filterContext.RouteData.Values.ContainsKey( this.RouteParameter ))
{
id = Convert.ToInt32( filterContext.RouteData.Values[this.RouteParameter] );
}
string userName = filterContext.HttpContext.User.Identity.Name;
return dc.Table<UserTable>().Where( p => p.UserName == userName && p.ParticipantID == id ).Any();
}
}
}
这是关联属性的代码,即,在路由参数中的id与用户表中的用户id之间的连接表中存在关联。请注意, VS2008示例中的System.Linq.Dynamic代码存在依赖关系。
[AttributeUsage( AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false )]
public class RoleOrOwnerAssociatedAuthorizationAttribute : MasterEventAuthorizationAttribute
{
private IDataContextFactory ContextFactory { get; set; }
public RoleOrOwnerAssociatedAuthorizationAttribute()
: this( null )
{
}
// this supports unit testing
public RoleOrOwnerAssociatedAuthorizationAttribute( IDataContextFactory factory )
{
this.ContextFactory = factory ?? new DefaultDataContextFactory();
}
/// <summary>
/// The table in which to find the current user by name.
/// </summary>
public string UserTable { get; set; }
/// <summary>
/// The name of the property in the UserTable that holds the user's name to match against
/// the current context's user name.
/// </summary>
public string UserNameProperty { get; set; }
/// <summary>
/// The property to select from the UserTable to match against the UserEntityProperty on the JoinTable
/// to determine membership.
/// </summary>
public string UserSelectionProperty { get; set; }
/// <summary>
/// The join table that links users and the entity table. An entry in this table indicates
/// an association between the user and the entity.
/// </summary>
public string JoinTable { get; set; }
/// <summary>
/// The property on the JoinTable used to hold the entity's key.
/// </summary>
public string EntityProperty { get; set; }
/// <summary>
/// The property on the JoinTable used to hold the user's key.
/// </summary>
public string UserEntityProperty { get; set; }
/// <summary>
/// The name of the route data parameter which holds the group's key being requested.
/// </summary>
public string RouteParameter { get; set; }
public override void OnAuthorization( AuthorizationContext filterContext )
{
using (var dc = this.ContextFactory.GetDataContextWrapper())
{
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" )
|| IsRelated( filterContext, this.GetTable( dc, this.JoinTable ), this.GetTable( dc, this.UserTable ) ))
{
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 bool IsRelated( AuthorizationContext filterContext, IQueryable joinTable, IQueryable userTable )
{
bool result = false;
try
{
int entityIdentifier = Convert.ToInt32( filterContext.RouteData.Values[this.RouteParameter] );
int userIdentifier = this.GetUserIdentifer( filterContext, userTable );
result = joinTable.Where( this.EntityProperty + "=@0 and " + this.UserEntityProperty + "=@1",
entityIdentifier,
userIdentifier )
.Count() > 0;
}
catch (NullReferenceException) { }
catch (ArgumentNullException) { }
return result;
}
private int GetUserIdentifer( AuthorizationContext filterContext, IQueryable userTable )
{
string userName = filterContext.HttpContext.User.Identity.Name;
var query = userTable.Where( this.UserNameProperty + "=@0", userName )
.Select( this.UserSelectionProperty );
int userIdentifer = -1;
foreach (var value in query)
{
userIdentifer = Convert.ToInt32( value );
break;
}
return userIdentifer;
}
private IQueryable GetTable( IDataContextWrapper dc, string name )
{
IQueryable result = null;
if (!string.IsNullOrEmpty( name ))
{
DataContext context = dc.GetContext<DefaultDataContext>();
PropertyInfo info = context.GetType().GetProperty( name );
if (info != null)
{
result = info.GetValue( context, null ) as IQueryable;
}
}
return result;
}
}
其他提示
我问了一个非常类似的问题 S#arp架构新闻组,其中 Tom Cabanski 提出了一个AOP框架,例如 PostSharp 。这看起来像是一个可行的解决方案来满足我的需求,因此我正计划对其进行更深入的研究。不幸的是,这不是您问题的完整答案,因为我没有代码示例可供分享。
不隶属于 StackOverflow