zoukankan      html  css  js  c++  java
  • 基于JWT的身份认证(05)

    目录:

    • 过滤器
    • JWT概念

    一. Web Api中的过滤器

      (1)所处命名空间不同

         WebApi过滤器的命名空间是"System.web.Http",而MVC过滤器的命名空间是"System.Web.Mvc"

      (2)WebApi没有结果过滤器

          WP过滤器的执行顺序为:OnAuthorization--OnActionExecuting--Action方法执行--OnActionExecuted

    WebApi过滤器的基本用法:

    // 授权过滤器
    public class MyAuthorize : AuthorizeAttribute
    {
       public override void OnAuthorization(HttpActionContext actionContext)
       {
          //1.如果保留如下代码,则会运行.net framework定义好的身份验证,如果希望自定义身份验证,则删除如下代码
          // base.OnAuthorization(actionContext);
          //2.获取控制器作用的Controller和action的名字
          string controllerName = actionContext.ActionDescriptor.ControllerDescriptor.ControllerName.ToLower();
          string actionName = actionContext.ActionDescriptor.ActionName.ToLower();
          HttpContext.Current.Response.Write("身份验证过滤器作用于" + controllerName + "控制器下的" + actionName + "方法");
            }
    }
    //行为过滤器
    public class MyAction: ActionFilterAttribute
    {
        /// <summary>
        /// 在Action方法运行之前调用
        /// </summary>
        public override void OnActionExecuting(HttpActionContext actionContext)
        {
            //2.获取控制器作用的Controller和action的名字
            string controllerName = actionContext.ActionDescriptor.ControllerDescriptor.ControllerName.ToLower();
            string actionName = actionContext.ActionDescriptor.ActionName.ToLower();
            HttpContext.Current.Response.Write("行为过滤器OnActionExecuting作用于" + controllerName + "控制器下的" + actionName + "方法运行之前");
        }
        /// <summary>
        /// 在Action方法运行之后调用
        /// </summary>
        public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
        {
            base.OnActionExecuted(actionExecutedContext);
           //2.获取控制器作用的Controller和action的名字
           string controllerName = actionExecutedContext.ActionContext.ControllerContext.ControllerDescriptor.ControllerName.ToLower();
           string actionName = actionExecutedContext.ActionContext.ActionDescriptor.ActionName.ToLower();
           HttpContext.Current.Response.Write("行为过滤器OnActionExecuted作用于" + controllerName + "控制器下的" + actionName + "方法运行之后");
        }
    }
    // 异常过滤器
    public class MyException : FilterAttribute,IExceptionFilter
    {
        public async Task ExecuteExceptionFilterAsync(HttpActionExecutedContext actionExecutedContext, CancellationToken cancellationToken)
        {
            //1.获取异常信息
            string errorMsg = actionExecutedContext.Exception.ToString();
            //2.对获取的异常信息进行处理
            using (StreamWriter writer = File.AppendText("d:/err.txt"))
            {
                await writer.WriteLineAsync(errorMsg);
            }
         }
    }

     

    二.JWT

    • Json web token 简称:JWT,一种基于JSON的开放标准,便于从资源服务器获取资源,也可被加密
    • 优点:

         -JWT是无状态的,不需要客户端保存客户会话信息,减轻服务器读取压力,同时易于扩展、易于分布式部署  
        -跨语言
        -便于传输,构成简单,字节占用空间少
        -自身构成有payload部分,可以存储一下业务逻辑相关的非敏感信息

    • 三段信息组成,"."连接:

        (1)eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.

        (2)eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.

        (3)TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

    • 三部分组成

      (1)Header 头部

       -两部分:类型(如 "typ":"JWT")和加密算法(如"alg":"HS256")也可以添加其它自定义的一些参数,然后对这个对象机型base64编码,生成一段字符串。

      {

          'typ': 'JWT',

          'alg': 'HS256'

       }  //Base64是一种编码,也就是说,它是可以被翻译回原来的样子来的。它并不是一种加密过程。

      (2)Pyload 负载

       用来存放一些业务但不敏感的信息

       默认声明:iss: jwt签发者、sub: jwt所面向的用户、aud: 接收jwt的一方、exp: jwt的过期时间,这个过期时间必须要大于签发时间、nbf: 定义在什么时间之前,该jwt都是不可用的.、 iat: jwt的签发时间、 jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。

      (3)Singnature 签名

       保存在服务器端三部分组成:header (base64后的)、payload (base64后的)、 secret

    在Asp.net WebAPI 中使用JWT

      (1)安装JWT包、创建JWT帮助类

    public  class JwtToken
    {
        //HMACSHA256加密
    static IJwtAlgorithm algorithm = new HMACSHA256Algorithm();
    //序列化和反序列
    static IJsonSerializer serializer = new JsonNetSerializer();
    //Base64编解码
    static IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder();
    //UTC时间获取
        static IDateTimeProvider provider = new UtcDateTimeProvider();        const string secret = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQK";//服务端
    [HttpGet]
        public string JiaM()
        {
            //设置过期时间(可以不设置,下面表示签名后 20分钟过期)
            double exp = (DateTime.UtcNow.AddMinutes(20) - new DateTime(1970, 1, 1)).TotalSeconds;
            var payload = new Dictionary<string, object>
            {
                { "UserId", 123 },
                { "UserName", "admin" },
                {"exp",exp }   //该参数也可以不写
            };
            //注意这个是额外的参数,默认参数是 typ 和alg
            var headers = new Dictionary<string, object>
            {
                { "typ1", "1234" },
                { "alg2", "admin" }
            };
            IJwtEncoder encoder = new JwtEncoder(algorithm, serializer, urlEncoder,algorithm);
            var token = encoder.Encode(headers, payload, secret);
            return token;
       }
       [HttpGet]
       public string JieM(string token)
       {
           try
           {
               //用于验证JWT的类
           IJwtValidator validator = new JwtValidator(serializer, provider);
    //用于解析JWT的类
               IJwtDecoder decoder = new JwtDecoder(serializer, validator, urlEncoder,);
               var json = decoder.Decode(token, secret, true);
               return json;
            }   
            catch (TokenExpiredException e)
            {
                //过期了自动进入这里
                return "Token has expired";
            }
            catch (SignatureVerificationException e)
            {
                //校验未通过自动进入这里
                return "Token has invalid signature";
            }
            catch (Exception e)
            {
                //其它错误,自动进入到这里
                return "other error";
            }

    (2)模拟登陆接口

    /// <summary>
    /// 模拟登陆
    /// </summary>
    [HttpGet]
    public string Login1(string userAccount, string pwd)
    {
        try
        {
            //这里模拟数据操作,只要是admin和123456就验证通过
            if (userAccount == "admin" && pwd == "123456")
            {
                 //1. 进行业务处理(这里模拟获取userId)
                 string userId = "0806";
                 //过期时间(可以不设置,下面表示签名后 20分钟过期)
                 double exp = (DateTime.UtcNow.AddMinutes(20) - new DateTime(1970, 1, 1)).TotalSeconds;
                 //进行组装
                 var payload = new Dictionary<string, object>
                 {
                      {"userId", userId },
                      {"userAccount", userAccount },
                      {"exp",exp }
                 };
                 //2. 进行JWT签名
                 var token = JWTHelp.JWTJiaM(payload);
                 var result = new { result = "ok", token = token };
                 return JsonConvert.SerializeObject(result);
             }
             else
             {
                  var result = new { result = "error", token = "" };
                  return JsonConvert.SerializeObject(result);
             }
         }
         catch (Exception)
         {
             var result = new { result = "error", token = "" };
             return JsonConvert.SerializeObject(result);
         }
    }

    (3)客户端调用登陆接口

    //1.登录
    $('#j_jwtLogin').on('click', function () {
          $.Ajax({
    url: "/api/Seventh/Login1",
    type: “get”,
    data: { userAccount: "admin", pwd: "123456" },
    success: function (data) {
                 var jsonData = JSON.parse(data);
                 if (jsonData.result == "ok") {
                      console.log(jsonData.token);
                      //存放到本地缓存中
                      window.localStorage.setItem("token", jsonData.token);
                      alert("登录成功,ticket=" + jsonData.token);
                 } else {
                       alert("登录失败");
                 }
             });
    });

    (4)服务器端过滤器

      两种获取header中信息的方式:校验通过的话,则使用 actionContext.RequestContext.RouteData.Values.Add("auth", result); 进行解密值的存储,方便后续action的直接获取。

    /// <summary>
    /// 验证JWT算法的过滤器
    /// </summary>
    public class JWTCheck : AuthorizeAttribute
    {
         public override void OnAuthorization(HttpActionContext actionContext)
         {
              //获取表头Header中值的几种方式
              //方式一:
              //{
              //    var authHeader2 = from t in actionContext.Request.Headers
              //                      where t.Key == "auth"
              //                      select t.Value.FirstOrDefault();
              //    var token2 = authHeader2.FirstOrDefault();
              //}
     
              //方式二:
              IEnumerable<string> auths;
              if (!actionContext.Request.Headers.TryGetValues("auth", out auths))
              {
                  //HttpContext.Current.Response.Write("报文头中的auth为空");
                  //返回状态码验证未通过,并返回原因(前端进行401状态码的捕获),注意:这句话并不能阶段该过滤器,还会继续往下走,要借助if-else        
                  actionContext.Response = actionContext.Request.
    CreateErrorResponse(HttpStatusCode.Unauthorized,
    new HttpError("报文头中的auth为空"));
              }
              else
              {
                  var token = auths.FirstOrDefault();
                  if (token != null)
                  {
                      if (!string.IsNullOrEmpty(token))
                      {
                          var result = JWTHelp.JWTJieM(token);
                           if (result == "expired")
                           {
                               //返回状态码验证未通过,并返回原因(前端进行401状态码的捕获)        
                               actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.Unauthorized, new HttpError("expired"));
                             }
                           else if (result == "invalid")
                           {
                               //返回状态码验证未通过,并返回原因(前端进行401状态码的捕获)   
                               actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.Unauthorized, new HttpError("invalid"));
                           }
                           else if (result == "error")
                           {
                               //返回状态码验证未通过,并返回原因(前端进行401状态码的捕获)   
                               actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.Unauthorized, new HttpError("error"));
                           }
                           else
                           {
                               //表示校验通过,用于向控制器中传值
                                 actionContext.RequestContext.RouteData.Values.Add("auth", result);
                           }
                       }
                   }
                   else
                   {
                       //返回状态码验证未通过,并返回原因(前端进行401状态码的捕获)   
                       actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.Unauthorized, new HttpError("token 空"));
                   }
               }        
    }
    }

    (5)服务器端获取信息的方法

    /// <summary>
    /// 加密后的获取信息
    /// </summary>
    [JWTCheck]
    [HttpGet]
    public string GetInfor()
    {
         var userData = JsonConvert.DeserializeObject<userData>(RequestContext.RouteData.Values["auth"].ToString()); ;
         if (userData == null)
         {
              var result = new { Message = "error", data = "" };
              return JsonConvert.SerializeObject(result);
         }
         else
         {
              var data = new { userId = userData.userId, userAccount = userData.userAccount };
              var result = new { Message = "ok", data =data };
              return JsonConvert.SerializeObject(result);
         }
    }

    (6)客户端调用获取信息方法

    //2.获取信息
    $('#jwtGetInfor').on('click', function () {
         //从本地缓存中读取token值
         var token = window.localStorage.getItem("token");
         $.ajax({
             url: "/api/Seventh/GetInfor",
             type: "Get",
             data: {},
             datatype: "json",
             //设置header的方式1
             headers: { "auth": token},
             //设置header的方式2
             //beforeSend: function (xhr) {
                  //    xhr.setRequestHeader("auth", token)
             //},           
             success: function (data) {
                  console.log(data);
                  var jsonData = JSON.parse(data);
                  if (jsonData.Message == "ok") {
                      var myData = jsonData.data;
                      console.log("获取成功");
                      console.log(myData.userId);
                      console.log(myData.userAccount);
                  } else {
                      console.log("获取失败");
                  }              
              },
              //当安全校验未通过的时候进入这里
              error: function (xhr) {
                  if (xhr.status == 401) {
                      console.log(xhr.responseText);
                      var jsonData = JSON.parse(xhr.responseText); //通过xhr.responseText获取详细的值进行判断。
                      console.log("授权失败,原因为:" + jsonData.Message);
                  }
               }
           });
    });
  • 相关阅读:
    UEmacs/PK Keybindings
    ubunut install golang
    vyos ipsec l2tp simple experiment
    Lynx 命令行操作
    Linux开启路由转发功能
    PROTEUS的元器件及模型制作
    5G模拟网自动化测试的需求和挑战
    vyos site-to-site ipsec simple experiment
    kubeadm 搭建 k8s 时用到的常用命令汇总
    html 特殊符号标记
  • 原文地址:https://www.cnblogs.com/shishixiang/p/14009699.html
Copyright © 2011-2022 走看看