Как я могу получить роль пользователя внутри метода WebAPI без поиска в таблице AspNetUserRoles?

У меня есть хранимая процедура, которая обновляет статус. В зависимости от роли пользователя хранимая процедура имеет код, который может разрешить или запретить изменение статуса. По этой причине мне нужно передать имя роли в хранимую процедуру. Имя моей роли хранится на клиенте в моем коде javascript, но, конечно, мне нужна вторая проверка на сервере. У каждого пользователя есть только одна из трех ролей, и при запросе обновления статуса я могу вызвать один из трех методов в зависимости от роли, которую имеет клиент. Вот что я пробовал.

Я использую WebApi с аутентификацией на основе токенов-носителей и ASP.NET Identity 2.1, и приложение всегда запускается в браузере. Моим пользователям были назначены соответствующие роли.

Я добавляю некоторый код для получения идентификатора пользователя, а затем перехожу к таблице 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);
        }

Однако мой вопрос здесь относится к серверу и к тому, как я могу проверить в своем методе, находится ли пользователь в определенной роли, не обращаясь к базе данных. Может быть, есть способ сделать это безопасно с помощью утверждений, но я не знаю, как это сделать.

Любая помощь и совет будут высоко оценены.


person Samantha J T Star    schedule 27.10.2014    source источник
comment
Вы думали об использовании претензий?   -  person Excommunicated    schedule 28.10.2014
comment
Вы уверены, что именно поиск роли действительно вызывает задержку. Это должно быть довольно эффективно. Чем занимается ваш СП? Сколько у вас пользователей?   -  person Pleun    schedule 29.10.2014
comment
Я выполнял поиск роли вручную с вызовом таблицы базы данных. Время поиска заняло около 500 мс. Я уверен, что в продакшене это будет намного быстрее, но я хотел бы узнать больше о том, обращается ли инфраструктура WebAPI к базе данных каждый раз, чтобы проверить роль перед запуском метода, или есть какое-то кеширование. У меня много других задержек, поэтому я хочу ускорить все, что могу.   -  person Samantha J T Star    schedule 29.10.2014
comment
Определены ли ваши роли пользователей в базе данных? Если да, то почему вы должны передать роль обратно процедуре? Разве вы не можете просто запросить его по имени пользователя?   -  person Ray Cheng    schedule 30.10.2014
comment
Роли пользователей определяются в другой базе данных SQL Azure. Насколько я понимаю, с облачной базой данных SQL Azure невозможно выполнить запрос между базами данных.   -  person Samantha J T Star    schedule 30.10.2014
comment
Как насчет переноса ролей в одну и ту же базу данных?   -  person Pleun    schedule 30.10.2014


Ответы (3)


Как вы заявили, вы используете токены на предъявителя для защиты своих конечных точек. Я полагаю, что есть небольшое недопонимание относительно того, что содержит магическая строка этих токенов на предъявителя. Что ж, эти токены содержат все роли для пользователя, для которого вы выпустили токен, а также, если вы используете DPAPI для защиты данных по умолчанию в веб-API, а не (токены JWT), тогда эти токены подписаны и зашифрованы, поэтому никто не может вмешиваться в данные внутри токена, если у него нет mashineKey для веб-сервера, выдавшего этот токен, так что не беспокойтесь о защите данных.

Я рекомендую читать роли/претензии для пользователя из базы данных, нет необходимости в этих обходных путях и хаках, которые вы пытаетесь сделать, все, что вам нужно сделать, это установить претензии для пользователей, когда они входят в систему методом 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. Я рекомендую вам прочитать эти сообщения, чтобы лучше понять токены на предъявителя.

person Taiseer Joudeh    schedule 30.10.2014
comment
Спасибо за вашу помощь и совет. Я прочитаю ваши блоги и попытаюсь реализовать ваше решение в течение следующих нескольких дней. Если у меня есть вопрос, я буду обновлять здесь. Только один быстрый вопрос в это время. Как бы вы предложили мне справиться с необходимостью обрабатывать несколько разных атрибутов авторизации. Прямо сейчас работает метод наличия одного частного метода с тремя или более общедоступными методами (каждый с другим авторизацией). Но я думаю, что может быть лучший способ. Можете ли вы предложить какой-либо способ, которым я мог бы сделать это, используя только один метод и тест внутри него для роли? - person Samantha J T Star; 30.10.2014
comment
Добро пожаловать @SamanthaJ. То, что вы делаете, не является неправильным, и вы можете использовать вложенный if else внутри своего метода действия, например if(User.IsInRole(Teacher)){}, чтобы в итоге у вас было одно действие (общедоступная конечная точка) вместо трех, тогда вы присвойте этому методу действия [Авторизовать (Роли = Учитель, Студент, Администратор)]. Кроме того, эта проверка не попадет в БД, поскольку субъект утверждений создается на основе вашего токена доступа. - person Taiseer Joudeh; 31.10.2014
comment
@SamanthaJ, вы пробовали предложенное решение? Работаете на вашем конце? - person Taiseer Joudeh; 01.11.2014
comment
@TaiseerJoudeh Как вы думаете, вы могли бы помочь мне с этим вопросом goo.gl/qiOdmT - person Axel; 10.11.2014
comment
Я думаю, что здесь я упускаю что-то действительно очевидное, но как вы получаете утверждения, которые вы устанавливаете для токена? - person Carlos Martinez; 31.12.2014
comment
@TaiseerJoudeh: могу ли я добавить , разделенных ролей, чтобы утверждать, как это identity.AddClaim(new Claim(ClaimTypes.Role, "Client,User")); - person Amit Kumar; 25.04.2016

Вместо того, чтобы иметь 3 отдельных метода, вы можете просто проверить 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
    {
}
person Excommunicated    schedule 30.10.2014
comment
Но не будет ли каждый из этих User.IsInRole(Admin) иметь доступ к базе данных? Что, если бы у меня было 5 ролей, то это было бы пять обращений к базе данных, чтобы проверить, находится ли пользователь в последней из ролей, не так ли? - person Samantha J T Star; 30.10.2014
comment
В зависимости от вашего принципала. Если вы используете ClaimsPrincipal, то нет, для каждого из них не будет вызова БД, поскольку он получит роли из претензии. - person Excommunicated; 31.10.2014
comment
+1 И это правильный ответ. Платформа @Pleun Identity не будет вызывать БД для проверки роли. Смотрите ответ Хао Кунга для объяснения. - person trailmax; 31.10.2014
comment
Кроме того, коллекцию ролей можно кэшировать в файле cookie, указав атрибут в конфигурации RoleProvider в файле web.config. Это означает, что при использовании User.IsInRole(имя роли) поиск в БД не выполняется. - person Paul Taylor; 04.11.2014
comment
@PaulTaylor, вот: принцип ClaimsPrincipal = Request.GetRequestContext(). Принципал as ClaimsPrincipal; имя переменной = Пользователь.Идентификация.Имя; var ClaimValue = Principal.Claims.Where(c =› c.Type == sub).Single().Value; - person Taiseer Joudeh; 31.12.2014
comment
Пользовательский объект находится в HttpContext для тех, кто не знает: HttpContext.Current.User.IsInRole - person Sal; 21.07.2017

Заявки на роли по умолчанию хранятся в файле cookie входа пользователя, поэтому [Authorize(Roles = "Teacher")] должен выполняться быстро. Единственное попадание в базу данных произойдет при первом входе в систему (или всякий раз, когда обновляется файл cookie для входа. Проверки авторизации выполняются для IClaimsPrincipal, созданного из файла cookie для входа.

Возможно, вам также потребуется обновить какой-то другой код, чтобы это работало, см. этот вопрос, который похож на то, что вы делаете: Авторизация с помощью ролей

person Hao Kung    schedule 29.10.2014
comment
Возможно ли, чтобы этот файл cookie для входа пользователя был изменен пользователем? Я обеспокоен тем, что пользователь меняет там файл cookie, а затем делает что-то, чего он не должен делать. Также есть ли лучший способ выполнить проверку ролей внутри метода, а не способ, который я предлагаю с тремя методами с [Авторизовать] на каждом, указывающем на один и тот же частный метод? - person Samantha J T Star; 30.10.2014
comment
Нет, файл cookie зашифрован и должен быть защищен от подделки, я не думаю, что вы можете легко выполнить одну авторизацию с семантикой ИЛИ, поэтому наличие 3 отдельных методов, каждый из которых сопоставлен с одной конкретной ролью, кажется прекрасным. Но вы можете написать руководство If User.IsInRole() для каждой роли в методе напрямую - person Hao Kung; 31.10.2014
comment
Аргумент Roles для атрибута Authorize принимает список имен ролей, разделенных запятыми. - person Paul Taylor; 04.11.2014