HTTP Bearer认证及JWT的使用


一、概述

1、理解Http的无状态特性

HTTP是一个无状态的协议,WEB服务器在处理所有传入HTTP请求时,根本就不知道某个请求是否是一个用户的第一次请求与后续请求,或者是另一个用户的请求。 WEB服务器每次在处理请求时,都会按照用户所访问的资源所对应的处理代码,从头到尾执行一遍,然后输出响应内容,WEB服务器根本不会记住已处理了哪些用户的请求,因此,我们通常说HTTP协议是无状态的。

2、为什么需要认证

虽然HTTP协议与WEB服务器是无状态,但我们的业务需求却要求有状态,典型的就是用户登录, 在这种业务需求中,要求WEB服务器端能区分某个请求是不是一个已登录用户发起的,或者当前请求是哪个用户发出的。 在开发WEB应用程序时,我们通常会使用Cookie来保存一些简单的数据供服务端维持必要的状态。总的来说,加入认证的根本原因就是确保请求的合法性以及资源的安全性,如下图:

二、HTTP Bearer认证

http认证根据凭证协议的不同,划分为不同的方式。常用的方式有:

  • HTTP基本认证
  • HTTP摘要认证
  • HTTP Bearer认证

本篇文章介绍HTTP Bearer认证。

1、原理解析

下面通过图详细的了解下HTTP Bearer认证过程:

Bearer认证也是http协议中的标准认证方式,在Bearer认证中的凭证称为Bearer_token 或者Access_token。该种方式的优点就是灵活方便,因为凭证的生成和验证完全由开发人员设计和实现。一般凭证的设计尽量能保证以下几点:
  • 用户信息安全性,即保证重要信息不被泄露和恶意破解

  • 避免重放攻击

  • 更方便的适应更多的应用场景,主要体现在在服务端不需要凭证状态保存,分布式场景中,不需要状态共享

  • 少查库,减少服务端负担。

目前最流行的token编码协议就是JWT(JSON WEB TOKEN),下面通过jwt协议说明。

二、JWT协议

1、什么是JWT

Json web token (JWT),是一种基于JSON的开放标准((RFC 7519)。

2、JWT协议优缺点

优点:
1、可以避免用户信息泄露。
2、payload中可以携带一些必要的非敏感信息,比如用户名、用户邮箱,供前端使用
3、服务端不用存放jwt的状态信息,减轻服务端压力,在分布式场景中更方便,不需要状态共享
4、通过时间戳的方式避免重放攻击,token的时效性尽量短
缺点:
1、可以使用jwt 提供的jti,即jwt的id,来避免重放攻击,但是jti的值需要存储,不管是存储在redis还是mysql,都是要损耗性能的。

3、JWT组成

   

 

JWT是由三段信息构成的,将这三段信息文本连接起来就构成了Jwt字符串。第一部分我们称它为头部(header),第二部分我们称其为载荷(payload),第三部分是签证(signature),下面是个JWT实例:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOiJBZG1pbiIsIkV4cGlyZSI6IjIwMjAtMDctMTEgMTY6NDc6MTYifQ.9ev6IGc1K3xvYaEfmMYeyFz5oHCM57fRGOvSZ-jvArw

(1)Header

 

 

 在这里,我们说明了这是一个JWT,并且我们所用的签名算法是HS256算法。对它也要进行Base64编码,之后的字符串就成了JWT的Header(头部):

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9

(2)Payload

 

 

 将上面的JSON对象进行[base64编码]可以得到下面的字符串。这个字符串我们将它称作JWT的Payload(载荷):

eyJpc3MiOiJKb2huIFd1IEpXVCIsImlhdCI6MTQ0MTU5MzUwMiwiZXhwIjoxNDQxNTk0NzIyLCJhdWQiOiJ3d3cuZXhhbXBsZS5jb20iLCJzdWIiOiJqcm9ja2V0QGV4YW1wbGUuY29tIiwiZnJvbV91c2VyIjoiQiIsInRhcmdldF91c2VyIjoiQSJ9

(3)signature

jwt的第三部分是一个签证信息,这个签证信息由三部分组成:

  • header (base64后的)
  • payload (base64后的)
  • secret

这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行secret组合加密,然后就构成了jwt的第三部分。

var encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload); 
var signature = HMACSHA256(encodedString, 'secret'); // TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

将这三部分用.连接成一个完整的字符串,构成了最终的jwt:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

注意:secret是保存在服务器端的,jwt的签发生成也是在服务器端的,secret就是用来进行jwt的签发和jwt的验证,所以,它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。

4、JWT签名的目的

最后一步签名的过程,实际上是对头部以及载荷内容进行签名。一般而言,加密算法对于不同的输入产生的输出总是不一样的。所以,如果有人对头部以及载荷的内容解码之后进行修改,再进行编码的话,那么新的头部和载荷的签名和之前的签名就将是不一样的。而且,如果不知道服务器加密的时候用的密钥的话,得出来的签名也一定会是不一样的。

服务器应用在接受到JWT后,会首先对头部和载荷的内容用同一算法再次签名。那么服务器应用是怎么知道我们用的是哪一种算法呢?别忘了,我们在JWT的头部中已经用alg字段指明了我们的加密算法了。如果服务器应用对头部和载荷再次以同样方法签名之后发现,自己计算出来的签名和接受到的签名不一样,那么就说明这个Token的内容被别人动过的,我们应该拒绝这个Token,返回一个HTTP 401 Unauthorized响应。

5、JWT安全性

使用JWT会暴露信息吗?是的。所以,在JWT中,不应该在载荷里面加入任何敏感的数据。在上面的例子中,我们传输的是用户的User ID。这个值实际上不是什么敏感内容,一般情况下被知道也是安全的。但是像密码这样的内容就不能被放在JWT中了。如果将用户的密码放在了JWT中,那么怀有恶意的第三方通过Base64解码就能很快地知道你的密码了。总结如下:

  • 不应该在jwt的payload部分存放敏感信息,因为该部分是客户端可解密的部分。
  • 保护好secret私钥,该私钥非常重要。
  • 如果可以,请使用https协议

6、JWT的适用场景

JWT适合用于向Web应用传递一些非敏感信息。其实JWT还经常用于设计用户认证和授权系统,甚至实现Web应用的单点登录。

三、基于JWT协议的Bearer认证示例

1、自定义CheckJWTAttribute特性方式

之前使用的是这种方式,根据jwt原理自定义生成JWT、验证jwt,感觉挺好。原理就是自定义一个拦截器(特性),拦截器对每个请求都优先进行处理,认证成功的进行下一步操作。

(1)新建netcore webapi项目

 

 

(2)定义JWTPayload类

using System;

namespace JwtAuthenticationOne
{
    public class JWTPayload
    {
        public string UserName { get; set; }
        public string Email { get; set; }
        public string UserId { get; set; }
        public DateTime Expire { get; set; }
    }
}

(3)定义CheckJWTAttribute特性

using Microsoft.AspNetCore.Mvc.Filters;
using System;
using System.Threading.Tasks;

namespace JwtAuthenticationOne
{
    /// <summary>
    /// JWT校检
    /// </summary>
    public class CheckJWTAttribute : BaseActionFilterAsync
    {
        private static readonly int _errorCode = 401;
        public override async Task OnActionExecuting(ActionExecutingContext context)
        {
            if (context.ContainsFilter<NoCheckJWTAttribute>()) return;
            try
            {
                var req = context.HttpContext.Request;
                string token = req.GetToken();
                if (token.IsNullOrEmpty())
                {
                    context.Result = Error("缺少token", _errorCode);
                    return;
                }
                if (!JWTHelper.CheckToken(token, JWTHelper.JWTSecret))
                {
                    context.Result = Error("token校检失败!", _errorCode);
                    return;
                }

                var payload = JWTHelper.GetPayload<JWTPayload>(token);
                if (payload.Expire < DateTime.Now)
                {
                    context.Result = Error("token过期!", _errorCode);
                    return;
                }
            }
            catch (Exception ex)
            {
                context.Result = Error(ex.Message, _errorCode);
            }
            await Task.CompletedTask;
        }
    }
}

(4)定义NoCheckJWTAttribute类

namespace JwtAuthenticationOne
{
    /// <summary>
    /// 忽略JWT校验
    /// </summary>
    public class NoCheckJWTAttribute : BaseActionFilterAsync
    {
    }
}

(5)定义BaseActionFilterAsync类

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using System;
using System.Threading.Tasks;

namespace JwtAuthenticationOne
{
    public class BaseActionFilterAsync : Attribute, IAsyncActionFilter
    {
        /// <summary>
        /// action执行之前执行
        /// </summary>
        /// <param name="context"></param>
        /// <returns></returns>
        public async virtual Task OnActionExecuting(ActionExecutingContext context)
        {
            await Task.CompletedTask;
        }
        /// <summary>
        /// action执行之后执行
        /// </summary>
        /// <param name="context"></param>
        /// <returns></returns>
        public async virtual Task OnActionExecuted(ActionExecutedContext context)
        {
            await Task.CompletedTask;
        }
        /// <summary>
        /// 在模型绑定完成后,在操作之前异步调用。
        /// </summary>
        /// <param name="context"></param>
        /// <param name="next"></param>
        /// <returns></returns>
        public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
        {
            await OnActionExecuting(context);
            if (context.Result == null)
            {
                var nextContext = await next();
                await OnActionExecuted(nextContext);
            }
        }

        /// <summary>
        /// 返回JSON
        /// </summary>
        /// <param name="json">json字符串</param>
        /// <returns></returns>
        public ContentResult JsonContent(string json)
        {
            return new ContentResult { Content = json, StatusCode = 200, ContentType = "application/json; charset=utf-8" };
        }

        /// <summary>
        /// 返回成功
        /// </summary>
        /// <returns></returns>
        public ContentResult Success()
        {
            AjaxResult res = new AjaxResult
            {
                Success = true,
                Msg = "请求成功!"
            };

            return JsonContent(res.ToJson());
        }

        /// <summary>
        /// 返回成功
        /// </summary>
        /// <param name="msg">消息</param>
        /// <returns></returns>
        public ContentResult Success(string msg)
        {
            AjaxResult res = new AjaxResult
            {
                Success = true,
                Msg = msg
            };

            return JsonContent(res.ToJson());
        }

        /// <summary>
        /// 返回成功
        /// </summary>
        /// <param name="data">返回的数据</param>
        /// <returns></returns>
        public ContentResult Success<T>(T data)
        {
            AjaxResult<T> res = new AjaxResult<T>
            {
                Success = true,
                Msg = "请求成功!",
                Data = data
            };

            return JsonContent(res.ToJson());
        }

        /// <summary>
        /// 返回错误
        /// </summary>
        /// <returns></returns>
        public ContentResult Error()
        {
            AjaxResult res = new AjaxResult
            {
                Success = false,
                Msg = "请求失败!"
            };

            return JsonContent(res.ToJson());
        }

        /// <summary>
        /// 返回错误
        /// </summary>
        /// <param name="msg">错误提示</param>
        /// <returns></returns>
        public ContentResult Error(string msg)
        {
            AjaxResult res = new AjaxResult
            {
                Success = false,
                Msg = msg,
            };

            return JsonContent(res.ToJson());
        }

        /// <summary>
        /// 返回错误
        /// </summary>
        /// <param name="msg">错误提示</param>
        /// <param name="errorCode">错误代码</param>
        /// <returns></returns>
        public ContentResult Error(string msg, int errorCode)
        {
            AjaxResult res = new AjaxResult
            {
                Success = false,
                Msg = msg,
                ErrorCode = errorCode
            };

            return JsonContent(res.ToJson());
        }
    }
}

(6)定义AjaxResult类

namespace JwtAuthenticationOne
{
    /// <summary>
    /// Ajax请求结果
    /// </summary>
    public class AjaxResult
    {
        /// <summary>
        /// 是否成功
        /// </summary>
        public bool Success { get; set; } = true;

        /// <summary>
        /// 错误代码
        /// </summary>
        public int ErrorCode { get; set; }

        /// <summary>
        /// 返回消息
        /// </summary>
        public string Msg { get; set; }
    }
    /// <summary>
    /// Ajax请求结果
    /// </summary>
    public class AjaxResult<T> : AjaxResult
    {
        /// <summary>
        /// 返回数据
        /// </summary>
        public T Data { get; set; }
    }
}

(7)定义扩展类Extention

using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Filters;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;

namespace JwtAuthenticationOne
{
    public static partial class Extention
    {
        /// <summary>
        /// 构造函数
        /// </summary>
        static Extention()
        {
            JsonSerializerSettings setting = new JsonSerializerSettings();
            JsonConvert.DefaultSettings = new Func<JsonSerializerSettings>(() =>
            {
                //日期类型默认格式化处理
                setting.DateFormatHandling = DateFormatHandling.MicrosoftDateFormat;
                setting.DateFormatString = "yyyy-MM-dd HH:mm:ss";
                return setting;
            });
        }
        /// <summary>
        /// 将对象序列化成Json字符串
        /// </summary>
        /// <param name="obj">需要序列化的对象</param>
        /// <returns></returns>
        public static string ToJson(this object obj)
        {
            return JsonConvert.SerializeObject(obj);
        }
        /// <summary>
        /// 是否拥有某过滤器
        /// </summary>
        /// <typeparam name="T">过滤器类型</typeparam>
        /// <param name="actionExecutingContext">上下文</param>
        /// <returns></returns>
        public static bool ContainsFilter<T>(this FilterContext actionExecutingContext)
        {
            return actionExecutingContext.Filters.Any(x => x.GetType() == typeof(T));
        }
        /// <summary>
        /// 获取Token
        /// </summary>
        /// <param name="req">请求</param>
        /// <returns></returns>
        public static string GetToken(this HttpRequest req)
        {
            string tokenHeader = req.Headers["Authorization"].ToString();
            if (tokenHeader.IsNullOrEmpty()) return null;
            string pattern = "^Bearer (.*?)$";
            if (!Regex.IsMatch(tokenHeader, pattern)) throw new Exception("token格式不对!格式为:Bearer {token}");
            string token = Regex.Match(tokenHeader, pattern).Groups[1]?.ToString();
            if (token.IsNullOrEmpty()) throw new Exception("token不能为空!");
            return token;
        }
        /// <summary>
        /// 判断是否为Null或者空
        /// </summary>
        /// <param name="obj">对象</param>
        /// <returns></returns>
        public static bool IsNullOrEmpty(this object obj)
        {
            if (obj == null)
                return true;
            else
            {
                string objStr = obj.ToString();
                return string.IsNullOrEmpty(objStr);
            }
        }
        /// <summary>
        /// Base64Url编码
        /// </summary>
        /// <param name="text">待编码的文本字符串</param>
        /// <returns>编码的文本字符串</returns>
        public static string Base64UrlEncode(this string text)
        {
            var plainTextBytes = Encoding.UTF8.GetBytes(text);
            var base64 = Convert.ToBase64String(plainTextBytes).Replace('+', '-').Replace('/', '_').TrimEnd('=');

            return base64;
        }
        /// <summary>
        /// Base64Url解码
        /// </summary>
        /// <param name="base64UrlStr">使用Base64Url编码后的字符串</param>
        /// <returns>解码后的内容</returns>
        public static string Base64UrlDecode(this string base64UrlStr)
        {
            base64UrlStr = base64UrlStr.Replace('-', '+').Replace('_', '/');
            switch (base64UrlStr.Length % 4)
            {
                case 2:
                    base64UrlStr += "==";
                    break;
                case 3:
                    base64UrlStr += "=";
                    break;
            }
            var bytes = Convert.FromBase64String(base64UrlStr);

            return Encoding.UTF8.GetString(bytes);
        }
        /// <summary>
        /// 将Json字符串转为JObject
        /// </summary>
        /// <param name="jsonStr">Json字符串</param>
        /// <returns></returns>
        public static JObject ToJObject(this string jsonStr)
        {
            return jsonStr == null ? JObject.Parse("{}") : JObject.Parse(jsonStr.Replace("&nbsp;", ""));
        }
        /// <summary>
        /// 将Json字符串反序列化为对象
        /// </summary>
        /// <typeparam name="T">对象类型</typeparam>
        /// <param name="jsonStr">Json字符串</param>
        /// <returns></returns>
        public static T ToObject<T>(this string jsonStr)
        {
            return JsonConvert.DeserializeObject<T>(jsonStr);
        }
        /// <summary>
        /// HMACSHA256算法
        /// </summary>
        /// <param name="text">内容</param>
        /// <param name="secret">密钥</param>
        /// <returns></returns>
        public static string ToHMACSHA256String(this string text, string secret)
        {
            secret = secret ?? "";
            byte[] keyByte = Encoding.UTF8.GetBytes(secret);
            byte[] messageBytes = Encoding.UTF8.GetBytes(text);
            using (var hmacsha256 = new HMACSHA256(keyByte))
            {
                byte[] hashmessage = hmacsha256.ComputeHash(messageBytes);
                return Convert.ToBase64String(hashmessage).Replace('+', '-').Replace('/', '_').TrimEnd('=');
            }
        }
    }
}

(8)定义JWTHelper类

using Newtonsoft.Json.Linq;

namespace JwtAuthenticationOne
{
    /// <summary>
    /// JWT帮助类
    /// </summary>
    public class JWTHelper
    {
        private static readonly string _headerBase64Url = "{\"alg\":\"HS256\",\"typ\":\"JWT\"}".Base64UrlEncode();
        public static readonly string JWTSecret = AppsettingsHelper.GetValue("JWTSecret");

        /// <summary>
        /// 生成Token
        /// </summary>
        /// <param name="payloadJsonStr">载荷,数据JSON字符串</param>
        /// <param name="secret">秘钥</param>
        /// <returns></returns>
        public static string GetToken(string payloadJsonStr, string secret)
        {
            string payloadBase64Url = payloadJsonStr.Base64UrlEncode();
            string sign = $"{_headerBase64Url}.{payloadBase64Url}".ToHMACSHA256String(secret);
            return $"{_headerBase64Url}.{payloadBase64Url}.{sign}";
        }

        /// <summary>
        /// 获取Token中的数据
        /// </summary>
        /// <param name="token"></param>
        /// <returns></returns>
        public static JObject GetPayload(string token)
        {
            return token.Split('.')[1].Base64UrlDecode().ToJObject();
        }

        /// <summary>
        /// 获取Token中的数据
        /// </summary>
        /// <typeparam name="T">泛型</typeparam>
        /// <param name="token">token</param>
        /// <returns></returns>
        public static T GetPayload<T>(string token)
        {
            if (token.IsNullOrEmpty())
                return default;

            return token.Split('.')[1].Base64UrlDecode().ToObject<T>();
        }

        /// <summary>
        /// 校验Token
        /// </summary>
        /// <param name="token">token</param>
        /// <param name="secret">密钥</param>
        /// <returns></returns>
        public static bool CheckToken(string token, string secret)
        {
            var items = token.Split('.');
            var oldSign = items[2];
            string newSign = $"{items[0]}.{items[1]}".ToHMACSHA256String(secret);
            return oldSign == newSign;
        }
    }
}

(9)定义AppsettingsHelper类

using Microsoft.Extensions.Configuration;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace JwtAuthenticationOne
{
    public class AppsettingsHelper
    {
        private static IConfiguration _config;
        private static object _lock = new object();
        public static IConfiguration Configuration
        {
            get
            {
                if (_config == null)
                {
                    lock (_lock)
                    {
                        if (_config == null)
                        {
                            var builder = new ConfigurationBuilder().SetBasePath(AppContext.BaseDirectory).AddJsonFile("appsettings.json");
                            _config = builder.Build();
                        }
                    }
                }
                return _config;
            }
            set
            {
                _config = value;
            }
        }
        /// <summary>
        /// 从AppSettings获取key的值
        /// </summary>
        /// <param name="key">key</param>
        /// <returns></returns>
        public static string GetValue(string key)
        {
            return Configuration[key];
        }

        /// <summary>
        /// 获取连接字符串
        /// </summary>
        /// <param name="nameOfCon">连接字符串名</param>
        /// <returns></returns>
        public static string GetConnectionString(string nameOfCon)
        {
            return Configuration.GetConnectionString(nameOfCon);
        }
        /// <summary>
        /// 封装要操作的字符
        /// </summary>
        /// <param name="sections">节点配置</param>
        /// <returns></returns>
        public static string GetApp(params string[] sections)
        {
            try
            {
                if (sections.Any())
                {
                    return Configuration[string.Join(":", sections)];
                }
            }
            catch (Exception) { }
            return "";
        }

        /// <summary>
        /// 递归获取配置信息数组
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="sections"></param>
        /// <returns></returns>
        public static List<T> GetApp<T>(params string[] sections)
        {
            List<T> list = new List<T>();
            // 引用 Microsoft.Extensions.Configuration.Binder 包
            Configuration.Bind(string.Join(":", sections), list);
            return list;
        }
    }
}

(10)使用CheckJWTAttribute

定义一个BaseApiController,所有的controller都继承该类,BaseApiController类使用CheckJWTAttribute特性

using Microsoft.AspNetCore.Mvc;

namespace JwtAuthenticationOne.Controllers
{
    /// <summary>
    /// Mvc对外接口基控制器
    /// </summary>
    [CheckJWT]
    public class BaseApiController : ControllerBase
    {
    }
}

不需要认证的,使用NoCheckJWT限制,如下:

[HttpGet]
[NoCheckJWT]
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();
}

(11)最终项目结构及效果

 

 运行项目,查看效果:

 

 

2、使用aspnetcore提供的组件

(1)新建netcore webapi项目

(2)安装组件

通过nugut搜索安装Microsoft.AspNetCore.Authentication.JwtBearer 

(3)jwtconfig配置

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*",
  "Jwt": {
    "Issuer": "Issuer",
    "Audience": "Audience",
    "SigningKey": "EF1DA5B4-C7FA-4240-B997-7D1701BF9BE2"
  }
}

(4)定义jwtconfig对应的实体

namespace JwtAuthenticationTwo
{
    public class JwtConfig
    {
        public string Issuer { get; set; }
        public string Audience { get; set; }
        public string SigningKey { get; set; }
    }
}

(5)Startup.cs 配置

ConfigureServices 中需要进行添加的信息

public void ConfigureServices(IServiceCollection services)
{
  services.AddControllers();
   #region   Token验证信息  JWT
   //读取JWT的配置信息
   var jwtconfig = Configuration.GetSection("Jwt").Get<JwtConfig>();
   //注册JWT认证所需要的服务
   services.AddAuthentication(option =>
   {
     //设置验证时使用的默认方案 option.DefaultAuthenticateScheme
= JwtBearerDefaults.AuthenticationScheme;
     //设置挑战时使用的默认方案 option.DefaultChallengeScheme
= JwtBearerDefaults.AuthenticationScheme; }) .AddJwtBearer(option => { option.TokenValidationParameters = new TokenValidationParameters { ValidIssuer = jwtconfig.Issuer, ValidAudience = jwtconfig.Audience, ValidateIssuer = true, ValidateLifetime = true, IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtconfig.SigningKey)), // 缓冲过期时间,总的有效时间等于这个时间加上jwt的过期时间,如果不配置,默认是5分钟 ClockSkew = TimeSpan.FromSeconds(5) };        option.Events = new JwtBearerEvents { //此处为权限验证失败后触发的事件 OnChallenge = context => { //此处代码为终止.Net Core默认的返回类型和数据结果,这个很重要哦,必须 context.HandleResponse(); //自定义自己想要返回的数据结果,我这里要返回的是Json对象,通过引用Newtonsoft.Json库进行转换 var payload = JsonConvert.SerializeObject(new { message = "授权未通过,Token无效", status = false, code = 401 }); //自定义返回的数据类型 context.Response.ContentType = "application/json"; //自定义返回状态码,默认为401 我这里改成 200 context.Response.StatusCode = StatusCodes.Status200OK; //输出Json数据结果 context.Response.WriteAsync(payload); return Task.FromResult(0); } }; }); services.AddOptions().Configure<JwtConfig>(Configuration.GetSection("Jwt")); #endregion services.AddSwaggerGen(c => { c.SwaggerDoc("v1", new OpenApiInfo { Title = "JwtAuthenticationTwo", Version = "v1" }); }); }

Configure需要添加的信息

// JWT身份认证
app.UseAuthentication();
app.UseAuthorization();

以上配置好了 就基本上可以使用JWT验证了,下面介绍如何生成jwt token

(6)定义JwtHelper.cs

using Microsoft.IdentityModel.Tokens;
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;
using System.Text;

namespace JwtAuthenticationTwo
{
    /// <summary>
    /// JWT帮助类信息
    /// </summary>
    public class JwtHelper
    {
        /// <summary>
        /// 颁发JWT字符串
        /// </summary>
        /// <param name="tokenModel"></param>
        /// <returns></returns>
        public static string IssueJwt(Claim[] claim)
        {
            // 读取对应的配置信息
            string iss = AppsettingsHelper.GetApp("Jwt:Issuer");
            string aud = AppsettingsHelper.GetApp("Jwt:Audience");
            string secret = AppsettingsHelper.GetApp("Jwt:SigningKey");
            //加密关键字
            var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secret));
            //编码格式
            var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
            //token存储相关信息
            var token = new JwtSecurityToken(
                issuer: iss,
                audience: aud,
                claims: claim,
                notBefore: DateTime.Now,
                expires: DateTime.Now.AddSeconds(300),
                signingCredentials: creds);
            var jwtHandler = new JwtSecurityTokenHandler();
            //生成对应的编码信息
            var encodedJwt = jwtHandler.WriteToken(token);
            return encodedJwt;
        }
        /// <summary>
        /// 解析
        /// </summary>
        /// <param name="jwtStr"></param>
        /// <returns></returns>
        public static List<Claim> SerializeJwt(string jwtStr)
        {
            var jwtHandler = new JwtSecurityTokenHandler();
            JwtSecurityToken jwtToken = jwtHandler.ReadJwtToken(jwtStr);
            object role;
            try
            {
                jwtToken.Payload.TryGetValue(ClaimTypes.Role, out role);
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                throw;
            }
            var list = jwtToken.Claims.ToList();
            return list;
        }
    }
}

(7)定义AppsettingsHelper.cs

using Microsoft.Extensions.Configuration;
using System;
using System.Collections.Generic;
using System.Linq;

namespace JwtAuthenticationTwo
{
    public class AppsettingsHelper
    {
        private static IConfiguration _config;
        private static object _lock = new object();
        public static IConfiguration Configuration
        {
            get
            {
                if (_config == null)
                {
                    lock (_lock)
                    {
                        if (_config == null)
                        {
                            var builder = new ConfigurationBuilder().SetBasePath(AppContext.BaseDirectory).AddJsonFile("appsettings.json");
                            _config = builder.Build();
                        }
                    }
                }
                return _config;
            }
            set
            {
                _config = value;
            }
        }
        /// <summary>
        /// 从AppSettings获取key的值
        /// </summary>
        /// <param name="key">key</param>
        /// <returns></returns>
        public static string GetValue(string key)
        {
            return Configuration[key];
        }

        /// <summary>
        /// 获取连接字符串
        /// </summary>
        /// <param name="nameOfCon">连接字符串名</param>
        /// <returns></returns>
        public static string GetConnectionString(string nameOfCon)
        {
            return Configuration.GetConnectionString(nameOfCon);
        }
        /// <summary>
        /// 封装要操作的字符
        /// </summary>
        /// <param name="sections">节点配置</param>
        /// <returns></returns>
        public static string GetApp(params string[] sections)
        {
            try
            {
                if (sections.Any())
                {
                    return Configuration[string.Join(":", sections)];
                }
            }
            catch (Exception) { }
            return "";
        }

        /// <summary>
        /// 递归获取配置信息数组
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="sections"></param>
        /// <returns></returns>
        public static List<T> GetApp<T>(params string[] sections)
        {
            List<T> list = new List<T>();
            // 引用 Microsoft.Extensions.Configuration.Binder 包
            Configuration.Bind(string.Join(":", sections), list);
            return list;
        }
    }
}

(8)控制器层使用示例

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;

namespace JwtAuthenticationTwo.Controllers
{
    [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<WeatherForecastController> _logger;

        public WeatherForecastController(ILogger<WeatherForecastController> logger)
        {
            _logger = logger;
        }
        [Authorize]
        [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();
        }
        /// <summary>
        /// 测试获取token信息
        /// </summary>
        /// <returns></returns>
        [HttpPost]
        public ActionResult<string> GetToken()
        {
            //参数存储的信息
            var claim = new Claim[]{
                new Claim("UserName", "测试"),
                new Claim("UserId", "10086")
            };
            //生成证书
            var token = JwtHelper.IssueJwt(claim);
            //解析证书
            var data = JwtHelper.SerializeJwt(token);
            return Ok(new { token = token, data = data });
        }
    }
}

(9)项目结构和效果

 

 项目运行效果:

然后整体就基本上结束了。虽然说这种方式没有自定义的方式代码容易读,但是也方便了好多

四、扩展

第三部分介绍了使用Microsoft.AspNetCore.Authentication.JwtBearer 来实现jwt认证,微软官网提供了对该dll的解析。这里简单的描述下。先来熟悉几个概念:

  • 身份验证:身份验证基本上意味着使用给定的信息并尝试用该信息对用户进行身份验证。因此,这将尝试创建一个用户标识,并使其可用于框架。例如,cookie认证方案使用cookie数据恢复用户身份。或者JWT bearing认证方案将使用在请求中作为Authorization头的一部分提供的令牌来创建用户身份。

  • 挑战:当认证方案受到挑战时,该方案应提示用户对自己进行认证。例如,这可能意味着用户被重定向到一个登录表单,或者将被重定向到外部身份验证提供者。

  • Forbid:当一个认证方案被禁止时,该方案基本上只是响应一些东西,告诉用户他们可能不做任何他们试图做的事情。这通常是一个HTTP 403错误,可能是重定向到某些错误页面。 

  • 登录:当一个身份验证方案正在被登录时,该方案将被告知获取一个现有用户(claimsprinprincipal)并以某种方式持久化该用户。例如,在cookie认证方案上注册用户基本上会创建一个包含该用户身份的cookie。 

  • 退出:这与登录相反,它将告诉身份验证方案删除持久性。在cookie方案上注销将有效地使cookie过期。

现在来具体说下JwtBearer中涉及的概念,先来说下AuthenticationOptions涉及到的属性

  • Defaultauthenticatscheme:设置认证时使用的默认方案。

  • Defaultchallengesscheme:设置挑战时使用的默认方案。

  • DefaultForbidScheme:设置禁止访问时使用的默认方案。

  • DefaultSignInScheme:设置要登录的默认方案。

  • DefaultSignOutScheme:设置退出的默认方案。

  • DefaultScheme:设置默认回退方案。

正常情况下,我们没有必要把这些属性全部进行配置,如下说明:

  • 身份认证: 配置defaultauthenticatscheme或DefaultScheme
  • 挑战:配置defaultchallengesscheme或DefaultScheme
  • Forbid:配置 DefaultForbidScheme或defaultchallengesscheme或DefaultScheme
  • Sign-in:配置DefaultSignInScheme或DefaultScheme
  • Sign-out:配置 DefaultSignOutScheme或DefaultScheme

有些时候我们也可以自定义认证或者挑战方案。下面来说下JwtBear方案中的事件:

  • OnChallenge:发生质询的时,在将询问发送回调用方之前调用。
  • OnAuthenticationFailed:认证期间出现异常调用。
  • OnForbidden:因为禁止而导致授权失败调用。
  • OnTokenValidated:令牌通过认证后调用。
  • OnMessageReceived:首次收到协议消息时调用。

虽然使用微软提供的JwtBearer方便了很多,但是不是那么好理解了,不太喜欢使用该方案的可以采用自定义的方式实现jwt认证。

五、源码下载

源码:https://github.com/qiuxianhu/AuthenticationAndAuthorization

 


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM