zoukankan      html  css  js  c++  java
  • ASP.Net Core 3.1 中使用JWT认证

    JWT认证简单介绍

    关于Jwt的介绍网上很多,此处不在赘述,我们主要看看jwt的结构。

    JWT主要由三部分组成,如下:

    HEADER.PAYLOAD.SIGNATURE
    

    HEADER包含token的元数据,主要是加密算法,和签名的类型,如下面的信息,说明了

    加密的对象类型是JWT,加密算法是HMAC SHA-256

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

    然后需要通过BASE64编码后存入token中

    eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9    
    

    Payload主要包含一些声明信息(claim),这些声明是key-value对的数据结构。

    通常如用户名,角色等信息,过期日期等,因为是未加密的,所以不建议存放敏感信息。

    {"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name":"admin","exp":1578645536,"iss":"webapi.cn","aud":"WebApi"}
    

    也需要通过BASE64编码后存入token中

    eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoiYWRtaW4iLCJleHAiOjE1Nzg2NDU1MzYsImlzcyI6IndlYmFwaS5jbiIsImF1ZCI6IldlYkFwaSJ9 
    

    Signaturejwt要符合jws(Json Web Signature)的标准生成一个最终的签名。把编码后的Header和Payload信息加在一起,然后使用一个强加密算法,如 HmacSHA256,进行加密。HS256(BASE64(Header).Base64(Payload),secret)

    2_akEH40LR2QWekgjm8Tt3lesSbKtDethmJMo_3jpF4
    

    最后生成的token如下

    eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoiYWRtaW4iLCJleHAiOjE1Nzg2NDU1MzYsImlzcyI6IndlYmFwaS5jbiIsImF1ZCI6IldlYkFwaSJ9.2_akEH40LR2QWekgjm8Tt3lesSbKtDethmJMo_3jpF4
    

    开发环境

    框架:asp.net 3.1

    IDE:VS2019

    ASP.NET 3.1 Webapi中使用JWT认证

    命令行中执行执行以下命令,创建webapix项目:

    dotnet new webapi -n Webapi -o WebApi
    

    特别注意的时,3.x默认是没有jwt的Microsoft.AspNetCore.Authentication.JwtBearer库的,所以需要手动添加NuGet Package,切换到项目所在目录,执行 .net cli命令

    dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer --version 3.1.0
    

    创建一个简单的POCO类,用来存储签发或者验证jwt时用到的信息

    using Newtonsoft.Json;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;
    
    namespace Webapi.Models
    
    {
        public class TokenManagement
        {
            [JsonProperty("secret")]
            public string Secret { get; set; }
    
            [JsonProperty("issuer")]
            public string Issuer { get; set; }
    
            [JsonProperty("audience")]
            public string Audience { get; set; }
    
            [JsonProperty("accessExpiration")]
            public int AccessExpiration { get; set; }
    
            [JsonProperty("refreshExpiration")]
            public int RefreshExpiration { get; set; }
        }
    }
    

    然后在 appsettings.Development.json 增加jwt使用到的配置信息(如果是生成环境在appsettings.json添加即可)

    "tokenManagement": {
            "secret": "123456",
            "issuer": "webapi.cn",
            "audience": "WebApi",
            "accessExpiration": 30,
            "refreshExpiration": 60
        }
    

    然后再startup类的ConfigureServices方法中增加读取配置信息

    public void ConfigureServices(IServiceCollection services)
            {
                services.AddControllers();
                services.Configure<TokenManagement>(Configuration.GetSection("tokenManagement"));
                var token = Configuration.GetSection("tokenManagement").Get<TokenManagement>();
    
            }
    

    到目前为止,我们完成了一些基础工作,下面再webapi中注入jwt的验证服务,并在中间件管道中启用authentication中间件。

    startup类中要引用jwt验证服务的命名空间

    using Microsoft.AspNetCore.Authentication.JwtBearer;
    using Microsoft.IdentityModel.Tokens;
    

    然后在ConfigureServices方法中添加如下逻辑

    services.AddAuthentication(x =>
                {
                    x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                    x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
                }).AddJwtBearer(x =>
                {
                    x.RequireHttpsMetadata = false;
                    x.SaveToken = true;
                    x.TokenValidationParameters = new TokenValidationParameters
                    {
                        ValidateIssuerSigningKey = true,
                        IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(token.Secret)),
                        ValidIssuer = token.Issuer,
                        ValidAudience = token.Audience,
                        ValidateIssuer = false,
                        ValidateAudience = false
                    };
                });
    

    Configure方法中启用验证

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
            {
                if (env.IsDevelopment())
                {
                    app.UseDeveloperExceptionPage();
                }
    
                app.UseHttpsRedirection();
    
                app.UseAuthentication();
                app.UseRouting();
    
                app.UseAuthorization();
    
                app.UseEndpoints(endpoints =>
                {
                    endpoints.MapControllers();
                });
            }
    

    上面完成了JWT验证的功能,下面就需要增加签发token的逻辑。我们需要增加一个专门用来用户认证和签发token的控制器,命名成AuthenticationController,同时增加一个请求的DTO类

    public class LoginRequestDTO
        {
            [Required]
            [JsonProperty("username")]
            public string Username { get; set; }
    
    
            [Required]
            [JsonProperty("password")]
            public string Password { get; set; }
        }
    
    [Route("api/[controller]")]
        [ApiController]
        public class AuthenticationController : ControllerBase
        {
            [AllowAnonymous]
             [HttpPost, Route("requestToken")]
            public ActionResult RequestToken([FromBody] LoginRequestDTO request)
            {
                if (!ModelState.IsValid)
                {
                    return BadRequest("Invalid Request");
                }
    
                return Ok();
    
            }
        }
    

    目前上面的控制器只实现了基本的逻辑,下面我们要创建签发token的服务,去完成具体的业务。第一步我们先创建对应的服务接口,命名为IAuthenticateService

    public interface IAuthenticateService
        {
            bool IsAuthenticated(LoginRequestDTO request, out string token);
        }
    

    接下来,实现接口

    public class TokenAuthenticationService : IAuthenticateService
        {
            public bool IsAuthenticated(LoginRequestDTO request, out string token)
            {
                throw new NotImplementedException();
            }
        }
    

    StartupConfigureServices方法中注册服务

    services.AddScoped<IAuthenticateService, TokenAuthenticationService>();
    

    在Controller中注入IAuthenticateService服务,并完善action

    public class AuthenticationController : ControllerBase
        {
            private readonly IAuthenticateService _authService;
            public AuthenticationController(IAuthenticateService authService)
            {
                this._authService = authService;
            }
            [AllowAnonymous]
             [HttpPost, Route("requestToken")]
            public ActionResult RequestToken([FromBody] LoginRequestDTO request)
            {
                if (!ModelState.IsValid)
                {
                    return BadRequest("Invalid Request");
                }
    
                string token;
                if (_authService.IsAuthenticated(request, out token))
                {
                    return Ok(token);
                }
    
                return BadRequest("Invalid Request");
    
            }
        }
    

    正常情况,我们都会根据请求的用户和密码去验证用户是否合法,需要连接到数据库获取数据进行校验,我们这里为了方便,假设任何请求的用户都是合法的。

    这里单独加个用户管理的服务,不在IAuthenticateService这个服务里面添加相应逻辑,主要遵循了职责单一原则。首先和上面一样,创建一个服务接口IUserService

    public interface IUserService
        {
            bool IsValid(LoginRequestDTO req);
        }
    

    实现IUserService接口

    public class UserService : IUserService
        {
            //模拟测试,默认都是人为验证有效
            public bool IsValid(LoginRequestDTO req)
            {
                return true;
            }
        }
    

    同样注册到容器中

    services.AddScoped<IUserService, UserService>();
    

    接下来,就要完善TokenAuthenticationService签发token的逻辑,首先要注入IUserService 和 TokenManagement,然后实现具体的业务逻辑,这个token的生成还是使用的Jwt的类库提供的api,具体不详细描述。

    特别注意下TokenManagement的注入是已IOptions的接口类型注入的,还记得在Startpup中吗?我们是通过配置项的方式注册TokenManagement类型的。

     public class TokenAuthenticationService : IAuthenticateService
        {
            private readonly IUserService _userService;
            private readonly TokenManagement _tokenManagement;
            public TokenAuthenticationService(IUserService userService, IOptions<TokenManagement> tokenManagement)
            {
                _userService = userService;
                _tokenManagement = tokenManagement.Value;
            }
            public bool IsAuthenticated(LoginRequestDTO request, out string token)
            {
                token = string.Empty;
                if (!_userService.IsValid(request))
                    return false;
                var claims = new[]
                {
                    new Claim(ClaimTypes.Name,request.Username)
                };
                var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_tokenManagement.Secret));
                var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
                var jwtToken = new JwtSecurityToken(_tokenManagement.Issuer, _tokenManagement.Audience, claims, expires: DateTime.Now.AddMinutes(_tokenManagement.AccessExpiration), signingCredentials: credentials);
    
                token = new JwtSecurityTokenHandler().WriteToken(jwtToken);
    
                return true;
    
            }
        }
    

    准备好测试试用的APi,打上Authorize特性,表明需要授权!

    [ApiController]
        [Route("[controller]")]
        [Authorize]
        public class WeatherForecastController : ControllerBase
        {
            private static readonly string[] Summaries = new[]
            {
                "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
            };
    
            private readonly ILogger<WeatherForecastController> _logger;
    
            public WeatherForecastController(ILogger<WeatherForecastController> logger)
            {
                _logger = logger;
            }
    
            [HttpGet]
            public IEnumerable<WeatherForecast> Get()
            {
                var rng = new Random();
                return Enumerable.Range(1, 5).Select(index => new WeatherForecast
                {
                    Date = DateTime.Now.AddDays(index),
                    TemperatureC = rng.Next(-20, 55),
                    Summary = Summaries[rng.Next(Summaries.Length)]
                })
                .ToArray();
            }
        }
    

    支持我们可以测试验证了,我们可以使用postman来进行http请求,先启动http服务,获取url,先测试一个访问需要授权的接口,但没有携带token信息,返回是401,表示未授权

    下面我们先通过认证接口,获取token,居然报错,查询了下,发现HS256算法的秘钥长度最新为128位,转换成字符至少16字符,之前设置的秘钥是123456,所以导致异常。

    System.ArgumentOutOfRangeException: IDX10603: Decryption failed. Keys tried: 'HS256'. Exceptions caught: '128'. token: '48' (Parameter 'KeySize') at
    

    更新秘钥

     "tokenManagement": {
            "secret": "123456123456123456",
            "issuer": "webapi.cn",
            "audience": "WebApi",
            "accessExpiration": 30,
            "refreshExpiration": 60
        }
    

    重新发起请求,成功获取token

    eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoiYWRtaW4iLCJleHAiOjE1Nzg2NDUyMDMsImlzcyI6IndlYmFwaS5jbiIsImF1ZCI6IldlYkFwaSJ9.AehD8WTAnEtklof2OJsvg0U4_o8_SjdxmwUjzAiuI-o

    把token带到之前请求的api中,重新测试,成功获取数据

    总结

    基于token的认证方式,让我们构建分布式/松耦合的系统更加容易。任何地方生成的token,只有拥有相同秘钥,就可以再任何地方进行签名校验。

    当然要用好jwt认证方式,还有其他安全细节需要处理,比如palyload中不能存放敏感信息,使用https的加密传输方式等等,可以根据业务实际需要再进一步安全加固!

    同时我们也发现使用token,就可以摆脱cookie的限制,所以JWT是移动app开发的首选!

  • 相关阅读:
    std thread
    windows更新包发布地址
    How to set up logging level for Spark application in IntelliJ IDEA?
    spark 错误 How to set heap size in spark within the Eclipse environment?
    hadoop 常用命令
    windows 安装hadoop 3.2.1
    windows JAVA_HOME 路径有空格,执行软连接
    day01MyBatisPlus条件构造器(04)
    day01MyBatisPlus的CRUD 接口(03)
    day01MyBatisPlus入门(02)
  • 原文地址:https://www.cnblogs.com/liuww/p/12177272.html
Copyright © 2011-2022 走看看