百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 热门文章 > 正文

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

bigegpt 2024-08-07 17:47 2 浏览

作者 | 那一片蓝海

来源 | 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 (.*?)#34;;
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}×tamp=${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秒
  • 小程序内调用



相关推荐

VS Code上也能玩转Jupyter Notebook,这是一份完整教程

点击上方"码农真经"关注,星标或者置顶22点24分准时推送,第一时间送达来源:公众号机器之心|编辑:真经君码农真经(ID:coder_experience)第115次推文图源:...

看开发一款AR眼镜产品都需要那些东东?

开发AR眼镜应用时,使用的操作系统和编程语言主要取决于目标设备和开发平台。以下是主流AR眼镜的开发系统和语言总结:一、操作系统WindowsHolographic设备:微软HoloLens系列特点:...

ONNX Runtime 黑科技曝光:一行 C++ 代码让 ResNet 起飞,GitHub 已爆星

1.ONNXRuntime简介ONNXRuntime(ORT)是由微软开发的高性能推理引擎,支持跨平台(Windows/Linux/macOS)运行ONNX格式的深度学习模型。其核心优势...

海康工业相机SDK开发介绍

这篇文章,我来介绍一下海康工业相机SDK的使用方法。如果是老手,你可以跳过,如果是新手,可以看看,欢迎拍砖要使用海康的工业相机SDK,要先下载机器视觉工业相机SDKV3.2.0版本Runtime组...

深圳机器视觉相机USB3.0数字相机及千兆网口相机有什么特点?

四元数机器视觉相机的目的是将通过镜头投影到传感器的图像传送到能够储存、分析和(或者)显示的机器设备上。那么深圳机器视觉相机USB3.0数字相机及千兆网口相机有什么特点?相信不少人是有疑问的,今天深圳四...

蚁利智慧工地展厅控制系统:多媒体交互软件及中控系统开发方案

来源:蚁利科技以下是一篇关于隆兰智慧工地安全体验数字展厅多媒体互动软件及中控系统开发技术的技术方案框架,包含核心代码示例和实现思路:---#隆兰智慧工地安全体验数字展厅开发技术方案##一、系统架构...

C#程序员在工控行业到底有没有立足之地

当年忐忑的从互联网卷到工控圈时也一脸懵逼,结果发现咱C#程序员在工控界简直是万金油!今天就用我踩坑几年的经验给你们盘一盘转行路线。一、能撸哪些岗位?上位机开发工程师(工控圈入场券):天天和PLC、传感...

C#一行代码搞定OCR

本文将介绍如何使用C#代码来实现OCR功能。一、在VS中新建一个控制台应用,选择.net框架,.net6.0~8.0均可。创建好的项目如下图,然后在配置管理器中修改活动解决方案平台为x64,以及项目平...

在 C# WinForms 中 OpenCvSharp 进行边缘检测:Laplacian 算子介绍

边缘检测在计算机视觉和图像处理领域中具有非常重要的作用,它是物体识别、特征提取的基础。Laplacian算子是一种经典且常用的边缘检测方法,它对噪声非常敏感,能够很好地检测到图像中的细节和边缘。本文...

C# OpenCV机器视觉:对位贴合

在热闹非凡的手机维修街上,阿强开了一家小小的手机贴膜店。每天看着顾客们自己贴膜贴得歪歪扭扭,不是膜的边缘贴不整齐,就是里面充满了气泡,阿强心里就想:“要是我能有个自动贴膜的神器,那该多好啊,就可以让顾...

C# OpenCV机器视觉:缺陷检测

在一个阳光明媚的早晨,阿强正准备享受他的一杯咖啡,突然接到了老板的电话。“阿强,我们的生产线出现了问题!有几个产品的质量不合格,客户投诉不断!你能不能想办法解决这个问题?”阿强一听,心中一紧,随即灵光...

CHAPTER II 第二章

CHAPTERIICHAPTERII第二章Iresistedalltheway:anewthingforme...

万字图文,将&quot;类加载器&quot;与&quot;双亲委派机制&quot;一网打尽

引子大家想必都有过平时开发springboot项目的时候稍微改动一点代码,就得重启,就很烦网上一般介绍2种方式spring-boot-devtools,或者通过JRebel插件来实现"...

Java:Java中的微服务

  如果你想使用Java中的云原生微服务快速实现大规模可扩展性,那么不需要重新发明轮子。一些创新工具可以帮助你。通过Java培训课程,在Java(软件开发中最常用的编程语言)方面打下坚实的基础,更好地...

&quot;类加载器&quot;与&quot;双亲委派机制&quot;一网打尽

引子大家好,我是呼噜噜,大家想必都有过平时开发springboot项目的时候稍微改动一点代码,就得重启,就很烦网上一般介绍2种方式spring-boot-devtools,或者通过JRebel插件...