Как получить роль пользователя внутри метода WebAPI без поиска таблицы AspNetUserRoles?
У меня есть хранимая процедура, которая обновляет статус. В зависимости от роли пользователя хранимая процедура имеет код, который может или не может позволить изменять статус. По этой причине мне нужно передать имя роли в хранимую процедуру. Имя моей роли хранится на клиенте в моем javascript-коде, но, конечно, мне нужна вторая проверка на сервере. Каждый пользователь имеет только одну из трех ролей и при запросе обновления статуса я могу вызвать один из трех методов в зависимости от роли, которую имеет клиент. Вот что я пробовал.
Я использую WebApi с аутентификацией на основе токена и идентификатором ASP.NET 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);
}
Однако мой вопрос здесь относится к серверу и как я могу проверить свой метод, если пользователь находится в определенной роли, не обращаясь к базе данных. Возможно, есть способ сделать это безопасно с претензиями, но я не знаю, как это сделать.
Любая помощь и советы будут высоко оценены.
Ответы
Ответ 1
Как вы заявили, вы используете токены-носители для защиты своих конечных точек. Я считаю, что есть мало недоразумений в том, что в них содержатся магические струны-носители.
Ну, эти жетоны содержат все роли для пользователя, которому вы выдали токен, а также если вы используете DPAPI по умолчанию для защиты данных в Web API не (JWT Tokens), то эти токены подписываются и шифруются, поэтому никто не может вмешиваться в данные внутри маркера, если у него нет машинного ключа для веб-сервера, выдавшего этот токен, поэтому не беспокойтесь о защите данных.
Моя рекомендация состоит в том, чтобы прочитать роли/претензии для пользователя из базы данных, нет необходимости в обходных методах и взломах, которые вы пытаетесь сделать, все, что вам нужно сделать, - это установить требования к пользователям при входе в систему в методе GrantResourceOwnerCredentials
Вы можете установить его таким образом, получив пользователя, затем прочитав роли из БД и установив их в качестве утверждения типа "Роль"
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"));
Помните, что это происходит только один раз при входе пользователя в систему, после чего вы получите подписанный подписчик и подписанный токен, который содержит все претензии для этого пользователя, нет необходимости в доступе к БД для его проверки.
Или, если вы хотите создать личность из базы данных, вы можете использовать ниже:
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 и проверить, прочитает ли он заявки из зашифрованного токена вместе с требованием Роли и проверить, принадлежит ли этот пользователь роли Учителя и разрешить или отклонить запрос.
Я опубликовал подробную серию из 5 сообщений о аутентификации на основе токенов вместе с сервером авторизации, и токены JWT. Я рекомендую вам прочитать эти сообщения, чтобы лучше понять токены-носители.
Ответ 2
Вместо трех отдельных методов вы можете просто проверить User.IsInRole("RoleName")
, доступные в классе контроллера. Он использует ту же логику, что и в атрибуте [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
{
}
Ответ 3
Заявки о роли по умолчанию хранятся в файле cookie для входа пользователя, поэтому [Authorize (Роли = "Учитель" )] должен быть быстрым. Единственный бэнд базы данных будет при первом входе в систему (или при обновлении знака в cookie. Проверки авторизации выполняются против IClaimsPrincipal, созданных из знака в cookie.
Вам может потребоваться также обновить какой-либо другой код, чтобы это работало, см. этот вопрос, который похож на то, что вы делаете: Авторизовать с ролями