HTTPBearer认证及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的开放标准()。
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实例:
(1)Header
在这⾥,我们说明了这是⼀个JWT,并且我们所⽤的签名算法是HS256算法。对它也要进⾏Base64编码,之后的字符串就成了JWT的Header(头部):
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
(2)Payload
将上⾯的JSON对象进⾏[base64编码]可以得到下⾯的字符串。这个字符串我们将它称作JWT的Payload(载荷):
eyJpc3MiOiJKb2huIFd1IEpXVCIsImlhdCI6MTQ0MTU5MzUwMiwiZXhwIjoxNDQxNTk0NzIyLCJhdWQiOiJ3d3cuZXhhbXBsZS5jb20iLCJzdWIiOiJqcm9ja2V0QGV4YW1wbGUuY29tIiwiZnJvbV91c2VyIjoiQiIsInRhcmdldF91c2VyIjo (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:
注意: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)
{
case2:
base64UrlStr += "==";
break;
case3:
base64UrlStr += "=";
break;
}
var bytes = Convert.FromBase64String(base64UrlStr);
return Encoding.UTF8.GetString(bytes);
}
/
//<summary>
///将Json字符串转为JObject
///</summary>
///<param name="jsonStr">Json字符串</param>
///<returns></returns>
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论