接口鉴权之sign签名校验与JWT验证

优质文章,第一时间送达

作者 |  那一片蓝海

来源 |  urlify.cn/FJjmqa

需求描述:

  项目里的几个Webapi接口需要进行鉴权,同接口可被小程序或网页调用,小程序里没有用户登录的概念,网页里有用户登录的概念,对于调用方来源是小程序的情况下进行放权,其他情况下需要有身份验证。也就是说给所有小程序请求进行放行,给网页请求进行jwt身份验证。由于我的小程序没有用户登录的功能,所以要针对小程序和网页设计出两套完全不同的鉴权方式。

鉴权流程设计:

  查阅相关资料,最终决定的鉴权方式:

  • 小程序采用sign签名检验

  • 网页采用目前比较流行的JWT的token校验

通过AOP的思想使用.Net的Attribute进行拦截请求

 代码实现

  主要是服务端写一个Attribute,判断是小程序还是网页,然后采用不同的两种不同的鉴权方式。

  • Attribute代码:

public class WxAllowFilterAttribute : BaseActionFilter
    {
        private static readonly int _errorCode = 401;
        public override void OnActionExecuting(HttpActionContext filterContext)
        {
            var iswx = filterContext.iswx();//判断是否是小程序发来的请求
            if (iswx)
            {          //小程序的签名校验
                if (!filterContext.checkwx()) {
                    filterContext.Response = Error('小程序签名验证失败', _errorCode);
                };
            }
            else
            {          //JWT的token校验
                string token = filterContext.GetToken();
                if (string.IsNullOrEmpty(token))
                {
                    filterContext.Response = Error('缺少token', _errorCode);
                    return;
                }
                if (!JWTHelper.CheckToken(token, JWTHelper.JWTSecret))
                {
                    filterContext.Response = Error('token校验失败!', _errorCode);
                    return;
                }

var payload = JWTHelper.GetPayload<JWTPayload>(token);
                if (payload.Expire < DateTime.Now)
                {
                    filterContext.Response = Error('token过期!', _errorCode);
                    return;
                }
                base.OnActionExecuting(filterContext);
            }
        }
    }

  • 扩展类

public static class HttpRequest    {        public static readonly string wx_secret = ConfigurationManager.AppSettings['wx_secret'];        /// <summary>        /// 获取Token        /// </summary>        /// <param name='req'>请求</param>        /// <returns></returns>        public static string GetToken(this HttpActionContext req)        {            string tokenHeader = req.Request.Headers.Authorization == null ? '' : req.Request.Headers.Authorization.Parameter;            if (string.IsNullOrEmpty(tokenHeader))                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 (string.IsNullOrEmpty(token))                throw new Exception('token不能为空!');            return token;        }        /// <summary>        /// 判断是否微信        /// </summary>        /// <param name='req'></param>        /// <returns></returns>        public static bool iswx(this HttpActionContext req)        {            var queryList = req.Request.RequestUri.Query.Split('&').ToList<string>();            Dictionary<String, String> pList = new Dictionary<String, String>();            if (queryList.Count < 2)            {                return false;            }            else            {                queryList.ForEach(x =>                {                    var a = x.Split('=');                    if (a.Count() >= 2)                    {                        pList.Add(a[0], a[1]);                    }                });                var iswx = pList.Any(x => x.Key == 'app_key' && x.Value == 'wx');//判断是否有微信标识的字段                return iswx;            }

        }        /// <summary>        /// 检验微信sign是否合法        /// </summary>        /// <param name='req'></param>        /// <returns></returns>        public static bool checkwx(this HttpActionContext req)        {            var queryList = req.Request.RequestUri.Query.Split('&').ToList<string>();            Dictionary<String, String> pList = new Dictionary<String, String>();            queryList.ForEach(x =>            {                var a = x.Split('=');                if (a.Count() >= 2)                {                    pList.Add(a[0], a[1]);                }            });            var app_key = pList['app_key'];            var app_secret = wx_secret;            var timetamp = pList['timestamp'];            var sign = pList['sign'];            if (!string.IsNullOrEmpty(timetamp)) {                 var tamp=Convert.ToInt64(timetamp);                var nowtamp = ToTimestamp(DateTime.Now);                var a = nowtamp-tamp;                if (a >= 15) {                    return false;                }            }            StringBuilder sb = new StringBuilder();            sb.Append(app_key);            sb.Append(app_secret);            sb.Append(timetamp);            var newsign = GetMD5(sb.ToString());            return newsign == sign;

        }        public static string GetMD5(string sDataIn)        {            MD5CryptoServiceProvider provider = new MD5CryptoServiceProvider();            byte[] bytes = Encoding.UTF8.GetBytes(sDataIn);            byte[] buffer2 = provider.ComputeHash(bytes);            provider.Clear();            string str = '';            for (int i = 0; i < buffer2.Length; i++)            {                str = str + buffer2[i].ToString('X').PadLeft(2, '0');            }            return str.ToLower();        }

        public static long ToTimestamp(this DateTime target)        {            return (target.ToUniversalTime().Ticks - 621355968000000000) / 10000000;        }    }
  • Filter基类

public class BaseActionFilter : ActionFilterAttribute
    {
        //public virtual void OnActionExecuting(HttpActionContext filterContext)
        //{
        //}
        //public virtual void OnActionExecuted(HttpActionContext filterContext)
        //{
        //}
        /// <summary>
        /// 返回JSON
        /// </summary>
        /// <param name='json'>json字符串</param>
        /// <returns></returns>
        public HttpResponseMessage JsonContent(string json)
        {
            var content = new StringContent(json, Encoding.UTF8, 'application/json');
            return new HttpResponseMessage { Content = content, StatusCode = HttpStatusCode.OK };
        }
        public HttpResponseMessage IsSuccess()
        {
            AjaxResult res = new AjaxResult
            {
                IsSuccess = true,
                Msg = '请求成功!'
            };

return JsonContent(JsonHelper.SerializeObject(res));
        }

/// <summary>
        /// 返回成功
        /// </summary>
        /// <param name='msg'>消息</param>
        /// <returns></returns>
        public HttpResponseMessage IsSuccess(string msg)
        {
            AjaxResult res = new AjaxResult
            {
                IsSuccess = true,
                Msg = msg
            };

return JsonContent(JsonHelper.SerializeObject(res));
        }

/// <summary>
        /// 返回成功
        /// </summary>
        /// <param name='data'>返回的数据</param>
        /// <returns></returns>
        public HttpResponseMessage IsSuccess<T>(T data)
        {
            AjaxResult<T> res = new AjaxResult<T>
            {
                IsSuccess = true,
                Msg = '请求成功!',
                Data = data
            };

return JsonContent(JsonHelper.SerializeObject(res));
        }

/// <summary>
        /// 返回错误
        /// </summary>
        /// <returns></returns>
        public HttpResponseMessage Error()
        {
            AjaxResult res = new AjaxResult
            {
                IsSuccess = false,
                Msg = '请求失败!'
            };

return JsonContent(JsonHelper.SerializeObject(res));
        }

/// <summary>
        /// 返回错误
        /// </summary>
        /// <param name='msg'>错误提示</param>
        /// <returns></returns>
        public HttpResponseMessage Error(string msg)
        {
            AjaxResult res = new AjaxResult
            {
                IsSuccess = false,
                Msg = msg,
            };

return JsonContent(JsonHelper.SerializeObject(res));
        }

/// <summary>
        /// 返回错误
        /// </summary>
        /// <param name='msg'>错误提示</param>
        /// <param name='errorCode'>错误代码</param>
        /// <returns></returns>
        public HttpResponseMessage Error(string msg, int errorCode)
        {
            AjaxResult res = new AjaxResult
            {
                IsSuccess = false,
                Msg = msg,
                StatusCode = errorCode
            };

return JsonContent(JsonHelper.SerializeObject(res));
        }
    }

  • JWT扩展类

public class JWTHelper    {        private static readonly string _headerBase64Url = '{\'alg\':\'HS256\',\'typ\':\'JWT\'}'.Base64UrlEncode();        public static readonly string JWTSecret = ConfigurationManager.AppSettings['JWTSecret'];        /// <summary>        /// 生成Token        /// </summary>        /// <param name='payloadJsonStr'>数据JSON字符串</param>        /// <param name='secret'>密钥</param>        /// <returns></returns>        public static string GetToken(string payloadJsonStr, string secret)        {            string payloadBase64Url = payloadJsonStr.Base64UrlEncode();            StringBuilder sb = new StringBuilder();            StringBuilder sb1 = new StringBuilder();            sb.AppendFormat('{0}', _headerBase64Url);            sb.Append('.');            sb.AppendFormat('{0}', payloadBase64Url);            sb1 = sb;            string sign = sb.ToString().ToHMACSHA256String(secret);

            string token = sb1.AppendFormat('.{0}', sign).ToString();

            return token;        }

        /// <summary>        /// 获取Token中的数据        /// </summary>        /// <typeparam name='T'>泛型</typeparam>        /// <param name='token'>token</param>        /// <returns></returns>        public static T GetPayload<T>(string token)        {            if (string.IsNullOrEmpty(token))            {                return default(T);            }            return token.Split('.')[1].Base64UrlDecode().ToObject<T>();        }

        /// <summary>        /// 校验Token        /// </summary>        /// <param name='token'>token</param>        /// <param name='secret'>密钥</param>        /// <returns></returns>        public static bool CheckToken(string token, string secret)        {            var items = token.Split('.');            var oldSign = items[2];            StringBuilder sb = new StringBuilder();            sb.AppendFormat('{0}', items[0]);            sb.AppendFormat('.{0}', items[1]);            string newSign = sb.ToString().ToHMACSHA256String(secret);            return oldSign == newSign;        }    }

检验用户名密码是否正确的业务接口代码这里不贴了..

网页客户端的代码还没写完,主要思路就是判断缓存里是否有token,没有就去把用户名密码去调用服务端的登录接口拿到token后存到缓存里,之后的所有请求都在头部带上这个token。

小程序客户端代码 :

在app.js中定义一个公共的promise请求方法,并带上请求的参数(app_key,时间戳,md5加密后的sign等),这里要注意区分get和post请求的区别,get是放在url后的,post是放在body里的,要对传参的格式要稍加处理

request(params) {
    reqTime++;
    //加载弹框
    wx.showLoading({
      title: '加载中...',
      mask: true
    });
    //返回
    return new Promise((resolve, reject) => {
      var data = {
        app_key: this.globalData.app_key,
        timestamp: Math.round(new Date() / 1000),
        sign: ''
      }
      data.sign = utilMd5.hexMD5(`${this.globalData.app_key}${this.globalData.app_secret}${data.timestamp}`)
      if (params.method.toUpperCase() == 'POST') {
        if (!params.url.includes('?')) {
          params.url += '?'
        }
        var url = `&app_key=${this.globalData.app_key}&timestamp=${data.timestamp}&sign=${data.sign}`
        params = {
          ...params,
          url: params.url + url
        }
        data = params.data
      } else {
        data = {
          ...params.data,
          ...data
        }
      }
      params = {
        ...params,
        data: {
          ...data
        }
      }
      wx.request({
        //解构params获取请求参数
        ...params,
        success: (result) => {
          resolve(result);
        },
        fail: (err) => {
          reject(err);
        },
        complete: () => {
          reqTime--;
          //停止加载
          if (!reqTime)
            wx.hideLoading();
        }
      });
    });
  }

这边说明下,我的app_key和app_secret都是写在app.js里的公共变量中的,app_key在url里是暴露的,但是app_secret是绝不能被暴露的。光知道app_key是无法生成正确的sign的,必须app_key,app_secret和timestap三者的加密才能生成正确的sign。我把app_secret写在app.js中可能不是安全的做法,但是通过请求服务器去获取app.secret又要面临网络请求的安全问题,最多对字符串进行加密解密,但也不能说绝对安全了。app_secret怎么处理最安全我目前也没想到很好的办法。。

好了,以上就是小程序的鉴权方法,小程序客户端在请求时只需要调用这个公共方法就行。

鉴权测试结果

  • 给控制器或者方法前面加上鉴权的特性[WxAllowFilter]

  • PostMan直接调用不带任何sign等参数

  • 伪造小程序参数签名验证失败或者时间戳超过10秒

  • 小程序内调用

锋哥最新SpringCloud分布式电商秒杀课程发布

👇👇👇

(0)

相关推荐

  • 5 分钟,带你快速撸一个 iOS App

    5 分钟,带你快速撸一个 iOS App

  • Python爬虫实战练习:爬取微信公众号文章

    前言 本文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,版权归原作者所有,如有问题请及时联系我们以作处理. 作者:徐洲更 为了实现该爬虫我们需要用到如下工具 Chrome浏览器 Py ...

  • 公司这套架构统一处理 try...catch 这么香,求求你不要再满屏写了,再发现扣绩效!

    设为"星标",重磅干货,第一时间送达 作者:小李子说程序 www.toutiao.com/i6878184496945070604 前言 软件开发springboot项目过程中,不 ...

  • .Net之Swagger基础使用

    今天 以下文章来源于鹏祥 ,作者AZRNG 鹏祥分享.Net相关技术文章,一起学习一起成长 介绍 Swagger 是一个规范和完整的框架,用于生成.描述.调用和可视化 RESTful 风格的 Web ...

  • 前后端接口鉴权全解 Cookie/Session/Token 的区别

    今天 作者:ssshooter 不知不觉也写得比较长了,一次看不完建议收藏夹!本文主要解释与请求状态相关的术语(cookie.session.token)和几种常见登录的实现方式,希望大家看完本文后可 ...

  • python笔记44-HTTP对外接口sign签名

    前言 一般公司对外的接口都会用到sign签名,对不同的客户提供不同的apikey ,这样可以提高接口请求的安全性,避免被人抓包后乱请求. sign签名是一种很常见的方式 sign签名 签名参数sign ...

  • 编程 | http 四种鉴权方式简介,未来可能还会出现第 5 种鉴权方式:全息生物验证

    因为 http 协议,它是一种无状态的协议,在服务器端并不知道客户的那一头,是谁在请求服务器.而服务器上的资源,有时候并不是向所有人开放的,而是仅对部分人开放的,在这种情况下,实现用户的登陆鉴权,就成 ...

  • 开放api接口签名验证,添加sign,时间戳

    你在写开放的API接口时是如何保证数据的安全性的?先来看看有哪些安全性问题在开放的api接口中,我们通过http Post或者Get方式请求服务器的时候,会面临着许多的安全性问题,例如: 请求来源(身 ...

  • 汽车里的通讯鉴权—Sec OC

    什么是SecOC? SecOC是Secure Onboard Communication的简称,是AUTOSAR从Classic Platform 4.2开始新增的一个基础软件(BSW)模块,为汽车嵌 ...

  • 基于jwt token机制鉴权架构

    常见的鉴权方式有两种,一种是基于session,另一种是基于token方式的鉴权,我们来浅谈一下两种 鉴权方式的区别. 两种鉴权方式对比 session 安全性:session是基于cookie进行用 ...

  • 【学术论文】面向云计算服务的鉴权协议研究

    摘要: 5G通信技术的快速落地,面向云计算服务的系统迎来了全新机遇与挑战,用户通过移动泛在网络可在任何时间.任何地点远程登录该系统获取所需的云计算服务.面向云计算服务的系统的鉴权协议能够实现用户远程服 ...

  • .NET core webApi 使用JWT验证签名

    一.为什么使用JWT 1.跨语言使用. 2.服务器端无需再保存任何东西,只需要客户端保存token就可以. 3.实现简单. 4.统一认证方式,如果是移动端也要验证的话,jwt也支持就无需修改,否则客户 ...

  • Hex文件校验生成和验证

    手机升级系统,下载完系统都会存在一个校验过程: 其实,在MCU升级过程中,也是需要校验hex文件,如果要想升级更可靠,还可以在hex或bin升级文件后再增加校验信息. 我们之前公司做的一个远程升级的产 ...