我有一个更新状态的存储过程。根据用户的角色,存储过程具有允许或不允许状态更改的代码。出于这个原因,我需要将角色名称传递到存储过程中。我的角色名称存储在客户端的javascript代码中,但当然我需要在服务器上进行第二次检查。每个用户只有三个角色中的一个,当请求更新状态时,我可以根据客户端的角色调用三个方法之一。这是我试过的。
我使用WebApi,带有基于承载令牌的身份验证和ASP.NET Identity 2.1,应用程序始终在浏览器中运行。我的用户已经设置了适当的角色。
我放了一些代码来获取userId,然后转到AspNetUserRoles表来获取方法开始时的角色。然而,我注意到这大约需要500毫秒才能运行。作为替代方案,我正在考虑以下内容:
[HttpPut]
[Authorize(Roles = "Admin")]
[Route("AdminUpdateStatus/{userTestId:int}/{userTestStatusId:int}")]
public async Task<IHttpActionResult> AdminUpdateStatus(int userTestId, int userTestStatusId)
{
return await UpdateStatusMethod(userTestId, userTestStatusId, "Admin");
}
[HttpPut]
[Authorize(Roles = "Student")]
[Route("StudentUpdateStatus/{userTestId:int}/{userTestStatusId:int}")]
public async Task<IHttpActionResult> StudentUpdateStatus(int userTestId, int userTestStatusId)
{
return await UpdateStatusMethod(userTestId, userTestStatusId, "Student");
}
[HttpPut]
[Authorize(Roles = "Teacher")]
[Route("TeacherUpdateStatus/{userTestId:int}/{userTestStatusId:int}")]
public async Task<IHttpActionResult> TeacherUpdateStatus(int userTestId, int userTestStatusId)
{
return await UpdateStatusMethod(userTestId, userTestStatusId, "Teacher");
}
private async Task<IHttpActionResult> UpdateStatusMethod(int userTestId, int userTestStatusId, string roleName)
{
// Call the stored procedure here and pass in the roleName
}
这是一种有效的方法吗?或者还有其他更干净的方法吗。我不清楚的是前端还是后端缓存用户角色。我认为这已经完成,或者有一些设置可以允许这样做。
注意:我使用索赔将角色信息发送到我的客户这里:
public static AuthenticationProperties CreateProperties(
string userName,
ClaimsIdentity oAuthIdentity,
string firstName,
string lastName,
int organization)
{
IDictionary<string, string> data = new Dictionary<string, string>
{
{ "userName", userName},
{ "firstName", firstName},
{ "lastName", lastName},
{ "organization", organization.ToString()},
{ "roles",string.Join(":",oAuthIdentity.Claims.Where(c=> c.Type == ClaimTypes.Role).Select(c => c.Value).ToArray())}
};
return new AuthenticationProperties(data);
}
然而,我在这里的问题涉及服务器,以及如果用户在不访问数据库的情况下担任某个角色,我如何检查我的方法。也许有一种方法可以安全地处理索赔,但我不知道如何做到这一点。
如有任何帮助和建议,我们将不胜感激。
正如您所说,您正在使用承载令牌来保护您的端点。我相信,人们对魔术串中的承载符号并没有什么误解。这些令牌包含您为其颁发令牌的用户的所有角色,并且如果您在Web API not(JWT令牌)中使用默认的数据保护DPAPI,则这些令牌将被签名和加密,因此任何人都不能篡改令牌中的数据,除非他拥有颁发此令牌的Web服务器的mashineKey,因此不必担心数据保护。
我的建议是从数据库中读取用户的角色/声明,不需要你试图做的这些变通方法和技巧,你所需要做的就是在用户登录方法GrantResourceOwnerCredentials
时为他们设置声明。你可以这样设置,让用户从DB中读取角色,并将其设置为"角色"类型的声明
var identity = new ClaimsIdentity(context.Options.AuthenticationType);
identity.AddClaim(new Claim(ClaimTypes.Name, context.UserName));
identity.AddClaim(new Claim(ClaimTypes.Role, "Admin"));
identity.AddClaim(new Claim(ClaimTypes.Role, "Supervisor"));
请记住,当用户登录时,这种情况只发生一次,然后您将收到一个承载签名和加密的令牌,其中包含该用户的所有声明,无需任何DB访问来验证它。
或者,如果你想从数据库中创建身份,你可以使用以下方法:
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager, string authenticationType)
{
// Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
var userIdentity = await manager.CreateIdentityAsync(this, authenticationType);
// Add custom user claims here
return userIdentity;
}
然后在GrantResourceOwnerCredentials
中执行以下操作:
ClaimsIdentity oAuthIdentity = await user.GenerateUserIdentityAsync(userManager, OAuthDefaults.AuthenticationType);
现在,一旦您将承载令牌发送到具有Authorize
属性(如[Authorize(Roles = "Teacher")]
)的受保护端点,我可以向您保证您的代码不会进入数据库进行任何查询打开SQL探查器并检查,它将从加密令牌中读取声明以及Roles声明,并检查此用户是否属于教师角色,并允许或拒绝请求。
我在博客上写了一系列关于基于令牌的身份验证、授权服务器和JWT令牌的详细文章,共5篇。我建议你阅读这些帖子,以更好地了解持牌代币。
您可以简单地检查控制器类中可用的User.IsInRole("RoleName")
,而不是使用3个单独的方法。它使用的逻辑与[Authorise]
属性中的逻辑相同。例如
public class DataApiController : ApiController
{
[HttpPut]
[Route("UpdateStatus/{userTestId:int}/{userTestStatusId:int}")]
public async Task<IHttpActionResult> UpdateStatus(int userTestId, int userTestStatusId)
{
if(User.IsInRole("Admin"))
{
//Update method etc....
}
//else if(....) else etc
{
}
角色声明默认存储在用户的登录cookie中,因此[授权(Roles="Teacher")]应该很快。唯一的数据库命中将是在您第一次登录时(或每当刷新登录cookie时)。授权检查是针对从登录cookie创建的IClaimsPrincipal进行的。
您可能还需要更新一些其他代码才能正常工作,请参阅这个问题,它与您正在做的类似:使用角色授权