.Net Core 5.0身份验证(鉴权)

1.身份验证(鉴权)

1.1 什么是身份验证

身份验证,也叫鉴权,故名思意就是主要的作用便是用做验证身份。

1.2 鉴权与授权的区别

鉴权与授权的概念上是有区别的,举个例子。一天周末,小明去游乐场玩。首先,向工作人员出示入场门票,工作人员验证门票的有效期,门票是否伪造…验证通过后小明进入游乐园,这时小明入园后想去玩过山车,可过山车是VIP门票才能玩的,设备管理员查看了小明的门票,发现小明的门票是普通游客,所以没法玩过山车。

鉴权:工作人员验证门票的过程 = 鉴权。
授权:小明不是VIP,没有玩过山车的权限,这就是授权。

利用上面一个例子对应到我们一个请求的过程:
小明 = 一个请求
门票 = token
检票员 = 鉴权中间件
设备管理员 = 授权中间件
过山车 = 受保护的资源

那么将上面的例子转换为一次http请求过程的话就是,请求到达鉴权中间件,鉴权中间件验证后是否验证通过,不通过,则直接返回未授权,通过后到达授权中间件,授权中间件查看是否有权限访问该资源,有则通过,否则返回权限不足。
在这里插入图片描述

1.3 如何区分鉴权与授权(联想记忆法)

鉴权的英文单词是:Authentication
授权的英文单词是:Authorization

看起来很像对吧,我也经常搞混,记得第一次写鉴权时,硬是没效果,查了半天才发现中间件用错了。😂,所以,一定不要记混了,我也就是个专科英文不好,我是这么记的。
我用的是联想记忆法。首先,找它们之间的区别,对我而言最直观的是鉴权的单词里包含en,而授权的单词包含or。而鉴权又叫身份证,的拼音里也包含en,所以单词里有en的就是鉴权,而另一个就是授权。

1.4 身份验证中间件的简单使用

1.4.1 名词解释

Scheme 译:方案,简单了说就是使用什么方式来验证身份,常见的有cookie、jwt web token,这里主要使用jwt讲解。

Authentication 译:授权,就是身份验证,抽象了说就是看看你有没有身份证,身份证是否伪造,是否过期等。

1.4.2 授权相关的类与接口

Claim:你身份的信息,比如:身份证上有姓名,出生日期,那么就可以这样使用

//姓名
var cmName = new Claim("Name","张三")

//出生日期
var cmbirthday = new Claim("birthday", "1998.01.01", ClaimValueTypes.DateTime);

IAuthenticationHandler:身份验证处理程序,这个接口主要负责验证逻辑处理,也就是说你如果不想使用Jwt作为验证方案而想自定义验证逻辑就可以实现这个接口。主要的方法如下:

InitializeAsync(AuthenticationScheme, HttpContext) 初始化,也就是说最先执行的是这个方法。
AuthenticateAsync() 身份验证核心方法。
ChallengeAsync() 质疑当前请求,说白了就是身份验证不通过就调用这个方法,一般返回http状态码401表示身份授权失败
ForbidAsync()禁止当前请求,身份验证通过,但权限不足时调用,一般返回403表示权限不足

AuthenticateResult

身份验证结果,如果你用到IAuthenticationHandler来实现自定义身份验证的话那么这个类是一定会用上的,使用Seccess()来表示身份验证成功,NoResult()或不返回结果都表示身份验证失败。

Jwt相关的类:

我们都知道jwt加密方式有由三部分组成,分别是header、payload、signature,至于还不知道这三个部分的作用的可以自行百度,这里就不作多赘述了),对应的三个类分别是:

JwtHeader
JwtPayload
JwtSecurityToken
JwtBearerEvents:看到Event就知道与事件相关,通过重写该类中的各个事件对应的方法实现对验证流程的控制,各事件对应的方法如下

验证前:MessageReceived()
验证失败后:Challenge()
权限不足:Forbidden()
验证通过后:TokenValidated()
验证过程中发生异常:AuthenticationFailed()

1.5 实操

1.5.1 目标

使用Jwt身份很验证且对验证流程做控制。

1.5.2 环境

工具:vs2019
框架:Asp.Net Core 5.0

1.5.3 开始

1.先安装Microsoft.AspNetCore.Authentication.JwtBearer
在这里插入图片描述

2.准备好jwt配置文件

“JwtSetting”: {
“Issuer”: “zmz.com”,//颁发token端
“Audience”: blog.console.com",//接受端
“ExpireSeconds”: 1440,//token过期时间(秒)
“ENAlgorithm”: “HS256”,//加密算法
“SecurityKey”: “Zmz=Start20180621End20”//密钥,如果你是用的也是HS256算法那么密钥强制大于16位
}

3.准备Jwt配置类

	 /// <summary>
    /// jwt配置
    /// </summary>
  public  class JwtSetting
    {
        /// <summary>
        /// 密钥
        /// </summary>
        public string SecurityKey { get; set; }

        /// <summary>
        /// 加密算法
        /// </summary>
        public string ENAlgorithm { get; set; }

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

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

        /// <summary>
        /// 过期时间    单位:秒
        /// </summary>
        public int ExpireSeconds { get; set; }
    }

4.准备生成token的方法

    public class JwtHelper
    {
        public static string BuildToken(UserIdentity identity)
        {
            var jwtsetting = ConfigurationFileHelper.GetNode<JwtSetting>("JwtSetting");

            //准备calims,随便写,爱写多少写多少,但千万别放敏感信息
            var calims = identity.PropValuesType().Select(x => new Claim(x.Name, x.Value.ToString(),x.Type)).ToList();
            
            //创建header
            var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtsetting.SecurityKey));
            var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
            var header = new JwtHeader(creds);

            //创建payload
            var payload = new JwtPayload(jwtsetting.Issuer, jwtsetting.Audience,calims,DateTime.Now, DateTime.Now.AddMinutes(jwtsetting.ExpireSeconds));

            //创建令牌 
            var token = new JwtSecurityToken(header,payload);
            return new JwtSecurityTokenHandler().WriteToken(token);
        }
    }

生成token我使用了反射来生成Claims,涉及的方法放在了文章最后

5.为了能够实现对验证流程的控制,我们先重新JwtBearerEvents内的方法,并更换token的来源

 public class JwtBearerEvent : JwtBearerEvents
    {
    	//自定义的http响应类
        private ResponseDto ResponseDto;
        public JwtBearerEvent()
        {
            ResponseDto = new ResponseDto()
            {
                RequestIsSuccess = false,
            };
        }

        /// <summary>
        /// 验证之前
        /// </summary>
        /// <param name="context"></param>
        /// <returns></returns>
        public override Task MessageReceived(MessageReceivedContext context)
        {	
        	//默认情况下验证的token是从请求头中的【authorization】取得,我这里想要不使用默认的token来源,把它改成来自于请求头【access_token】,context.Token就是
            var authorizationIsHave = context.Request.Headers.ContainsKey("access_token");
            if (authorizationIsHave) context.Token = context.Request.Headers["access_token"].ToString();
            return base.MessageReceived(context);
        }

        /// <summary>
        /// 验证通过但权限不足
        /// </summary>
        /// <param name="context"></param>
        /// <returns></returns>
        public override Task Forbidden(ForbiddenContext context)
        {
            context.Response.StatusCode = 200;
            ResponseDto.ResponseState = ResponseState.Forbidden;
            ResponseDto.ErrorMsg = "Forbidden,权限不足";
            context.HttpContext.ResponseJsonAsync(ResponseDto);
            return Task.CompletedTask;
        }

        /// <summary>
        /// 验证失败,如token格式不正确
        /// </summary>
        /// <param name="context"></param>
        /// <returns></returns>
        public override Task Challenge(JwtBearerChallengeContext context)
        {
            context.Response.StatusCode = 200;
            ResponseDto.ResponseState = ResponseState.Unauthorized;
            ResponseDto.ErrorMsg = $"未经授权 Unauthorized,{context.Error}";
            context.HttpContext.ResponseJsonAsync(ResponseDto);
            return Task.CompletedTask;
        }

        /// <summary>
        /// 验证通过
        /// </summary>
        /// <param name="context"></param>
        /// <returns></returns>
        public override Task TokenValidated(TokenValidatedContext context)
        {
            base.TokenValidated(context);
            UserIdentity user = new UserIdentity();
            var claims = context.Principal.Claims;
            ///自定义的用户身份类。
            UserIdentity.CurrentUser = new UserIdentity()
            {
                UserId = Convert.ToInt32(claims.FirstOrDefault(x => x.Type == "UserId")?.Value),
                Account = claims.FirstOrDefault(x => x.Type == "Account")?.Value,
                Name = claims.FirstOrDefault(x => x.Type == "Name")?.Value,
            };
            return Task.CompletedTask;
        }

        /// <summary>
        /// 在处理请求期间引发异常时调用
        /// </summary>
        /// <param name="context"></param>
        /// <returns></returns>
        public override Task AuthenticationFailed(AuthenticationFailedContext context)
        {
            context.Fail(context.Exception);
            context.Response.StatusCode = 200;
            context.HttpContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
            ResponseDto.ResponseState = ResponseState.Unauthorized;
            ResponseDto.ErrorMsg = "token已过期,请重新登录";
            context.HttpContext.ResponseJsonAsync(ResponseDto);
            return Task.CompletedTask;
        }
    }

6.Startup添加Authentication服务与中间件

services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, option =>
            {

                JwtSetting jwtSetting = new JwtSetting();
                _configuration.Bind("JwtSetting", jwtSetting);
                option.SaveToken = true;
                option.TokenValidationParameters = new TokenValidationParameters()
                {
                    ValidIssuer = jwtSetting.Issuer,//发行人
                    ValidAudience = jwtSetting.Audience,//订阅人
                    IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSetting.SecurityKey)),//解密的密钥

                    ValidateIssuerSigningKey = true,//是否验证签名,不验证的画可以篡改数据,不安全
                    ValidateIssuer = true,//是否验证发行人,就是验证载荷中的Iss是否对应ValidIssuer参数
                    ValidateAudience = true,//是否验证订阅人,就是验证载荷中的Aud是否对应ValidAudience参数
                    ValidateLifetime = true,//是否验证过期时间,过期了就拒绝访问
                    ClockSkew = TimeSpan.Zero,//这个是缓冲过期时间,也就是说,即使我们配置了过期时间,这里也要考虑进去,过期时间+缓冲,默认好像是7分钟,你可以直接设置为0
                    //RequireExpirationTime = true,
                };

                option.Events = new JwtBearerEvent();
            });

记得将Events替换成我们重写的类,另外将授权的服务也加上,最后别忘了添加中间件。

      app.UseAuthentication();
      app.UseAuthorization();

TokenValidationParameters:token验证的参数

7.结束

最后只要将按流程走一边就可以了。

用户登录=>生成token并返回前端,添加access_token(默认情况下是authorization)将token设为其值,访问一个带有[Authorize]特性的Action。

1.6 总结

文章有点长,但其实讲的东西不多。总结一下,身份验证就是检查你身份的一个过程,这个方法由很多常见的由jwt、cookie甚至是自定义,不过万变不离其宗,核心的接口就一个
IAuthenticationHandler处理验证逻辑,无论是jwt还是什么,只要在.Net Core上提供的认证方案都必须实现这个接口,另外还遗留了一些其它的问题没有讲到,比如,我验证之后如何取得我当前用户的信息?如何退出登录?token如何续签?感兴趣的可以自行研究,另外学习一定要带着问题去学,加强深度思考的能力。

1.7实操涉及到的类:

用户身份模型

  /// <summary>
    /// 用户身份模型
    /// </summary>
    public class UserIdentity
    {

        /// <summary>
        /// 用户ID
        /// </summary>
        public int UserId { get; set; }

        /// <summary>
        /// 昵称
        /// </summary>
        public string Name { get; set; }

        /// <summary>
        /// 账号
        /// </summary>
        public string Account { get; set; }


        /// <summary>
        /// 当前登录用户身份模型
        /// </summary>
        [ThreadStatic, Description("指示该值线程内唯一")]
        public static UserIdentity CurrentUser;
    }

配置文件帮助类

using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Configuration.Json;

namespace Zmz.Tools
{
    /// <summary>
    /// 配置文件帮助类
    /// </summary>
    /// <summary>
    /// 读取配置文件信息
    /// </summary>
    public class ConfigurationFileHelper
    {
        public static IConfiguration Configuration { get; set; }

        static ConfigurationFileHelper()
        {
            Configuration = new ConfigurationBuilder()
                 .Add(new JsonConfigurationSource { Path = "appsettings.json", ReloadOnChange = true })
                 .Build();
        }

        /// <summary>
        /// 获得配置文件的对象值
        /// </summary>
        /// <param name="jsonPath">文件路径</param>
        /// <param name="key"></param>
        /// <returns></returns>
        public static string GetJson(string jsonPath, string key)
        {
            if (string.IsNullOrEmpty(jsonPath) || string.IsNullOrEmpty(key)) return null;
            IConfiguration config = new ConfigurationBuilder().AddJsonFile(jsonPath).Build();//json文件地址
            return config.GetSection(key).Value;//json某个对象
        }

        /// <summary>
        /// 获取数据库连接字符串
        /// </summary>
        /// <returns></returns>
        public static string GetMysqlConnection() {
            return Configuration.GetConnectionString("MySql").Trim();
        }

        /// <summary>
        /// 根据节点名称获取配置模型
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="Node"></param>
        /// <returns></returns>
        public static T GetNode<T>(string Node) where T : new(){
            T model = Configuration.GetSection(Node).Get<T>();
            return model;

        }
    }
}

获取类属性,值,属性类型

public static IEnumerable<(string Name,object Value,string Type)> PropValuesType(this object obj)
        {
            List<(string a, object b, string c)> result  = new List<(string a, object b, string c)>();

            var type = obj.GetType();
            var props = type.GetProperties();
            foreach (var item in props)
            {
                result.Add((item.Name,item.GetValue(obj),item.PropertyType.Name));
            }
            return result;
        }

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值