基于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);
              }
           }
       });
});
原文地址:https://www.cnblogs.com/shishixiang/p/14009699.html