首页 > 解决方案 > 如何使用从 ajax 调用获得的 JWT 令牌在 MVC 控制器中按角色授权用户

问题描述

我正在开发一个 ASP.NET Core MVC 项目,我在其中放置了一个基本的 MVC 网站以及一个 WebApi 控制器来处理来自视图的每个调用。

我并没有真正使用整个 MVC 工作流程,只有路由和授权部分,视图是用普通的 javascript 和 html 编写的,所以没有来自 ASP.NET MVC 的元素,如标签助手或模型。

在第一个索引页面中,我有一个登录表单,它调用(使用 jquery)WebApi 控制器中的一个操作,我使用 ASP.NET Identity Core 来授权用户,如果用户和密码有效,我返回一个智威汤逊令牌。

[Produces("application/json")]
[Route("api/[controller]")]
[AllowAnonymous]
[ApiController]
public class AccountController : ControllerBase
{
    private readonly UserManager<ApplicationUser> _userManager;
    private readonly SignInManager<ApplicationUser> _signInManager;
    private readonly ILogger _logger;
    private readonly IConfiguration _configuration;

    public AccountController(
        UserManager<ApplicationUser> userManager,
        SignInManager<ApplicationUser> signInManager,
        ILogger<AccountController> logger,
        IConfiguration configuration)
    {
        _userManager = userManager;
        _signInManager = signInManager;
        _logger = logger;
        _configuration = configuration;
    }

    [Route("test")]
    [HttpGet]
    public async Task<IActionResult> Test()
    {
        return Ok(await Task.FromResult("Endpoint working"));
    }

    [Route("login")]
    [HttpPost]
    public async Task<IActionResult> Login([FromBody] LoginDto model)
    {
        var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, false, false);

        if (result.Succeeded)
        {
            var appUser = _userManager.Users.SingleOrDefault(r => r.Email == model.Email);

            return Ok(await GenerateJwtToken(model.Email, appUser));
        }

        return BadRequest(string.Empty);
    }

    [Route("logout")]
    [HttpGet]
    public async Task Logout()
    {
        await _signInManager.SignOutAsync();
    }

    private async Task<object> GenerateJwtToken(string email, IdentityUser user)
    {
        var claims = new List<Claim>
        {
            new Claim(JwtRegisteredClaimNames.Sub, email),
            new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
            new Claim(ClaimTypes.NameIdentifier, user.Id)
        };

        var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["JwtKey"]));
        var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
        var expires = DateTime.Now.AddDays(Convert.ToDouble(_configuration["JwtExpireDays"]));

        var token = new JwtSecurityToken(
            _configuration["JwtIssuer"],
            _configuration["JwtIssuer"],
            claims,
            expires: expires,
            signingCredentials: creds
        );

        return await Task.FromResult(new JwtSecurityTokenHandler().WriteToken(token));
    }
}

这部分工作正常,在数据库中我有几个具有自己角色的用户。问题是登录后我想将用户重定向到一个 MVC 控制器,该控制器具有由角色保护的操作,控制器如下所示:

public class SecuredController : Controller
{
    [Authorize]
    public IActionResult Index()
    {
        return View();
    }

    [Authorize(Roles = "Role1, Admin")]
    public IActionResult Register()
    {
        return View();
    }

    [Authorize(Roles = "Role2, Admin")]
    public IActionResult Fraud()
    {
        return View();
    }
}

登录后我正在做类似的事情

window.location = '@Url.Action("Index", "Secured")'

但我总是收到 401 响应。

是否可以将带有令牌的授权标头添加到重定向中?控制器会从令牌中了解用户角色吗?

我会很感激你的指导。

标签: c#asp.net-core-mvcasp.net-identityasp.net-core-webapiasp.net-core-2.0

解决方案


好消息是,您实际上甚至有一些选择。我是用手机写的,所以会非常简洁:

  1. 您可以使用ServiceFilterAttribute来指定CustomJwtAuthorizationFilter执行 JWT 解析、提取声明并具体IPrincipal.Identity要绑定到User安全控制器中的 protenty 的 a,如下所示:

    [ServiceFilter(typeof(CustomJwtAuthorizationFilter))]
    public class SecuredController : Controller
    {
        ...
    }
    
    public class CustomJwtAuthorizationFilter : ActionFilterAttribute
    {
        public override void OnActionExecuting(ActionExecutingContext actionContext)
        {
            // get claims out of JWT in the request's Authorization header
            var claims = ...
            var identity = new ClaimsIdentity(claims);
            actionContext.HttpContext.User = new ClaimsPrincipal(identity);
            ...
        }
    }
    

    ClaimType这里的技巧是通过将它们的属性设置为已配置来确保代表角色的声明被清楚地区分RoleClaimType,在大多数情况下应该是默认的"role"!. 但这是应该几乎透明地完成的事情,因为角色声明应该在 JWT 中指定。

  2. 使用一些现有的中间件并在您的Startup.ConfigureServices(IServiceCollection).


推荐阅读