我在我的控制器中执行模型验证,但需要在服务/业务级别进行第二次业务验证。这通常与用户权限有关:当前用户是否可以访问他试图获取或发布的客户/订单信息?
我的第一个(也是工作)方法是传递整个User
实例或其Id
(通过调用User.Identity.GetUserId()
),这将是最充分的 - 如果不是全部 - 时间。所以我会有这样的事情:
public IHttpActionResult Get(int id)
{
try
{
var customer = customerService.GetById(id, userId);
return Ok(customer);
}
catch (BusinessLogicException e)
{
return CreateErrorResponse(e);
}
}
但我并不喜欢这样一个事实,即通过这种方法,我必须在几乎每次调用服务层时都包含一个额外的参数。如果我正在调用GetById()
方法,我希望通过提供ID来获取内容,而不是ID 和用户ID。
一个简单的解决方法就是沿着这些方向发展,这也很有效:
public IHttpActionResult Get(int id)
{
customerService.SetCurrentUser(User.Identity.GetUserId());
try
{
var customer = customerService.GetById(id);
return Ok(customer);
}
catch (BusinessLogicException e)
{
return CreateErrorResponse(e);
}
}
但是,我不需要单独调用来设置当前用户,而是希望每次调用服务时都自动完成此操作。我该怎么办?
这是我的服务的样子:
public class CustomerService : EntityService<Customer>, ICustomerService
{
public string UserId;
IContext context;
public CustomerService(IContext context) : base(context)
{
this.context = context;
this.dbSet = context.Set<Customer>();
}
public void SetCurrentUser(string userId)
{
UserId = userId;
}
public DTO.Customer GetById(int id)
{
if (!IsAccessibleByUser(id))
{
throw new BusinessLogicException(ErrorCode.UserError, "UserId: " + UserId);
}
return dbSet.FirstOrDefault(x => x.Id == id).ToDto<Customer, DTO.Customer>();
}
public bool IsAccessibleByUser(int id)
{
return context.UsersAPI.Any(a => a.AspNetUsersID == UserId);
}
}
答案 0 :(得分:1)
我宁愿在自定义授权过滤器中执行此授权逻辑。如果用户未经过身份验证或授权,则无需访问控制器操作代码。
例如,你可以这样:
public class MyAuthorizeAttribute : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
var authorized = base.AuthorizeCore(httpContext);
if (!authorized)
{
return false;
}
var rd = httpContext.Request.RequestContext.RouteData;
// Get the id of the requested resource from the route data
string resourceId = rd.Values["id"] as string;
if (string.IsNullOrEmpty(resourceId))
{
// No id of resource was specified => we do not allow access
return false;
}
string userId = httpContext.User.Identity.GetUserId();
return IsAccessibleByUser(resourceId, userId);
}
private bool IsAccessibleByUser(string resourceId, string userId)
{
// You know what to do here => fetch the requested resource
// from your data store and verify that the current user is
// authorized to access this resource
}
}
然后您可以使用自定义属性修饰需要此类授权的控制器或操作:
[MyAuthorize]
public IHttpActionResult Get(int id)
{
try
{
// At this stage you know that the user is authorized to
// access the requested resource
var customer = customerService.GetById(id);
return Ok(customer);
}
catch (BusinessLogicException e)
{
return CreateErrorResponse(e);
}
}
当然,通过使用自定义过滤器提供程序可以进一步改进此自定义属性,该提供程序允许将数据上下文注入其中,以便您可以执行正确的调用。那么你只能有一个标记属性,过滤器提供者将使用它来决定是否应该执行授权逻辑。
答案 1 :(得分:0)
您的服务在执行逻辑时是否需要考虑用户ID?如果是这样,那么将其作为输入的一部分是合理的。 或者它只是更多的授权,服务应拒绝请求而不处理取决于用户是谁(但一旦授权,用户是谁并不重要)? 您可以插入additional data into the header of the WCF request,然后在传入的请求中检查它。但是a)这是一种痛苦,b)客户端应该提供该数据的服务接口并不明显。
因此我在输入中输入了一个用户ID。我不会把它作为服务的财产。它实际上是输入的属性,而不是处理输入的类。
如果您不希望您的服务承担授权请求的额外责任,那么interceptor仍然是一个好主意。这样,您就可以在服务或服务方法上添加属性,并将授权保留在拦截器中。如果用户无法调用服务或方法,则它会在到达服务类之前拒绝该调用。
答案 2 :(得分:0)
迟到的答案,但我理解你的关注,你希望在你的代码中实现的最佳实践并尽可能地保持干净,我和你很久以前的问题完全一样,我觉得这不对,应该有更好的方法来做到这一点,在我的情况下,我在我的所有层次上使用:
System.Threading.Thread.CurrentPrincipal.Identity
这个答案在SO上描述了它:https://stackoverflow.com/a/27636802/20126
虽然这是我一直使用的方式,但值得看看别人做了什么:
Accessing HttpContext and User Identity from data layer
How to get User ID inside a separate assembly
Retrieving the current logged in user's Id in a class library outside of an ASP.NET Webforms App using forms authentication
Get the ID of the current user ASP.NET Membership
答案 3 :(得分:0)
您可以直接从以下位置获取用户主体
HttpContext.Current.User
在您的服务中。在检查令牌验证之后或在调用服务之前,需要在授权层中设置用户主体。
但是,我觉得这会使您的服务更受限制于使用某些特定的身份验证过程,除非您未设置用户主体,否则您将无法在其他一些应用程序中使用此服务功能。