ASP.NET Core 8.0 WebApi 从零开始学习JWT登录认证

ASP.NET Core 8.0 WebApi 从零开始学习JWT登录认证

前言

我一起写后端Api我都是直接裸连的,但是现在为了规范一些,我还是打算上一个JWT功能

相关链接

ASP.*** Web API 2系列(四):基于JWT的token身份认证方案

Jwt-dot*** github

Nuget选择


选好了模板,就进去看看号了,42M的下载量已经很高了,一般来说,只要超过500k的下载量,基本就是一个稳定的代码仓库了。

进去看看里面写的怎么样

可以看到写的还是比较清晰的

知识补充

JWT不是加密算法

JWT全名 JSON Web Token。Token这部分才是加密得出来的,JWT只是一个网页认证策略。

可逆加密和不可逆加密

无论是可逆加密还是不可逆加密,都需要一个自定义的【Key】来辅助加密。可逆加密就类似于一元二次方程,key就类似于修改方程各项的系数,【Key=125】得到【x^2+2x+5】。我的原文是【1】,得到的密文就是计算后的结果【8】。所以加密比解密要方便。

不可逆加密就是不能逆向解决的方程,比如高阶方程【x^7- x^3 +5x-3】,正向好算,逆向基本不可解。

所以现实使用的时候,可逆加密一般是解密后使用。因为可逆,所以可以用于复杂的敏感信息。
不可逆加密是判断加密后的的密文是否相同,比如密码,是不能泄漏的,所以我们的密码只能重置,因为系统也不知道原密码是什么。

普通Jwt(不推荐)

项目环境

  • visual studio 2022
  • .*** core 8.0
  • win 10

Nuget


最小JWT测试

我们先不管JWT是如何添加到ASP.*** Core里面的,我们先测试JWT的加密和登录验证的功能。

/// <summary>
/// 加密密钥
/// </summary>
private static readonly string jwtKey = "TokenKey";

public record UserTest(string Name,string A***ount,string Password);

static void Main(string[] args)
{

    UserTest userTest = new UserTest("小王","admin","1dixa0d");
    Console.WriteLine("原文");
    Console.WriteLine(JsonConvert.SerializeObject(userTest));//{"Name":"小王","A***ount":"admin","Password":"1dixa0d"}

    var encoder = GetEncoder();
    string jwtToken = encoder.Encode(userTest,jwtKey);
    Console.WriteLine("加密");
    Console.WriteLine(jwtToken);//eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJOYW1lIjoi5bCP546LIiwiQWNjb3VudCI6ImFkbWluIiwiUGFzc3dvcmQiOiIxZGl4YTBkIn0.C_tlDhHpkjAkJpRRdnqz6Jn2FmP0qONAI4oNl8Ye9wM

    var decoder =  GetDecoder();
    var decodeData =  decoder.Decode(jwtToken,jwtKey);
    Console.WriteLine("解密");
    Console.WriteLine(decodeData);//{"Name":"小王","A***ount":"admin","Password":"1dixa0d"}


    Console.WriteLine("Hello, World!");
}

/// <summary>
/// 获取加密解密
/// </summary>
/// <returns></returns>
public static IJwtEncoder GetEncoder()
{
    IJwtAlgorithm algorithm = new HMACSHA256Algorithm();//加密方式
    IJsonSerializer serializer = new Json***Serializer();//序列化Json
    IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder();//base64加解密
    IJwtEncoder encoder = new JwtEncoder(algorithm, serializer, urlEncoder);//JWT编码
    return encoder;
}

/// <summary>
/// 获取解密密钥
/// </summary>
/// <returns></returns>
public static IJwtDecoder GetDecoder()
{
    IJsonSerializer serializer = new Json***Serializer();
    IDateTimeProvider provider = new UtcDateTimeProvider();
    IJwtValidator validator = new JwtValidator(serializer, provider);
    IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder();
    IJwtAlgorithm algorithm = new HMACSHA256Algorithm();
    IJwtDecoder decoder = new JwtDecoder(serializer, validator, urlEncoder, algorithm);
    return decoder;
}


这里的JWT的加密解密算法是可以自己搭配的,选择加密算法,这里我就不展开了。详细可以看官方文档

在WebApi中简单使用

首先我们之前用到了可逆的加密和解密。那我们就需要用一个判断是否过期的类

public class UserJwtLogin
{
    /// <summary>
    /// 用户Id,因为数据库的Id是唯一的,不会重复
    /// </summary>
    public long UserId { get; set; }    

    /// <summary>
    /// 过期时间
    /// </summary>
    public DateTime ExpireTime { get; set; }
}

详细的解决方案就在这个文章里面了,我就不照抄了,拾人牙慧。

ASP.*** Web API 2系列(四):基于JWT的token身份认证方案

我这里是这么写的

新建一个JwtHelper

public static class JwtHelper
{

    private static readonly string JwtKey = "这里填你的密钥";
    /// <summary>
    /// 获取加密解密
    /// </summary>
    /// <returns></returns>
    private static IJwtEncoder GetEncoder()
    {
        IJwtAlgorithm algorithm = new HMACSHA256Algorithm();//加密方式
        IJsonSerializer serializer = new Json***Serializer();//序列化Json
        IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder();//base64加解密
        IJwtEncoder encoder = new JwtEncoder(algorithm, serializer, urlEncoder);//JWT编码
        return encoder;
    }

    /// <summary>
    /// 获取解密密钥
    /// </summary>
    /// <returns></returns>
    private static IJwtDecoder GetDecoder()
    {
        IJsonSerializer serializer = new Json***Serializer();
        IDateTimeProvider provider = new UtcDateTimeProvider();
        IJwtValidator validator = new JwtValidator(serializer, provider);
        IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder();
        IJwtAlgorithm algorithm = new HMACSHA256Algorithm();
        IJwtDecoder decoder = new JwtDecoder(serializer, validator, urlEncoder, algorithm);
        return decoder;
    }

    /// <summary>
    /// 加密
    /// </summary>
    public static string Encode(object payload)
    {
        var encoder = GetEncoder();
        var token = encoder.Encode(payload, JwtKey);
        return token;
    }

    /// <summary>
    /// 解密
    /// </summary>
    public static T Decode<T>(string token)
    {
        var decoder = GetDecoder();
        var data =  decoder.Decode(token,JwtKey);
        var res  = JsonConvert.DeserializeObject<T>(data);
        return res;
    }

    /// <summary>
    /// 解密,只返回Json文本
    /// </summary>
    /// <param name="token"></param>
    /// <returns></returns>
    public static string Decode(string token)
    {

        var decoder = GetDecoder();
        var data = decoder.Decode(token, JwtKey);
        return data;
    }

}

两个实体类

   public class UserJwtLogin
   {
       /// <summary>
       /// 用户Id,因为数据库的Id是唯一的,不会重复
       /// </summary>
       public long UserId { get; set; }    

       /// <summary>
       /// 过期时间
       /// </summary>
       public DateTime ExpireTime { get; set; }
   }
public class WebMsg
{

    public int Code { get; set; } = 200;

    public bool Su***ess { get; set; } = true;

    public object Data {get; set; }

    public string Msg { get; set; } = "操作成功!";


    public WebMsg()
    {

    }


    public WebMsg(object data)
    {
        Data = data;
    }
}

简单使用

/// <summary>
/// Jwt登录
/// </summary>
[Route("api/[controller]/[action]")]
[ApiController]
public class LoginController : ControllerBase
{
	//这个是我的Nlog配置,这里不做展开
    private NlogService nlogService;

    public LoginController(NlogService nlogService)
    {
        this.nlogService = nlogService;
    }



    /// <summary>
    /// JWT登录
    /// </summary>
    /// <param name="username">账号</param>
    /// <param name="password">密码</param>
    /// <returns></returns>
    [HttpGet]
    public WebMsg Login(string username, string password)
    {

        if (username == null || password == null)
        {
            return new WebMsg()
            {
                Msg = "登录信息为空",
                Su***ess = false,
            };
        }
        if (username == "admin" && password == "123456")
        {
			//这里是模拟拿到数据库的User表的Id
            var pyload = new UserJwtLogin()
            {
                UserId = 291,
                ExpireTime = DateTime.Now.AddDays(1)
            };
            var token = JwtHelper.Encode(pyload);
            return new WebMsg(token);
        }
        else
        {
            return new WebMsg()
            {
                Msg = "登录失败,账号或者密码错误",
                Su***ess = false,
            };
        }

    }

    /// <summary>
    /// Jwt解密
    /// </summary>
    /// <param name="token"></param>
    /// <returns></returns>
    [HttpGet]
    public WebMsg Decode(string token)
    {
        try
        {
            var res = JwtHelper.Decode(token);
            return new WebMsg(res);
        }
        catch (Exception ex)
        {
            nlogService.Error("登录解析失败:" + token);
            nlogService.Error(ex.Message);
            return new WebMsg() { 
                Msg = "登录解析失败:" + token,
                Su***ess = false,
            };
        }
    }
}

运行结果

{
  "code": 200,
  "su***ess": true,
  "data": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJVc2VySWQiOjI5MSwiRXhwaXJlVGltZSI6IjIwMjQtMDMtMTRUMTM6MjE6MzYuNjE3NDk0KzA4OjAwIn0.sxh9sM4gQoCfFfim-MQSsHqDQX3Dji3FZaEu4t06D1s",
  "msg": "操作成功!"
}
{
  "code": 200,
  "su***ess": true,
  "data": "{\"UserId\":291,\"ExpireTime\":\"2024-03-14T13:21:36.617494+08:00\"}",
  "msg": "操作成功!"
}

WebApi 授权,博客太老了,尝试失败

这里我们就用简单的授权,复杂的授权可以自己去看微软官方文档

ASP.*** Core 中的简单授权

然后我发现.*** core 8.0的认证方式好像变了,我按照博客的写法发现不让我重写了

这个方法是.*** framework上面用的,.*** core 用不了。我想还是算了,用.*** core 的方法好了


WebApi .*** core 8.0 最新版Jwt (微软官方集成)

我看了一天的博客,终于解决了JWT认证的问题。

在没有 ASP.*** CoreIdentity的情况下使用cookie身份验证

ASP.*** Core 6.0 添加 JWT 认证和授权

.*** Core WebApi集成JWT实现身份认证

重新新建一个Webapi

这里我就不多说了

.*** Core webapi 从零开始在IIS上面发布后端接口

我们还是要改一下的,因为之前的我测试的时候,发现Builder里面的代码实在是太多了,这里我们分开一下


using Microsoft.OpenApi.Models;
using System.Reflection;

namespace JtwTestWebApi
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var builder = WebApplication.CreateBuilder(args);
            var MyPolicy = "MyPolicy";
            // Add services to the container.

            builder.Services.AddControllers();
            // Learn more about configuring Swagger/OpenAPI at https://aka.ms/asp***core/swashbuckle
            builder.Services.AddEndpointsApiExplorer();
            builder.Services.AddSwaggerGen();
            //为了防止配套太多,代码混乱。这里自定义了一个方法
            AddMyService(builder);
            var app = builder.Build();

            // Configure the HTTP request pipeline.
            if (app.Environment.IsDevelopment())
            {
                
            }
            app.UseSwagger();
            app.UseSwaggerUI();
            app.UseStatusCodePagesWithRedirects("/swagger/index.html");
            app.UseHttpsRedirection();

            app.UseAuthorization();


            app.MapControllers();

            app.Run();
        }

        /// <summary>
        /// 为了防止代码过于臃肿,将新的配置放在这里写
        /// </summary>
        /// <param name="builder"></param>
        public static void AddMyService(WebApplicationBuilder builder)
        {
            builder.Services.AddCors(options =>
            {
                options.AddPolicy("MyPolicy", policy =>
                {
                    policy.AllowAnyHeader().AllowAnyOrigin().AllowAnyMethod();
                });
            });
            builder.Services.AddSwaggerGen(options =>
            {
                options.SwaggerDoc("v1", new OpenApiInfo
                {
                    Version = "v1",
                    Title = "API标题",
                    Description = $"API描述,v1版本"
                });
                var xmlFilename = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
                //IncludeXml***ments 第二参数 true 则显示 控制器 注释
                options.IncludeXml***ments(Path.***bine(AppContext.BaseDirectory, xmlFilename), true);
            });
        }
    }
}

简单Controller

public class WebMsg
{

    public object Data { get; set; }

    public bool Su***ess { get; set; } = true;

    public string Msg { get; set; } = "操作成功!";

    public WebMsg()
    {

    }

    public WebMsg(object data)
    {
        Data = data;
    }
}
/// <summary>
/// 测试控制器
/// </summary>
[Route("api/[controller]/[action]")]
[ApiController]
public class TestController : ControllerBase
{
    /// <summary>
    /// 测试返回值
    /// </summary>
    /// <returns></returns>
    [HttpGet]
    public WebMsg GetTest()
    {
        return new WebMsg("测试返回值");
    }

}

最简单的Jwt认证

我们知道Jwt其实就是生成和验证两个功能。微软把这个功能集成到一个JwtBearer库里面了。


为了方便Json打印,添加个Newtonsoft

获取JwtConfig

我们在appsetting.json里面添加

  "JwtConfig": {
    "SecretKey": "123123123123", // 密钥   可以是guid 也可以是随便一个字符串
    "Issuer": "XiaoWang", // 颁发者
    "Audience": "XiaoWang", // 接收者
    "Expired": 30 // 过期时间(30min)
  }
 public class JwtConfig
 {
     /// <summary>
     /// 密钥
     /// </summary>
     public string SecretKey { get; set; }   

     /// <summary>
     /// 发布者
     /// </summary>
     public string Issuer { get; set; }

     /// <summary>
     /// 接受者
     /// </summary>
     public string Audience { get; set; }

     /// <summary>
     /// 过期时间(min)
     /// </summary>
     public int Expired { get; set; }
 }

获取Config

var jwtConfig = new JwtConfig();
builder.Configuration.Bind("JwtConfig",jwtConfig);


新建JwtHelper类

 public class JwtHelper
 {

     public JwtConfig JwtConfig { get; set; }
     public JwtHelper()
     {

     }

     /// <summary>
     /// 添加Jwt服务
     /// </summary>
     public void AddJwtService()
     {

     }

     /// <summary>
     /// 返回
     /// </summary>
     /// <returns></returns>
     public string GetJwtToken()
     {
         //简单测试一下
         var token = JsonConvert.SerializeObject(JwtConfig);
         return token;
     }


 }

我们应该使用Ioc的方式注入JwtHelper

var jwtConfig = new JwtConfig();
builder.Configuration.Bind("JwtConfig",jwtConfig);
var jwtHelper = new JwtHelper() {
    JwtConfig = jwtConfig
};
//将JwtHelper添加到Services里面
builder.Services.AddSingleton<JwtHelper>(jwtHelper);

然后在控制器里面获取Token

/// <summary>
/// 测试控制器
/// </summary>
[Route("api/[controller]/[action]")]
[ApiController]
public class TestController : ControllerBase
{
    private JwtHelper jwtHelper;

    /// <summary>
    /// 通过Ioc得到Jwt
    /// </summary>
    /// <param name="jwtHelper"></param>
    public TestController(JwtHelper jwtHelper) {
        this.jwtHelper = jwtHelper;
    }
    /// <summary>
    /// 测试返回值
    /// </summary>
    /// <returns></returns>
    [HttpGet]
    public WebMsg GetTest()
    {
        return new WebMsg("测试返回值");
    }


    /// <summary>
    /// 获取JwtToken
    /// </summary>
    /// <returns></returns>
    [HttpGet]
    public WebMsg GetJwtToken()
    {
        var token = jwtHelper.GetJwtToken();
        return new WebMsg(token);
    }
}


接下来我们修改一下GetJwtToken这个函数即可

完善JwtConfig

using Microsoft.IdentityModel.Tokens;
using System.Text;

namespace JtwTestWebApi.Models
{
    public class JwtConfig
    {
        /// <summary>
        /// 密钥
        /// </summary>
        public string SecretKey { get; set; }

        /// <summary>
        /// 发布者
        /// </summary>
        public string Issuer { get; set; }

        /// <summary>
        /// 接受者
        /// </summary>
        public string Audience { get; set; }

        /// <summary>
        /// 过期时间(min)
        /// </summary>
        public int Expired { get; set; }

        /// <summary>
        /// 生效时间
        /// </summary>
        public DateTime NotBefore => DateTime.Now;

        /// <summary>
        /// 过期时间
        /// </summary>
        public DateTime Expiration => DateTime.Now.AddMinutes(Expired);

        /// <summary>
        /// 密钥Bytes
        /// </summary>
        private SecurityKey SigningKey => new SymmetricSecurityKey(Encoding.UTF8.GetBytes(SecretKey));

        /// <summary>
        /// 加密后的密钥,使用HmacSha256加密
        /// </summary>
        public SigningCredentials SigningCredentials =>
            new SigningCredentials(SigningKey, SecurityAlgorithms.HmacSha256);
    }
}

在JwtHelper里面使用

/// <summary>
/// 最简单的JwtToken
/// </summary>
/// <returns></returns>
public string GetJwtToken()
{
    var claims = new List<Claim>();
    var jwtSecurityToken = new JwtSecurityToken(
            JwtConfig.Issuer,
            JwtConfig.Audience,
            claims,
            JwtConfig.NotBefore,
            JwtConfig.Expiration,
            JwtConfig.SigningCredentials
        );
    var token = new JwtSecurityTokenHandler().WriteToken( jwtSecurityToken );
    return token;
}
       

运行报错,说明密钥的长度太短了,我们加长一下


返回成功!

eyJhbGciOiJIUzI1NiIsInR5***I6IkpXVCJ9.eyJuYmYiOjE3MTAzODUyODgsImV4***I6MTcxMDM4NzA4OCwiaXNzIjoiWGlhb1dhbmciLCJhdWQiOiJYaWFvV2FuZyJ9.v7UbQOba7VoNgoiRsoIQkFJKQTFBMLJlYEKBIfdFV4o

授权

加密了,肯定要有对应的授权

在JwtConfig里面添加对应的授权

public class JwtConfig
{
    /// <summary>
    /// 密钥
    /// </summary>
    public string SecretKey { get; set; }

    /// <summary>
    /// 发布者
    /// </summary>
    public string Issuer { get; set; }

    /// <summary>
    /// 接受者
    /// </summary>
    public string Audience { get; set; }

    /// <summary>
    /// 过期时间(min)
    /// </summary>
    public int Expired { get; set; }

    /// <summary>
    /// 生效时间
    /// </summary>
    public DateTime NotBefore => DateTime.Now;

    /// <summary>
    /// 过期时间
    /// </summary>
    public DateTime Expiration => DateTime.Now.AddMinutes(Expired);

    /// <summary>
    /// 密钥Bytes
    /// </summary>
    private SecurityKey SigningKey => new SymmetricSecurityKey(Encoding.UTF8.GetBytes(SecretKey));

    /// <summary>
    /// 加密后的密钥,使用HmacSha256加密
    /// </summary>
    public SigningCredentials SigningCredentials =>
        new SigningCredentials(SigningKey, SecurityAlgorithms.HmacSha256);

    /// <summary>
    /// 认证用的密钥
    /// </summary>
    public SymmetricSecurityKey SymmetricSecurityKey => new SymmetricSecurityKey(Encoding.UTF8.GetBytes(SecretKey));
}

在JwtHelper中

/// <summary>
/// 添加Jwt服务
/// </summary>
public void AddJwtService(IServiceCollection services)
{
    services.AddAuthentication(option =>
    {
        //认证middleware配置
        option.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
        option.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
    })
    .AddJwtBearer(options =>
    {
        options.TokenValidationParameters = new TokenValidationParameters
        {
            //Token颁发机构
            ValidIssuer = JwtConfig.Issuer,
            //颁发给谁
            ValidAudience = JwtConfig.Audience,
            //这里的key要进行加密
            IssuerSigningKey = JwtConfig.SymmetricSecurityKey,
            //是否验证Token有效期,使用当前时间与Token的Claims中的NotBefore和Expires对比
            ValidateLifetime = true,
        };
    });
}

调用JwtHelper

  var jwtConfig = new JwtConfig();
  builder.Configuration.Bind("JwtConfig",jwtConfig);
  var jwtHelper = new JwtHelper() {
      JwtConfig = jwtConfig
  };
  //将JwtHelper添加到Services里面
  builder.Services.AddSingleton<JwtHelper>(jwtHelper);
  jwtHelper.AddJwtService(builder.Services);

在app中启用

            app.UseHttpsRedirection();
            app.UseAuthentication();//要在授权之前认证,这个和[Authorize]特性有关
            app.UseAuthorization();

一定要在za之前使用ca

授权测试

我们接口最好单独开一个获取Token的接口

TestController

using JtwTestWebApi.Models;
using JtwTestWebApi.Utils;
using Microsoft.Asp***Core.Authorization;
using Microsoft.Asp***Core.Http;
using Microsoft.Asp***Core.Mvc;

namespace JtwTestWebApi.Controllers
{
    /// <summary>
    /// 测试控制器
    /// </summary>
    [Route("api/[controller]/[action]")]
    [ApiController]
    public class TestController : ControllerBase
    {
        private JwtHelper jwtHelper;

        /// <summary>
        /// 通过Ioc得到Jwt
        /// </summary>
        /// <param name="jwtHelper"></param>
        public TestController(JwtHelper jwtHelper) {
            this.jwtHelper = jwtHelper;
        }
        /// <summary>
        /// 测试返回值
        /// </summary>
        /// <returns></returns>
        [HttpGet]
        public WebMsg GetTest()
        {
            return new WebMsg("测试返回值");
        }


        /// <summary>
        /// 获取JwtToken
        /// </summary>
        /// <returns></returns>
        [HttpGet]
        public WebMsg GetJwtToken()
        {
            var token = jwtHelper.GetJwtToken();
            return new WebMsg(token);
        }

        /// <summary>
        /// 可以在方法前面加Authorize
        /// </summary>
        /// <returns></returns>
        [Authorize]
        [HttpGet]
        public WebMsg GetByJwt()
        {
            return new WebMsg("Jwt测试成功!");
        }
    }
}

JtwTestWebApi

如果我们在类前面添加了[Authorize],下面全部的接口都有Jwt认证。想放开Jtw认证,使用[AllowAnonymous]标记方法即可

using JtwTestWebApi.Models;
using Microsoft.Asp***Core.Authorization;
using Microsoft.Asp***Core.Http;
using Microsoft.Asp***Core.Mvc;

namespace JtwTestWebApi.Controllers
{
    /// <summary>
    /// Jwt测试类
    /// </summary>
    [Route("api/[controller]/[action]")]
    [ApiController]
    [Authorize]
    public class JwtTestController : ControllerBase
    {
        /// <summary>
        /// 不需要Jwt
        /// </summary>
        /// <returns></returns>
        [AllowAnonymous]
        [HttpGet]
        public WebMsg NoJwtGet()
        {
            return new WebMsg("我不需要Jwt");
        }


        /// <summary>
        /// 需要Jwt
        /// </summary>
        /// <returns></returns>
        [HttpGet]
        public WebMsg JwtGet()
        {
            return new WebMsg("我需要Jwt");
        }
    }
}

认证失败的结果

能通过Jwt的请求

既然我们拦截了请求,我们也得对符合规范的请求放行。那什么样的请求能放行呢?在Header中的【Authorization】中添加【Bearer {token}】才能放行。

PostMan测试


测试成功!

过期测试

.***Core JWT token过期时间设置

Jwt的过期是由两个数据综合相加的。一个是生成Token的过期时间


一个是缓冲时间,默认5分钟。因为服务器的时间可能不同步。


所以总过期时间=过期时间+缓冲时间


为了更简单的获取Token,我们直接把返回的Token自带[Bearer ]这个前缀好了

Swagger 全局Header

在JwtHelper中添加全局静态方法

/// <summary>
/// Swagger添加Jwt功能
/// </summary>
/// <param name="options"></param>
public static void SwaggerAddJwtHeader(SwaggerGenOptions options)
{
    options.AddSecurityRequirement(new OpenApiSecurityRequirement()
        {
            {
                new OpenApiSecurityScheme
                {
                    Reference = new OpenApiReference {
                        Type = ReferenceType.SecurityScheme,
                        Id = "Bearer",
                    }
                },
            new string[] { }
            }
        });
    options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
    {
        Description = "JWT授权(数据将在请求头中进行传输) 在下方输入Bearer {token} 即可,注意两者之间有空格",
        Name = "Authorization",//jwt默认的参数名称
        In = ParameterLocation.Header,//jwt默认存放Authorization信息的位置(请求头中)
        Type = SecuritySchemeType.ApiKey,
        BearerFormat = "JWT",
        Scheme = "Bearer"
    });
}





获取Jwt中的信息

因为我们的Jwt是自带【Bearer 】这个请求头的,所以去掉前面的头,里面其实是可以解密的

 /// <summary>
 /// 获取Jwt的信息
 /// </summary>
 /// <param name="request"></param>
 /// <returns></returns>
 public IEnumerable<Claim> Decode(HttpRequest request)
 {
     
     var authorization = request.Headers["Authorization"].ToString();
     //因为我们的Jwt是自带【Bearer 】这个请求头的,所以去掉前面的头
     var auth = authorization.Split(" ")[1];
     var handler = new JwtSecurityTokenHandler();
     //反解密,获取其中的Claims
     var payload = handler.ReadJwtToken(auth).Payload;
     var claims = payload.Claims;
     return claims;
 }

解密能拿到是因为我们加密的时候就已经放进去了

 /// <summary>
 /// 添加claims信息
 /// </summary>
 /// <param name="claims"></param>
 /// <returns></returns>
 public string GetJwtToken(List<Claim> claims)
 {
     var jwtSecurityToken = new JwtSecurityToken(
             JwtConfig.Issuer,
             JwtConfig.Audience,
             claims,
             JwtConfig.NotBefore,
             JwtConfig.Expiration,
             JwtConfig.SigningCredentials
         );
     var token = new JwtSecurityTokenHandler().WriteToken(jwtSecurityToken);
     token = "Bearer " + token;
     return token;
 }
 /// <summary>
 /// 获取JwtToken
 /// </summary>
 /// <returns></returns>
 [HttpGet]
 public WebMsg GetJwtToken2()
 {
 	//我们加密的时候,就放进去了一些额外的信息
     var token = jwtHelper.GetJwtToken(new List<Claim>()
     {
         new Claim("UserId","2"),
         new Claim("UserName","3")
     });
     return new WebMsg(token);
 }
/// <summary>
/// 可以在方法前面加Authorize
/// </summary>
/// <returns></returns>
[Authorize]
[HttpGet]
public WebMsg GetByJwt()
{
	//获取解密后的Claim
    var dic = jwtHelper.Decode(this.Request);
    return new WebMsg("Jwt测试成功!");
}


这样我们就可以把一些比较隐私的数据放在里面,比如用户Id,这样防止泄漏。

简单封装一下

using JtwTestWebApi.Models;
using Microsoft.Asp***Core.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using Microsoft.OpenApi.Models;
using Newtonsoft.Json;
using Swashbuckle.Asp***Core.SwaggerGen;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;

namespace JtwTestWebApi.Utils
{
    public class JwtHelper
    {

        public JwtConfig JwtConfig { get; set; }
        public JwtHelper()
        {

        }

        /// <summary>
        /// 添加Jwt服务
        /// </summary>
        public void AddJwtService(IServiceCollection services)
        {
            services.AddAuthentication(option =>
            {
                //认证middleware配置
                option.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                option.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
            })
            .AddJwtBearer(options =>
            {
                options.TokenValidationParameters = new TokenValidationParameters
                {
                    //Token颁发机构
                    ValidIssuer = JwtConfig.Issuer,
                    //颁发给谁
                    ValidAudience = JwtConfig.Audience,
                    //这里的key要进行加密
                    IssuerSigningKey = JwtConfig.SymmetricSecurityKey,
                    //是否验证Token有效期,使用当前时间与Token的Claims中的NotBefore和Expires对比
                    ValidateLifetime = true,
                    ValidateIssuer = true,
                    ValidateAudience = true,
                    ValidateIssuerSigningKey = true,
                    RequireExpirationTime = true,
                };
            });
        }

        /// <summary>
        /// 最简单的JwtToken
        /// </summary>
        /// <returns></returns>
        public string GetJwtToken()
        {
            var claims = new List<Claim>();
            var jwtSecurityToken = new JwtSecurityToken(
                    JwtConfig.Issuer,
                    JwtConfig.Audience,
                    claims,
                    JwtConfig.NotBefore,
                    JwtConfig.Expiration,
                    JwtConfig.SigningCredentials
                );
            var token = new JwtSecurityTokenHandler().WriteToken(jwtSecurityToken);
            token = "Bearer " + token;
            return token;
        }

        /// <summary>
        /// 添加claims信息
        /// </summary>
        /// <param name="claims"></param>
        /// <returns></returns>
        public string GetJwtToken(List<Claim> claims)
        {
            var jwtSecurityToken = new JwtSecurityToken(
                    JwtConfig.Issuer,
                    JwtConfig.Audience,
                    claims,
                    JwtConfig.NotBefore,
                    JwtConfig.Expiration,
                    JwtConfig.SigningCredentials
                );
            var token = new JwtSecurityTokenHandler().WriteToken(jwtSecurityToken);
            token = "Bearer " + token;
            return token;
        }

        /// <summary>
        /// UserModel类Token
        /// </summary>
        /// <param name="user"></param>
        /// <returns></returns>
        public string GetJwtToken(JwtUserModel user)
        {
            var claims = new List<Claim>() {
                new Claim("UserId",user.UserId.ToString()),
                new Claim("UserName",user.UserName),
                new Claim("UserType",user.UserType.ToString()),
            };
            return GetJwtToken(claims);
        }

        /// <summary>
        /// Swagger添加Jwt功能
        /// </summary>
        /// <param name="options"></param>
        public static void SwaggerAddJwtHeader(SwaggerGenOptions options)
        {
            options.AddSecurityRequirement(new OpenApiSecurityRequirement()
                {
                    {
                        new OpenApiSecurityScheme
                        {
                            Reference = new OpenApiReference {
                                Type = ReferenceType.SecurityScheme,
                                Id = "Bearer",
                            }
                        },
                    new string[] { }
                    }
                });
            options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
            {
                Description = "JWT授权(数据将在请求头中进行传输) 在下方输入Bearer {token} 即可,注意两者之间有空格",
                Name = "Authorization",//jwt默认的参数名称
                In = ParameterLocation.Header,//jwt默认存放Authorization信息的位置(请求头中)
                Type = SecuritySchemeType.ApiKey,
                BearerFormat = "JWT",
                Scheme = "Bearer"
            });
        }

        /// <summary>
        /// 获取Jwt的信息
        /// </summary>
        /// <param name="request"></param>
        /// <returns></returns>
        public IEnumerable<Claim> Decode(HttpRequest request)
        {

            var authorization = request.Headers["Authorization"].ToString();
            //因为我们的Jwt是自带【Bearer 】这个请求头的,所以去掉前面的头
            var auth = authorization.Split(" ")[1];
            var handler = new JwtSecurityTokenHandler();
            //反解密,获取其中的Claims
            var payload = handler.ReadJwtToken(auth).Payload;
            var claims = payload.Claims;
            return claims;
        }

        /// <summary>
        /// 解析得到User
        /// </summary>
        /// <param name="request"></param>
        /// <returns></returns>
        public JwtUserModel DecodeToUser(HttpRequest request)
        {
            var claims = Decode(request);
            var user = new JwtUserModel()
            {
                UserId = claims.Where(t => t.Type == "UserId").First().Value,
                UserName = claims.Where(t => t.Type == "UserName").First().Value,
                UserType = claims.Where(t => t.Type == "UserType").First().Value
            };
            return user;
        }
    }
}

[HttpGet]
public WebMsg GetJwtToken3()
{
    var token = jwtHelper.GetJwtToken(new JwtUserModel()
    {
        UserName = "小王",
        UserId = "32",
        UserType = "admin"
    });
    return new WebMsg(token);
}
[Authorize]
[HttpGet]
public WebMsg GetByJwt3()
{
    var dic = jwtHelper.DecodeToUser(this.Request);

    return new WebMsg("Jwt测试成功!");
}

运行得到结果

Jwt授权模式基础讲解

介绍三种授权方式(Policy、Role、Scheme),Scheme这种用的太少了,我们就简单讲解一下Policy和Role这两种方式。Policy和Role的核心就在于我们封装Token的时候添加的Claim对象。大家看到这里就会发现,其实Jwt的核心就是装包和拆包。网页请求Token的时候拿到了个包裹,网页发送Http的时候解开这个包裹。

我写了一会,感觉有点累了。这里有个别人写好的代码,有兴趣的自己去看吧。

ASP.*** Core 6.0 添加 JWT 认证和授权

简单的【角色授权】

获取不同权限的Token

获取不同权限的Token

/// <summary>
/// 获取Role = admin的Token
/// </summary>
/// <returns></returns>
[HttpGet]
public WebMsg GetJwtToken_Admin()
{
    var token = jwtHelper.GetJwtToken(new List<Claim>()
    {
        new Claim(ClaimTypes.Role,"admin")
    });

    return new WebMsg(token);
}
/// <summary>
/// 获取Role = user
/// </summary>
/// <returns></returns>
[HttpGet]
public WebMsg GetJwtToken_User()
{
    var token = jwtHelper.GetJwtToken(new List<Claim>()
    {
        new Claim(ClaimTypes.Role,"user")
    });

    return new WebMsg(token);
}

/// <summary>
/// 获取Role = admin和user
/// </summary>
/// <returns></returns>
[HttpGet]
public WebMsg GetJwtToken_UserAndAdmin()
{
    var token = jwtHelper.GetJwtToken(new List<Claim>()
    {
        new Claim(ClaimTypes.Role,"user"),
        new Claim(ClaimTypes.Role,"admin")
    });

    return new WebMsg(token);
}
不同权限的jwt认证
/// <summary>
/// 需要role=admin
/// </summary>
/// <returns></returns>
[HttpGet]
[Authorize(Roles = "admin")]
public WebMsg AdminGet()
{
    return new WebMsg("admin");
}
/// <summary>
/// role=user
/// </summary>
/// <returns></returns>
[HttpGet]
[Authorize(Roles = "user")]
public WebMsg UserGet()
{
    return new WebMsg("user");
}

/// <summary>
/// role = admin或user
/// </summary>
/// <returns></returns>
[HttpGet]
[Authorize(Roles = "admin,user")]
public WebMsg AdminOrUserGet()
{
    return new WebMsg("admin or user");
}
/// <summary>
/// role=admin和user
/// </summary>
/// <returns></returns>
[HttpGet]
[Authorize(Roles = "admin")]
[Authorize(Roles = "user")]
public WebMsg AdminAndUserGet()
{
    return new WebMsg("admin and user");
}

这里推荐使用enum枚举类型,这样不会出现拼写错误

封装好的代码

using Microsoft.IdentityModel.Tokens;
using System.Text;

namespace JtwTestWebApi.Models
{
    public class JwtConfig
    {
        /// <summary>
        /// 密钥
        /// </summary>
        public string SecretKey { get; set; }

        /// <summary>
        /// 发布者
        /// </summary>
        public string Issuer { get; set; }

        /// <summary>
        /// 接受者
        /// </summary>
        public string Audience { get; set; }

        /// <summary>
        /// 过期时间(min)
        /// </summary>
        public int Expired { get; set; }

        /// <summary>
        /// 生效时间
        /// </summary>
        public DateTime NotBefore => DateTime.Now;

        /// <summary>
        /// 过期时间
        /// </summary>
        public DateTime Expiration => DateTime.Now.AddMinutes(Expired);

        /// <summary>
        /// 密钥Bytes
        /// </summary>
        private SecurityKey SigningKey => new SymmetricSecurityKey(Encoding.UTF8.GetBytes(SecretKey));

        /// <summary>
        /// 加密后的密钥,使用HmacSha256加密
        /// </summary>
        public SigningCredentials SigningCredentials =>
            new SigningCredentials(SigningKey, SecurityAlgorithms.HmacSha256);

        /// <summary>
        /// 认证用的密钥
        /// </summary>
        public SymmetricSecurityKey SymmetricSecurityKey => new SymmetricSecurityKey(Encoding.UTF8.GetBytes(SecretKey));



    }
}

using JtwTestWebApi.Models;
using Microsoft.Asp***Core.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using Microsoft.OpenApi.Models;
using Newtonsoft.Json;
using Swashbuckle.Asp***Core.SwaggerGen;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;

namespace JtwTestWebApi.Utils
{
    public class JwtHelper
    {

        public JwtConfig JwtConfig { get; set; }
        public JwtHelper()
        {

        }

        /// <summary>
        /// 添加Jwt服务
        /// </summary>
        public void AddJwtService(IServiceCollection services)
        {
            services.AddAuthentication(option =>
            {
                //认证middleware配置
                option.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                option.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
            })
            .AddJwtBearer(options =>
            {
                options.TokenValidationParameters = new TokenValidationParameters
                {
                    //Token颁发机构
                    ValidIssuer = JwtConfig.Issuer,
                    //颁发给谁
                    ValidAudience = JwtConfig.Audience,
                    //这里的key要进行加密
                    IssuerSigningKey = JwtConfig.SymmetricSecurityKey,
                    //是否验证Token有效期,使用当前时间与Token的Claims中的NotBefore和Expires对比
                    ValidateLifetime = true,
                    ValidateIssuer = true,
                    ValidateAudience = true,
                    ValidateIssuerSigningKey = true,
                    RequireExpirationTime = true,
                };
            });
        }

        /// <summary>
        /// 最简单的JwtToken
        /// </summary>
        /// <returns></returns>
        public string GetJwtToken()
        {
            var claims = new List<Claim>();
            var jwtSecurityToken = new JwtSecurityToken(
                    JwtConfig.Issuer,
                    JwtConfig.Audience,
                    claims,
                    JwtConfig.NotBefore,
                    JwtConfig.Expiration,
                    JwtConfig.SigningCredentials
                );
            var token = new JwtSecurityTokenHandler().WriteToken(jwtSecurityToken);
            token = "Bearer " + token;
            return token;
        }

        /// <summary>
        /// 添加claims信息
        /// </summary>
        /// <param name="claims"></param>
        /// <returns></returns>
        public string GetJwtToken(List<Claim> claims)
        {
            var jwtSecurityToken = new JwtSecurityToken(
                    JwtConfig.Issuer,
                    JwtConfig.Audience,
                    claims,
                    JwtConfig.NotBefore,
                    JwtConfig.Expiration,
                    JwtConfig.SigningCredentials
                );
            var token = new JwtSecurityTokenHandler().WriteToken(jwtSecurityToken);
            token = "Bearer " + token;
            return token;
        }

        /// <summary>
        /// UserModel类Token
        /// </summary>
        /// <param name="user"></param>
        /// <returns></returns>
        public string GetJwtToken(JwtUserModel user)
        {
            var claims = new List<Claim>() {
                new Claim("UserId",user.UserId.ToString()),
                new Claim("UserName",user.UserName),
                new Claim("UserType",user.UserType.ToString()),
            };
            return GetJwtToken(claims);
        }

        /// <summary>
        /// Swagger添加Jwt功能
        /// </summary>
        /// <param name="options"></param>
        public static void SwaggerAddJwtHeader(SwaggerGenOptions options)
        {
            options.AddSecurityRequirement(new OpenApiSecurityRequirement()
                {
                    {
                        new OpenApiSecurityScheme
                        {
                            Reference = new OpenApiReference {
                                Type = ReferenceType.SecurityScheme,
                                Id = "Bearer",
                            }
                        },
                    new string[] { }
                    }
                });
            options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
            {
                Description = "JWT授权(数据将在请求头中进行传输) 在下方输入Bearer {token} 即可,注意两者之间有空格",
                Name = "Authorization",//jwt默认的参数名称
                In = ParameterLocation.Header,//jwt默认存放Authorization信息的位置(请求头中)
                Type = SecuritySchemeType.ApiKey,
                BearerFormat = "JWT",
                Scheme = "Bearer"
            });
        }

        /// <summary>
        /// 获取Jwt的信息
        /// </summary>
        /// <param name="request"></param>
        /// <returns></returns>
        public IEnumerable<Claim> Decode(HttpRequest request)
        {

            var authorization = request.Headers["Authorization"].ToString();
            //因为我们的Jwt是自带【Bearer 】这个请求头的,所以去掉前面的头
            var auth = authorization.Split(" ")[1];
            var handler = new JwtSecurityTokenHandler();
            //反解密,获取其中的Claims
            var payload = handler.ReadJwtToken(auth).Payload;
            var claims = payload.Claims;
            return claims;
        }

        /// <summary>
        /// 解析得到User
        /// </summary>
        /// <param name="request"></param>
        /// <returns></returns>
        public JwtUserModel DecodeToUser(HttpRequest request)
        {
            var claims = Decode(request);
            var user = new JwtUserModel()
            {
                UserId = claims.Where(t => t.Type == "UserId").First().Value,
                UserName = claims.Where(t => t.Type == "UserName").First().Value,
                UserType = claims.Where(t => t.Type == "UserType").First().Value
            };
            return user;
        }
    }
}

namespace JtwTestWebApi.Models
{
    public class JwtUserModel
    {

        public string UserId { get; set; }

        public string UserName { get; set; }

        public string UserType { get; set; }
    }
}


using JtwTestWebApi.Models;
using JtwTestWebApi.Utils;
using Microsoft.Extensions.Configuration;
using Microsoft.OpenApi.Models;
using System.IdentityModel.Tokens.Jwt;
using System.Reflection;

namespace JtwTestWebApi
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var builder = WebApplication.CreateBuilder(args);
            var MyPolicy = "MyPolicy";
            // Add services to the container.

            builder.Services.AddControllers();
            // Learn more about configuring Swagger/OpenAPI at https://aka.ms/asp***core/swashbuckle
            builder.Services.AddEndpointsApiExplorer();
            builder.Services.AddSwaggerGen();
            //为了防止配套太多,代码混乱。这里自定义了一个方法
            AddMyService(builder);
            var app = builder.Build();

            // Configure the HTTP request pipeline.
            if (app.Environment.IsDevelopment())
            {
                
            }
            app.UseSwagger();
            app.UseSwaggerUI();
            app.UseStatusCodePagesWithRedirects("/swagger/index.html");
            app.UseHttpsRedirection();
            app.UseAuthentication();//要在授权之前认证,这个和[Authorize]特性有关
            app.UseAuthorization();


            app.MapControllers();

            app.Run();
        }

        /// <summary>
        /// 为了防止代码过于臃肿,将新的配置放在这里写
        /// </summary>
        /// <param name="builder"></param>
        public static void AddMyService(WebApplicationBuilder builder)
        {
            #region 默认的Webapi配置
            builder.Services.AddCors(options =>
            {
                options.AddPolicy("MyPolicy", policy =>
                {
                    policy.AllowAnyHeader().AllowAnyOrigin().AllowAnyMethod();
                });
            });
            builder.Services.AddSwaggerGen(options =>
            {
                options.SwaggerDoc("v1", new OpenApiInfo
                {
                    Version = "v1",
                    Title = "API标题",
                    Description = $"API描述,v1版本"
                });
                var xmlFilename = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
                //IncludeXml***ments 第二参数 true 则显示 控制器 注释
                options.IncludeXml***ments(Path.***bine(AppContext.BaseDirectory, xmlFilename), true);
                JwtHelper.SwaggerAddJwtHeader(options);
            });
            #endregion 

            #region 添加Jwt服务
            var jwtConfig = new JwtConfig();
            builder.Configuration.Bind("JwtConfig",jwtConfig);
            var jwtHelper = new JwtHelper() {
                JwtConfig = jwtConfig
            };
            //将JwtHelper添加到Services里面
            builder.Services.AddSingleton<JwtHelper>(jwtHelper);
            jwtHelper.AddJwtService(builder.Services);
            #endregion

        }
    }
}

appsettings.json中添加

  "JwtConfig": {
    "SecretKey": "lisheng741@qq.***lisheng741@qq.***",
    "Issuer": "WebAppIssuer",
    "Audience": "WebAppAudience",
    "Expired": 30 // 过期时间(30min)
  }

总结

Jwt其实也不是特别难,就是第一次配置的时候容易被绕晕。Jwt的策略我暂时先跳过了,对于解决普通问题一般来说已经够用了。

转载请说明出处内容投诉
CSS教程_站长资源网 » ASP.NET Core 8.0 WebApi 从零开始学习JWT登录认证

发表评论

欢迎 访客 发表评论

一个令你着迷的主题!

查看演示 官网购买