ASP.NET MVC 5 Identity 2 Переадресация на вход в зависимости от роли пользователя

Я пытаюсь перенаправить пользователя на страницу на основе их роли,

Это стандартная реализация функции входа в систему, которая поставляется с ASP.NET MVC 5:

[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
    if (ModelState.IsValid)
    {
        var user = await UserManager.FindAsync(model.UserName, model.Password);
        if (user != null)
        {
            await SignInAsync(user, model.RememberMe);
            return RedirectToLocal(returnUrl);
        }
        else
        {
            ModelState.AddModelError("", "Invalid username or password.");
        }
    }

    // If we got this far, something failed, redisplay form
    return View(model);
}

private ActionResult RedirectToLocal(string returnUrl)
{
    if (Url.IsLocalUrl(returnUrl))
    {
        return Redirect(returnUrl);
    }
    else
    {
        return RedirectToAction("Index", "Employer");
    }
}

Я хочу, чтобы иметь возможность перенаправлять пользователя на основе их роли, я пытался сделать это следующим образом:

[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
    if (ModelState.IsValid)
    {
        var user = await UserManager.FindAsync(model.UserName, model.Password);
        if (user != null)
        {
            await SignInAsync(user, model.RememberMe);

            //role Employer go to Employer page
            if (UserManager.IsInRole(user.Id, "Employer"))
            {
                return RedirectToAction("Index", "Employer");
            }
            //role Admin go to Admin page
            else if (UserManager.IsInRole(user.Id, "Admin"))
            {
                return RedirectToAction("Index", "Admin");
            }
            else
            {
                //no role
                return RedirectToAction("Index", "Home");
            }
        }
        else
        {
            ModelState.AddModelError("", "Invalid username or password.");
        }
    }

    // If we got this far, something failed, redisplay form
    return View(model);
}

Но есть проблема, хотя сайт перенаправляет меня на правильную страницу. Если я нахожусь, набрав url foo.com/admin, когда я не войду в систему с учетной записью администратора, сайт приведет меня к страницу входа с URL-адресом foo.com/Account/Login?ReturnUrl=%2Fadmin, что является ожидаемым поведением.

если я вхожу в систему с учетной записью работодателя на этом этапе, он перенаправит меня на страницу работодателя и запишет меня в качестве работодателя, что не так, но это не должно быть так, сайт должен упомянуть меня должен войти в систему с учетной записью администратора, потому что возвращаемый URL-адрес "admin". Надеюсь, у меня есть смысл.

Ответ 1

почему вы не проверяете, есть ли returnUrl перед вашими переадресациями?

    [HttpPost]
    [AllowAnonymous]
    [ValidateAntiForgeryToken]
    public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
    {
         if (ModelState.IsValid)
         {
              var user = await UserManager.FindAsync(model.UserName, model.Password);
              if (user != null)
              {
                    await SignInAsync(user, model.RememberMe);
                    if (String.IsNullOrEmpty(returnUrl))
                    {
                         if (UserManager.IsInRole(user.Id, "Employer"))
                         {
                             return RedirectToAction("Index", "Employer");
                         }
                         //role Admin go to Admin page
                         if (UserManager.IsInRole(user.Id, "Admin"))
                         {
                             return RedirectToAction("Index", "Admin");
                         }
                     }
                     else
                     {
                         return RedirectToLocal(returnUrl);
                     }
              }
              else
              {
                     ModelState.AddModelError("", "Invalid username or password.");
              }
       }

       // If we got this far, something failed, redisplay form
       return View(model);
   }

Подобно этому, если вы перейдете на foo.com/admin, он выкинет 401 и перенаправит вас на логин. Затем, если вы войдете в систему как работодатель, он бросит 401 и снова перенаправит вас на логин.

Из комментариев: "Могу ли я просто перенаправить (returnUrl) и удалить метод действия RedirectToLocal?"

RedirectToLocal(returnUrl) проверяет, есть ли Url.IsLocalUrl(returnUrl). Таким образом, это необходимо для предотвращения Open Redirect Attacks.

Ответ 2

Несмотря на [эту статью, написанную в 2008 году, она помогла мне в решении этой проблемы. Он дает все образцы кода, необходимые для перенаправления пользователей при входе в систему без загромождения вашего метода входа. Если вы добавляете новую роль и хотите перенаправить пользователей с этой ролью, это так же просто, как добавление строки в web.config.

Я встретил одну добычу. В своей статье он имеет следующий код, который вы бы поместили в свой AccountController (или где бы вы не перенаправляли):

/// <summary>
/// Redirect the user to a specific URL, as specified in the web.config, depending on their role.
/// If a user belongs to multiple roles, the first matching role in the web.config is used.
/// Prioritize the role list by listing higher-level roles at the top.
/// </summary>
/// <param name="username">Username to check the roles for</param>
private void RedirectLogin(string username)
{
    LoginRedirectByRoleSection roleRedirectSection = (LoginRedirectByRoleSection)ConfigurationManager.GetSection("loginRedirectByRole");
    foreach (RoleRedirect roleRedirect in roleRedirectSection.RoleRedirects)
    {
        if (Roles.IsUserInRole(username, roleRedirect.Role))
        {
            Response.Redirect(roleRedirect.Url);
        }
    }
}

В моем приложении не было найти поставщика ролей, и когда я добавил его в файл web.config, мне было трудно найти его роли. Я решил отключить роль поставщика роли и использовать UserManager, чтобы получить пользователя и роли:

    /// <summary>
    /// Redirect the user to a specific URL, as specified in the web.config, depending on their role.
    /// If a user belongs to multiple roles, the first matching role in the web.config is used.
    /// Prioritize the role list by listing higher-level roles at the top.
    /// </summary>
    /// <param name="username">Username to check the roles for</param>
    private void RedirectLogin(string username)
    {
        LoginRedirectByRoleSection roleRedirectSection = (LoginRedirectByRoleSection)ConfigurationManager.GetSection("loginRedirectByRole");
        var user = UserManager.FindByName(username);
        var rolesForUser = UserManager.GetRoles(user.Id);
        foreach (RoleRedirect roleRedirect in roleRedirectSection.RoleRedirects)
        {
            if (rolesForUser.Contains(roleRedirect.Role))
            {
                Response.Redirect(roleRedirect.Url);
            }
        }
    }