既然选择了远方,便只顾风雨兼程 __ HANS许
*
*
*
* JWT(JSON Web Token)
<https://www.cnblogs.com/xuhuale/p/10507241.html#jwtjson20web20token_1>
* ASP.NET Core 的Middleware实现
<https://www.cnblogs.com/xuhuale/p/10507241.html#aspnet20core20e79a84middlewaree5ae9ee78eb0_2>
引言:挺久没更新了,之前做了Vue的系列,后面想做做服务端的系列,上下衔接,我们就讲讲WebApi(网络应用程序接口),接口免不了用户认证,所以接下来我们的主题系列文章便是“
基于ASP.NET <http://xn--ASP-xi9dt44b.NET> Core的用户认证”,分为市面上流行的JWT(JSON
WebToken)与OAuth2(开放授权)
<> <> <> <>
JWT(JSON Web Token)
*
什么叫JWT
JSON Web Token(JWT)是目前最流行的跨域身份验证解决方案。
一般来说,互联网用户认证是这样子的。
1、用户向服务器发送用户名和密码。
2、服务器验证通过后,在当前对话(session)里面保存相关数据,比如用户角色、登录时间等等。
3、服务器向用户返回一个 session_id,写入用户的 Cookie。
4、用户随后的每一次请求,都会通过 Cookie,将 session_id 传回服务器。
5、服务器收到 session_id,找到前期保存的数据,由此得知用户的身份。
服务器需要保存session,做持久化,这种模式没有分布式架构,无法支持横向扩展
,如果真的要的话就必须采用分布式缓存来进行管理Seesion。那JWT相反,它保存的是在客户端,每次请求都将JWT代入服务器,进行签名,权限验证。
JWT由客户端请求,服务端生成,客户端保存,服务端验证。
*
JWT的原理与格式
*
原理
在上面,我们也讲过了,简单的来说,我们将服务器需要验证我们的条件(账户,密码等等),发给服务器,服务器认证通过,生成一个JSON对象返回给我们,例如下面。当然,为了防止被篡改,所以我们会将对象加密,再次请求服务器,需要将jwt放在请求头部,传递给服务器,来判断权限等等。
* {
* "姓名": "张三",
* "角色": "管理员",
* "到期时间": "2018年7月1日0点0分"
* }
*
格式
* 实际格式
* eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
*
eyJBIjoiQUFBQSIsIkIiOiJCQkJCIiwiQyI6IkNDQ0MiLCJ1ZXIiOiJ4dWh1YWxlIiwib3BlbmlkIjoiNTE1NjEzMTM1MTYzMjEiLCJmZiI6ImRmc2RzZGZzZGZzZHMiLCJuYmYiOjE1NTIyMTE4NjAsImV4cCI6MTU1MjIxMzY2MH0.
* 16m57YnnIcgIth25dwphQKPYuIq42BVmZV6LIBO7KDg
它是一个很长的字符串,中间用点(.)分隔成三个部分。注意,JWT内部是没有换行的,这里只是为了便于展示,将它写成了几行。JWT 的三个部分依次如下。
* Header(头部)
* Payload(负载)
* Signature(签名)
简单讲下,Header描述加密算法与token类型,Payload描述的是实际需要传递的数据,如失效时间,签发人等等,Signature
描述的是一段对于前面两部部分的签名,当然秘钥只有服务器才知道。
简单的介绍下JWT,更多的话,可以这边文章
<http://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html>
看看。我们着重讲下实现。
<> <> <> <>
ASP.NET <http://ASP.NET> Core 的Middleware实现
*
创建JWT
首先我们要先创建token,毕竟这个是最重要的。Core自带JWT帮助类,所以我们按照帮助类的意思来写个方法创建token。
* public string CreateJsonWebToken(Dictionary<string, string> payLoad)
* {
* if (string.IsNullOrWhiteSpace(setting.SecurityKey))
* {
* throw new ArgumentNullException("JsonWebTokenSetting.securityKey",
*
"securityKey为NULL或空字符串。请在\"appsettings.json\"配置\"JsonWebToken\"节点及其子节点\"securityKey\""
);
* }
* var now = DateTime.UtcNow;
*
* // Specifically add the jti (random nonce), iat (issued timestamp), and sub
(subject/user) claims.
* // You can add other claims here, if you want:
* var claims = new List<Claim>();
* foreach (var key in payLoad.Keys)
* {
* var tempClaim = new Claim(key, payLoad[key]?.ToString());
* claims.Add(tempClaim);
* }
*
* // Create the JWT and write it to a string
* var jwt = new JwtSecurityToken(
* issuer: null,
* audience: null,
* claims: claims,
* notBefore: now,
* expires: now.Add(TimeSpan.FromMinutes(setting.ExpiresMinute)),
* signingCredentials: new SigningCredentials(new
SymmetricSecurityKey(Encoding.ASCII.GetBytes(setting.SecurityKey)),
SecurityAlgorithms.HmacSha256));
* var encodedJwt = new JwtSecurityTokenHandler().WriteToken(jwt);
* return encodedJwt;
* }
从方法我们看到,我们传入的是负载这个片段,而失效时间与秘钥我们是放在了appsettings.json来进行配置的。使用DI来获取配置文件中的节点值。
*
编写中间件
我们都知道,中间件是Core的管道模型组成部分,所以我们在中间件做验证,来判断每次请求用户是有有权限是有该WebApi或者其他API。
* 中间件
* public JwtCustomerAuthorizeMiddleware(RequestDelegate next,
IOptions<JsonWebTokenSetting> options, IJsonWebTokenValidate
jsonWebTokenValidate, Func<Dictionary<string, string>, JsonWebTokenSetting, bool
> validatePayLoad, List<string> anonymousPathList)
* {
* this._next = next;
* this._setting = options.Value;
* this._jsonWebTokenValidate = jsonWebTokenValidate;
* this._validatePayLoad = validatePayLoad;
* this._anonymousPathList = anonymousPathList;
* }
*
* public async Task Invoke(HttpContext context)
* {
* //JsonWebTokenValidate
* //若是路径可以匿名访问,直接跳过
* if (_anonymousPathList.Contains(context.Request.Path.Value))
* {
* //还未验证
* await _next(context);
* return;
* }
* var result = context.Request.Headers.TryGetValue("Authorization", out
StringValues authStr);
* if (!result || string.IsNullOrEmpty(authStr.ToString()))
* {
* throw new UnauthorizedAccessException("未授权,请传递Header头的Authorization参数。");
* }
*
* //进行验证与自定义验证
* result = _jsonWebTokenValidate.Validate(authStr.ToString().Substring(
"Bearer ".Length).Trim()
* , _setting, _validatePayLoad);
* if (!result)
* {
* throw new UnauthorizedAccessException("验证失败,请查看传递的参数是否正确或是否有权限访问该地址。");
* }
*
* await _next(context);
* }
从代码来看,anonymousPathList是URL路径,若是在这个List内的URL,便可直接跳过验证,
接着将authStrtoken代入验证函数,validatePayLoad却是我们自代入的委托函数,用于服务器自定义验证。
* 验证
验证方法,我只是做了签名验证与时间验证。并没有定得死死的,让用户自由度的去进行验证。
* public bool Validate(string encodeJwt, JsonWebTokenSetting setting,
Func<Dictionary<string, string>, JsonWebTokenSetting, bool> validatePayLoad)
* {
* if (string.IsNullOrWhiteSpace(setting.SecurityKey))
* {
* throw new ArgumentNullException("JsonWebTokenSetting.securityKey",
*
"securityKey为NULL或空字符串。请在\"appsettings.json\"配置\"JsonWebToken\"节点及其子节点\"securityKey\""
);
* }
*
* var success = true;
* var jwtArr = encodeJwt.Split('.');
* var header = JsonConvert.DeserializeObject<Dictionary<string, string
>>(Base64UrlEncoder.Decode(jwtArr[0]));
* var payLoad = JsonConvert.DeserializeObject<Dictionary<string, string
>>(Base64UrlEncoder.Decode(jwtArr[1]));
*
* var hs256 = new HMACSHA256(Encoding.ASCII.GetBytes(setting.SecurityKey));
* //首先验证签名是否正确(必须的)
* success = success && string.Equals(jwtArr[2],
Base64UrlEncoder.Encode(hs256.ComputeHash(Encoding.UTF8.GetBytes(string
.Concat(jwtArr[0], ".", jwtArr[1])))));
* if (!success)
* {
* return success;//签名不正确直接返回
* }
* //其次验证是否在有效期内(也应该必须)
* var now = ToUnixEpochDate(DateTime.UtcNow);
* success = success && (now >= long.Parse(payLoad["nbf"].ToString()) && now <
long.Parse(payLoad["exp"].ToString()));
*
* //再其次 进行自定义的验证
* success = success && validatePayLoad(payLoad, setting);
*
* return success;
* }
*
加载中间件
* 使用扩展方法,来封装中间件
* public static IApplicationBuilder UseJwtCustomerAuthorize(this
IApplicationBuilder app, Action<IJwtCustomerAuthorezeOption> action)
* {
* var _JwtCustomerAuthorezeOption =
app.ApplicationServices.GetService<IJwtCustomerAuthorezeOption>()as
JwtCustomerAuthorezeOption;//new JwtCustomerAuthorezeOption();
* action(_JwtCustomerAuthorezeOption);
* return
app.UseMiddleware<JwtCustomerAuthorizeMiddleware>(_JwtCustomerAuthorezeOption.validatePayLoad,
_JwtCustomerAuthorezeOption.anonymousPath);
* }
* 在Startup.cs使用
* 注册服务
* public void ConfigureServices(IServiceCollection services) {
* services.AddJwt(Configuration);}
* 使用中间件
* public void Configure(IApplicationBuilder app, IHostingEnvironment env)
* {
* app.UseJwtCustomerAuthorize(option =>
* {
* //设置不会被验证的url,可以使用链式调用一直添加
* option.SetAnonymousPaths(new System.Collections.Generic.List<string>()
* {
* // "/",
* "/Home/Privacy",
* "/Home/CreateJsonToken"
* });
* // 自定义验证函数,playLoad为带过来的参数字典,setting为失效时间与秘钥
* option.SetValidateFunc((playLoad, sertting) =>
* {
* return true;
* });
* });
* }
总结下,通过上面,就完成了JWT在ASP.NET <http://xn--JWTASP-2e8in30d5jjesboy6a.NET>
Core使用中间件的方式的实现。简单来说就是用自带方法创建token,验证则使用中间件的形式,每次请求都需要进行验证当然你可以设置特殊URL。在下篇文章我们来讲讲使用策略模式的JWT实现。
热门工具 换一换