子曰:工欲善其事,必先利其器
github:https://github.com/redAntCpp/CSharpTools
紧接上篇,上篇只是实现了token的生成以及解析,顺便提到了验证机制。但是在实际业务中,通常需要对token进行时效性验证。就像去看电影,电影票当天有效,必须在有效期内进行使用,否则就需要重新买票。
token时效验证
总体思路
第一步中,有个代码:
if (isAPIUser(UserName, PassWord))
{
//这里应该验证有效期,暂时没写,后续提供思路
return true;
}
这里应该加上时效验证。那么如何实现token的时效性验证呢,这里提供两个思路:
思路1
- 设置一个有效期,让客户端每次调用服务时,带上第一次获取token的时间戳(加密)。
- 服务端取到这个时间后,进行解密,然后加上有效期,对比当前服务器的时间,大于则有效,小于则失效,
在IsAuthorized加上这一段即可
string requestTime = httpContext.Request.Headers["rtime"]; //请求时间经过DESC签名,头文件
if (string.IsNullOrEmpty(requestTime))return false;
DateTime Requestdt = DateTime.Parse(Decrypt.RSADecrypt(PrivateKey,requestTime)).AddMinutes(int.Parse(TimeStamp));
DateTime Newdt = DateTime.Now; //服务器接收请求的当前时间
if (Requestdt < Newdt) //token已过期
{
ErrorMessage = "token已过期,请重新获取";
return false;
}
思路2
- 当客户端获取token时,服务端记录获取token的时间与用户。
- 每当客户使用服务进入验证环节时,取出步骤1中的时间与用户id,加上有效期。
- 判断加上有效期后的时间与服务器的时间大小。
两个思路的区别在于谁记录token时间,当然安全性肯定是思路2更安全。因为,如果客户不按要求进行传时间,那么服务端是无法验证的。但是思路2也有弊端,每次都记录用户的token,存储空间消耗大,而且查询速度也会影响token验证速度。如果存放在数据库上,连接数据库的网速也是个需要考虑的问题。
两种方法各有利弊。
现在分享一下本人的思路2的一个改进方法,使用sqlite来进行token的存储,关于sqlite参考:私人工具集5——C#数据库操作类(DBHelper)
sqlite可以存储在本地,不用考虑网络问题,且结构简单,体积小,非常适合这种记录日志。
实现
- 创建SQLite本地表,T_APIUser,用来记录用户信息,字段参考如下:
- 创建本地表,T_TokenTrace,用来记录token生成时间以及状态。
- 在过滤器中,修改判断逻辑,如果用户验证通过,那么就获取最后一次该用户获取token的时间,并加上有效期,与现在时间比较。
if (APIUserID != -1)
{
//验证有效期
string TokenCreateTime = getTokenCreateTime(APIUserID);
DateTime Requestdt = DateTime.Parse(TokenCreateTime).AddMinutes(int.Parse(TimeStamp));
if (Requestdt < DateTime.Now)
{
ErrorMessage = "token已过期,请重新获取";
return false;
}
else
{
return true;
}
}
- . 这里补充一下辅助方法:
//辅助方法
//验证账号密码
private int isAPIUser(string UserNo, string PassWord)
{
Log.AddInfo("APIFilter.isAPIUser()", "Begin");
string sqlstr = "SELECT APIUserID FROM T_APIUser where UserNo = @UserNo and PassWord = @PassWord";
SQLiteParameter[] par = {
new SQLiteParameter("@UserNo", UserNo),
new SQLiteParameter("@PassWord",PassWord)
};
DataSet qry = site.SelectData(sqlstr, par);
if (qry.Tables.Count == 1 && qry.Tables[0].Rows.Count == 0)
{
Log.AddError("APIFilter.isAPIUser()", "APIUser账号不存在");
return -1;
}
else
{
Log.AddInfo("APIFilter.isAPIUser()", "End");
return Convert.ToInt32(qry.Tables[0].Rows[0]["APIUserID"].ToString());
}
}
private string getTokenCreateTime(int APIUserID)
{
string sqlStr = "SELECT CreateTime FROM T_TokenTrace where APIUserID = @APIUserID ORDER BY TokenTraceID DESC LIMIT 1";
SQLiteParameter[] par = {
new SQLiteParameter("@APIUserID", APIUserID),
};
DataSet qry = site.SelectData(sqlStr, par);
return qry.Tables[0].Rows[0]["CreateTime"].ToString();
}
这样就可以简单实现token的有效期验证了。
实际演练
- 获取token接口
- 在原来的token控制器中,加上一个测试用的接口,并加上我们的过滤器,程序会按顺序向下执行。
- 设置有效期为2分钟。我这边读取的是json中的配置:
- 验证token有效期内,接口是否能正常访问:
- 2分钟后再次调用此接口:
其他拓展
token一般每次都不一样,token的值跟加密的key相关,不同的key会产生不同的token。因此,如果想要每次获取的token都不一样,我们可以在配置key的时候,随机生成一个字符串作为key,用于保证token的随机性。要实现此功能,则不宜在配置文件中写入了,因为配置文件通常不进行修改。
在此就不再深入讨论。