假设您有一个带有GetResource(int resourceId)操作的.net web api。此操作(具有指定的id)仅应授权与该id相关联的用户(该资源可以是例如用户编写的博客帖子)。
这可以通过多种方式解决,但下面给出一个例子。
public Resource GetResource(int id)
{
string name = Thread.CurrentPrincipal.Identity.Name;
var user = userRepository.SingleOrDefault(x => x.UserName == name);
var resource = resourceRepository.Find(id);
if (resource.UserId != user.UserId)
{
throw new HttpResponseException(HttpStatusCode.Unauthorized);
}
return resource;
}
用户已通过某种机制进行身份验证。
现在,假设我还希望授权用户(例如,管理员类型)使用端点(具有相同的ID)。此用户与资源没有任何直接关系,但由于其类型(或角色)而具有授权。这可以通过检查用户是否为管理员类型并返回资源来解决。
有没有办法集中这种方式,以便我不必在每个动作中编写授权代码?
修改 基于答案,我想我必须澄清我的问题。
我真正追求的是一些机制,可以使基于资源的授权成为可能,但同时允许一些用户也使用相同的端点和相同的资源。以下操作将解决此特定端点和此特定角色(管理员)的问题。
public Resource GetResource(int id)
{
string name = Thread.CurrentPrincipal.Identity.Name;
var user = userRepository.SingleOrDefault(x => x.UserName == name);
var resource = resourceRepository.Find(id);
if (!user.Roles.Any(x => x.RoleName == "Admin" || resource.UserId != user.UserId)
{
throw new HttpResponseException(HttpStatusCode.Unauthorized);
}
return resource;
}
我所追求的是解决此问题的一些通用方法,这样我就不必编写具有相同目的的两个不同端点,也不必在每个端点中编写特定于资源的代码。
答案 0 :(得分:4)
对于基于资源的授权,我建议使用claim based identity并将用户ID嵌入为声明。编写扩展方法以从身份中读取声明。所以示例代码如下所示:
public Resource GetResource(int id)
{
var resource = resourceRepository.Find(id);
if (resource.UserId != User.Identity.GetUserId())
{
throw new HttpResponseException(HttpStatusCode.Unauthorized);
}
return resource;
}
如果您想进一步简化代码,可以编写一个知道用户数据和资源存储库的UserRepository来集中代码。代码如下:
public Resource GetResource(int id)
{
return User.Identity.GetUserRepository().FindResource(id);
}
对于基于角色的授权,AuthorizeAttribute将是处理它的最佳位置,您最好使用单独的操作或控制器。
[Authorize(Roles = "admin")]
public Resource GetResourceByAdmin(int id)
{
return resourceRepository.Find(id);
}
[编辑] 如果OP确实想要使用一个动作来处理不同类型的用户,我个人更喜欢使用用户存储库工厂。行动代码将是:
public Resource GetResource(int id)
{
return User.GetUserRepository().FindResource(id);
}
扩展方法为:
public static IUserRepository GetUserRepository(this IPrincipal principal)
{
var resourceRepository = new ResourceRepository();
bool isAdmin = principal.IsInRole("Admin");
if (isAdmin)
{
return new AdminRespository(resourceRepository);
}
else
{
return new UserRepository(principal.Identity, resourceRepository);
}
}
我不想使用AuthorizeAttribute来执行每个资源认证的原因是不同的资源可能有不同的代码来检查所有权,很难将代码集中在一个属性中,并且它需要额外的数据库操作,这不是真正的必要。 另一个问题是AuthroizeAttribute在参数绑定之前发生,因此您需要确保操作的参数来自路径数据。否则,例如,从帖子正文中,您将无法获得参数值。
答案 1 :(得分:2)
我会考虑实现一个自定义System.Web.Http.AuthorizeAttribute,您可以将其应用于需要此特定授权规则的操作。在自定义授权中,如果用户是Admins组的成员,或者他们是资源的作者,则可以允许访问。
编辑:
根据OP的编辑,允许我扩展我所说的内容。如果覆盖AuthorizeAttribute,则可以添加如下逻辑:
public class AuthorizeAdminsAndAuthors : System.Web.Http.AuthorizeAttribute
{
protected override bool IsAuthorized(HttpActionContext actionContext)
{
return currentUser.IsInRole("Admins") || IsCurrentUserAuthorOfPost(actionContext);
}
private bool IsCurrentUserAuthorOfPost(HttpActionContext actionContext)
{
// Get id for resource from actionContext
// look up if user is author of this post
return true;
}
这是伪代码,但应传达这个想法。如果您有一个AuthorizeAttribute根据您的要求确定授权:当前请求来自帖子的作者或管理员,那么您可以将AuthorizeAdminsAndAuthors属性应用于您需要此级别授权的任何资源。所以你的资源看起来像:
[AuthorizeAdminsAndAuthors]
public Resource GetResource(int id)
{
var resource = resourceRepository.Find(id);
return resource;
}
答案 2 :(得分:2)
您需要外部授权。您希望将整个授权逻辑移动到单独的层或服务中。
有几个框架 - 用不同的语言 - 可以让你这样做。在.NET世界中,如其他答案所示,您具有基于声明的授权。微软在here上有一篇很棒的文章。
我建议您采用标准化方法,即XACML,可扩展访问控制标记语言。 XACML为您提供了三件事:
如果我们重新审视你的例子,你会有以下几点:
public Resource GetResource(int id)
{
var resource = resourceRepository.Find(id);
if (isAuthorized(User.Identity,resource))
{
throw new HttpResponseException(HttpStatusCode.Unauthorized);
}
return resource;
}
public bool isAuthorized(User u, Resource r){
// Create XACML request here
// Call out to PDP
// return boolean decision
}
您的PDP将包含以下规则:
XACML的好处是您可以独立于代码增加授权规则/逻辑。这意味着无论何时逻辑发生变化,您都不必触摸应用程序代码。 XACML还可以满足更多参数/属性 - 例如设备ID,IP,一天中的时间......最后,XACML并不特定于.NET。它适用于许多不同的框架。
答案 3 :(得分:1)
另请参阅基于声明的授权方法 - 包括从.NET 4.5开始。
http://leastprivilege.com/2012/10/26/using-claims-based-authorization-in-mvc-and-web-api/
答案 4 :(得分:0)
非常简单
<强>要求强>
public class PrivateProfileRequirement : IAuthorizationRequirement
{
public string ClaimType { get; }
public PrivateProfileRequirement(string claimType)
{
ClaimType = claimType;
}
}
public class PrivateProfileHandler : AuthorizationHandler<PrivateProfileRequirement>
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, PrivateProfileRequirement requirement)
{
if (context.User != null)
{
if (context.User.Claims.Any(c => string.Equals(c.Type, requirement.ClaimType, StringComparison.OrdinalIgnoreCase)))
{
if (context.User.Identities.Any(i => string.Equals(i.GetId(), context.Resource)))
{
context.Succeed(requirement);
}
}
}
return Task.CompletedTask;
}
}
<强> Startup.cs 强>
services.AddAuthorization(options =>
{
options.AddPolicy("PrivateProfileRequirement",
policy => policy
.RequireAuthenticatedUser()
.RequireRole(Role.Profile.ToRole())
.AddRequirements(new PrivateProfileRequirement(ClaimTypes.NameIdentifier)));
});
<强>控制器强>
public class ProfileController : Controller
{
private readonly IAuthorizationService _authorizationService;
public ProfileController(IAuthorizationService authorizationService)
{
_authorizationService = authorizationService;
}
}
<强>动作强>
public async Task<IActionResult> OnGetAsync(int id)
{
var profile = _profileRepository.Find(id);
if (profile == null)
{
return new NotFoundResult();
}
var authorizationResult = await _authorizationService
.AuthorizeAsync(User, profile.Id, "PrivateProfileRequirement");
if (authorizationResult.Succeeded)
{
return View();
}
return new ChallengeResult();
}