ASP.NET Core 使用 JWT 进行身份认证

JWT (JSON Web Token) 是当前非常流行的无状态身份验证的一种解决方案,其原理为通过加密算法生成一个字符串,将字符串置于每次请求的Header中来进行身份验证。

JWT的构成

一个有效的JWT字符串示例如下:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1ODMyNDM0NzQsImlzcyI6Iklzc3VlciIsImF1ZCI6IkF1ZGllbmNlIn0.nrVYSkcIKMFdd9HSjkZz_7Kkcx3sdjEdFI9nT42OKac

可以看出JWT 字符串由.分隔开来的三部分构成:

  • Header
  • Payload
  • Signature

Header

JWTHeader部分eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9通过Base64解码可以得到其内容为:

{
  "alg": "HS256",
  "typ": "JWT"
}

容易看出Header包含了两个部分:

类型 全称 释义
alg algorithm 签名算法名称
typ type token类型

Payload

Payload即载荷,载荷中主要放一些声明(Claims)性的信息,比如用户名,角色等信息,因为是未加密的,所以不要放置密码等敏感的信息。
在这里同样将JWTPayload部分eyJleHAiOjE1ODMyNDM0NzQsImlzcyI6Iklzc3VlciIsImF1ZCI6IkF1ZGllbmNlIn0通过Base64解码得到其内容为:

{
    "exp":1583243474,
    "iss":"Issuer",
    "aud":"Audience"
}

容易看出这些键值对所代表的含义:

类型 全称 释义
exp expires 过期时间
iss issuer 发行者
aud audience 受众

Signature

Signature签名部分,由HeaderPayload两部分编码用Header里面注明的加密算法加密而成。

HMACSHA256(
  Base64UrlEncoder.Encode(header) + "." +
  Base64UrlEncoder.Encode((payload),
  Secret)

这里的Secret是自己确定的密钥。

ASP.NET Core 实现JWT

开发环境准备

  • 操作系统:Windows 10 1909
  • IDE:Visual Studio Enterprise 2019 16.4.5
  • .NET Core Runtime:3.1.102

新建项目

Visual Studio中新建一个ASP.NET Core Web API项目,如下所示:

新建项目

通过Nuget安装JWT相关包

Nuget包管理器中安装Microsoft.AspNetCore.Authentication.JwtBearer包,如下所示

Microsoft.AspNetCore.Authentication.JwtBearer包

Startup中配置JWT信息

ConfigureServices方法中加入配置后如下:

       public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers();
            services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
                .AddJwtBearer(options =>
                {
                    options.TokenValidationParameters = new TokenValidationParameters
                    {
                        ValidateIssuer = true,
                        ValidIssuer = "Issuer",

                        ValidateAudience = true,
                        ValidAudience = "Audience",

                        ValidateIssuerSigningKey = true,
                        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("{069BD1DF-72D8-474B-8950-2C3EB03B2D03}")),

                        ValidateLifetime = true,
                        ClockSkew = TimeSpan.FromMinutes(30),
                    };
                });
            services.AddSingleton();
        }

其中services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)是将Bearer作为身份认证的默认方案,JwtBearerDefaults.AuthenticationSchemeBearer字符串常量。
services.AddJwtBearer是进行JWT的相关配置。

添加认证管道

Configure方法中加入:

 app.UseAuthentication();

注意它需要添加在app.UseAuthorization();之前,app.UseRouting();之后,顺序很重要,否则身份认证不会生效。

新建LoginController签发token

namespace JwtTokenDemo.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class LoginController : ControllerBase
    {
        // POST: api/Login
        [HttpPost]
        public IActionResult Index([FromBody] LoginViewModel model)
        {
            if (_names.Contains(model.Username) && model.Password == "admin")
            {
                var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("{069BD1DF-72D8-474B-8950-2C3EB03B2D03}"));
                var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
                var token = new JwtSecurityToken(
                    "Issuer",
                    "Audience",
                    new List
                    {
                        new Claim(ClaimTypes.Role, "admin"),
                        new Claim(ClaimTypes.Gender,"Male"),
                        new Claim(ClaimTypes.Name,model.Username),
                    },
                    expires: DateTime.UtcNow.AddMinutes(30),
                    signingCredentials: credentials
                );
                return Ok(new
                {
                    access_token = new JwtSecurityTokenHandler().WriteToken(token)
                });
            }
            return Unauthorized();
        }
   }
}

在上面,我们定义了token的过期时间为令牌颁发后的30分钟后,同时定义了RoleGenderNameClaim声明,这些都可以通过解码Payload部分查看到。
这里用了一个用户类接受账号密码:

public class LoginViewModel
{
    [JsonProperty("username")]
    public string Username { get; set; }
    [JsonProperty("password")]
    public string Password { get; set; }
}

设置受保护的资源

在需要验证的控制器或者HTTP请求方法上添加[Authorize]修饰头,请求的时候就会服务器验证当前请求是否有权限访问此资源。如,我们给Weather控制器加上需要授权才能访问:

    [Authorize]
    [ApiController]
    [Route("[controller]")]
    public class WeatherForecastController : ControllerBase
    {
        private static readonly string[] Summaries = new[]
        {
            "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
        };

        private readonly ILogger _logger;
        private readonly WeatherForecast _weatherForecast;


        public WeatherForecastController(ILogger logger,WeatherForecast weatherForecast)
        {
            _logger = logger;
            _weatherForecast = weatherForecast;
        }

        [HttpGet]
        public IEnumerable Get()
        {
            var user = HttpContext.User;
            var rng = new Random();
            return Enumerable.Range(1, 5).Select(index => _weatherForecast)
            .ToArray();
        }
    }

Postman 测试

FromBody提交用户名和密码来获取access_token

登录

验证资源

access_token的值加入到HTTP请求的Header中的Authorization去,正确格式为:

Authorization:Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dzLzIwMDgvMDYvaWRlbnRpdHkvY2xhaW1zL3JvbGUiOiJhZG1pbiIsImh0dHA6Ly9zY2hlbWFzLnhtbHNvYXAub3JnL3dzLzIwMDUvMDUvaWRlbnRpdHkvY2xhaW1zL2dlbmRlciI6Ik1hbGUiLCJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoiYWRtaW4iLCJleHAiOjE1ODMzMzk4MTMsImlzcyI6Iklzc3VlciIsImF1ZCI6IkF1ZGllbmNlIn0.ln6ZZ2b_eNgcDxW7zrmI6xY4clxdu4f-zScJ43FjmwY

注意Bearer与后面的access_token之间有一个空格。
我们请求资源可以看到200 OK,请求成功。如下图:

请求成功

当我们修改token中某个字符再次请求,就会发现401 Unauthorized 未授权响应。如下图:
请求失败

欲知如何进行基于角色、基于声明、基于策略的授权,请听下回分解。

你可能感兴趣的:(ASP.NET Core 使用 JWT 进行身份认证)