我正在开发一个具有多层安全性的网站。最基本的安全级别由自定义Authorize属性处理,该属性验证用户是否具有正确的角色等。
我们使用Authorize方法修饰控制器中的方法,这可以防止未经授权的用户访问这些调用。
但是,我们还有一些细粒度的权利要求。例如,来自公司A的用户不能修改公司B中的任何用户。因此,虽然公司A和公司B的管理员都有权从API调用“UpdateUser”,但公司A不应该更新公司B的用户之一。
目前,控制器内部正在处理跨公司访问的限制。我遇到的问题是我们引入的每个新方法都包括代码的复制和粘贴或非常类似的变体。
那就是说,我不确定它是否属于该服务(让我们称之为UserService)。
所以我的选择是:
在控制器内部进行检查,以查看请求更新的用户是否与更新用户属于同一公司
在服务内部执行相同的检查。
社区的任何建议。
第二种方法的优点是代码不需要在控制器中重复。当多个控制器调用相同的服务方法时,好处很明显 - 基础检查是一致的,代码不会重复。
第二种方法的缺点是现在我的服务需要了解经过身份验证的用户。这给单元测试带来了一些问题。
对于这些类型的细粒度权利,哪些模式被视为“最佳做法”?
答案 0 :(得分:0)
TLDR;我个人只是在操作中保留访问权限检查。您总是可以尝试使用其他函数和lambdas对其进行泛化,例如:
return IfAccessIsGood("CompanyA","anotherparam",()=>{ /* do something */ });
但这是我最初为你准备的完整回复:
听起来你有一些参数在控制器动作内部发生变化,指明哪家公司和你根据公司进行访问检查,如:
[AuthorizeCustom]
[Route("{company}/{something}")]
[HttpPost]
public async Task<IHttpActionResult> DoSomething(string company, string something) {
if(company == "A" && something == "NOTA")
{
return BadRequest("Your company can't do that!!!");
}
else if(company == "B" && something == "NOTB")
{
return BadRequest("Your company can't do that!!!");
}
else {} // do the thing
}
显然,你的实现可能看起来更优雅,但我认为它正在做类似的事情。
如果公司访问从根本上必须区别对待,您可以创建一个继承自ApiController的基类,并将CompanyA和CompanyB实现的基类实现为控制器,这将使它们完全分开。然后,您可以覆盖Access函数,该函数将检查访问权限,并为您公开的api实现每种方法。您的路线将不再变化并变为:
〔路线( “公司A / {东西}”)]
〔路线( “CompanyB / {东西}”)]
这些将驻留在从Base继承的各自的类中,因此允许您进行所需的任何控制,而无需实际复制非常多的代码。
这确实很棘手,因为属性不能被继承(至少不能通过常规手段),但你可以这样做:
[AuthorizeCustom]
[RoutePrefix("CompanyA")]
public class CompanyA : BaseClass
{
[Route("{something}")]
[HttpPost]
override async Task<IHttpActionResult> DoSomething(string something) { return await base.DoSomething(something); }
override bool CheckAccess(string something) {
if(something != "A") return false;
else return true;
}
}
通过标准化访问权限并简单地覆盖属性以获取公司价值而不是覆盖每个公司实施中的访问权限,您可以使这更加优雅,这仍然不是一个很好的解决方案,除非您可以找到一些其他好处,覆盖集中功能。
老实说这一切,我可能只是在控制器动作本身中保持逻辑。如果你想轻松进行单元测试,可以将动作逻辑的核心放在别处,只需在动作中返回它的值,这样你就可以在一个单独的函数/类中单元测试99%的逻辑。