首先说一下具体思路和认证的过程:
1.客户端登录成功后生成令牌token,并在服务器保存token,然后返回给客户端。
2.客户端用时间戳+随机数+数据=数字签名,通过加密算法生成数字签名。
3.将token、用户ID、时间戳、随机数、数据、数字签名,保存到http的header中,通过接口提交给服务器。
4.由服务器验证访问的合法性。包括:token的有效性和是否过期、屏蔽爬虫、数字签名的真实性。也可加入系统级判断:是否有权限访问。
一、客户端登录
客户端通过jquery对API接口发出请求:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title></title>
<script src="Scripts/jquery-3.4.1.min.js"></script>
<script type="text/javascript">
//早期浏览器版本跨域设置
jQuery.support.cors = true;
var token = "";
function login() {
$.ajax({
type: "get",
url: "http://localhost:44338/api/Login?uid=123&pwd=123&usertype=user",
success: function (data, status) {
token = data.Data;
alert(data.Info);
},
error: function (e) {
console.log(e)
}
});
}
</script>
</head>
服务器端API接口方法:
public class LoginController : AnonyController
{
[HttpGet]
public IHttpActionResult Login(string uid,string pwd,string usertype)
{
ResultMsg result = null;
if (uid=="123"&&pwd=="123")
{
string token = Guid.NewGuid().ToString();
//token失效时间
var timeout = DateTime.Now.AddDays(1);
//添加token
CacheManager.TokenInsert(token, uid, usertype, timeout);
result = new ResultMsg { StatusCode = 1, Info = "登录成功",Data=token };
}
else
{
result = new ResultMsg { StatusCode = 0, Info = "登录失败,账号或密码错误",Data=null };
}
return Json<ResultMsg>(result);
}
}
/// <summary>
/// 添加令牌
/// </summary>
/// <param name="token">令牌</param>
/// <param name="uuid">用户ID凭证</param>
/// <param name="userType">用户类别</param>
/// <param name="timeout">过期时间</param>
public static void TokenInsert(string token, object uuid, string userType, DateTime timeout)
{
CacheInit();
// token不存在则添加
if (!TokenIsExist(token))
{
DataTable dt = (DataTable)HttpRuntime.Cache["PASSPORT.TOKEN"];
DataRow dr = dt.NewRow();
dr["token"] = token;
dr["uuid"] = uuid;
dr["userType"] = userType;
dr["timeout"] = timeout;
dt.Rows.Add(dr);
HttpRuntime.Cache["PASSPORT.TOKEN"] = dt;
//保存进数据库
//tempCacheService.Add_TempCaches(new List<TempCacheDTO_ADD>()
//{
// new TempCacheDTO_ADD()
// {
// EndTime = timeout,
// UserAccountId = new Guid(uuid.ToString()),
// UserToken = new Guid(token),
// UserType = (UserType)Enum.Parse(typeof(UserType),userType)
// }
//});
}
// token存在则更新过期时间
else
{
TokenTimeUpdate(token, timeout);
//更新数据库
//tempCacheService.Update_TempCaches(new Guid(token), timeout);
}
}
完成了对登录的请求、服务器token的保存、返回给客户端token操作。
二、客户端生成数字签名
生成数字签名需要:时间戳、随机数、页面提交的数据。时间戳:用于防止爬虫重复提交。页面提交的数据:用于防止爬虫篡改数据,随机数算是一个变量因子。客户端与服务器端生成数字签名的算法要一致。客户端数字签名需要与服务器的数字签名对比来识别提交的数据是否被篡改。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title></title>
<script src="jquery-3.4.1.min.js"></script>
<script src="jquery.md5.js"></script>
<script type="text/javascript">
//早期浏览器版本跨域设置
jQuery.support.cors = true;
var token = "";
var uid = "123";
function login() {
$.ajax({
type: "get",
url: "http://localhost:44338/api/Login?uid=" + uid + "&pwd=123&usertype=user",
success: function (data, status) {
token = data.Data;
alert(data.Info);
},
error: function (e) {
console.log(e)
}
});
}
function Getdata() {
//数据
var jsonparam = { name1: "数据1", name2: "数据2", name3: "数据3" };
var url = "http://localhost:44338/api/send/Getdata";
GetAPI(jsonparam, url);
}
function GetAPI(jsonparam, url) {
//时间戳
var timestamp = new Date().getTime().toString();
//随机数
var nonce = Math.round(Math.random() * 100000000).toString();
//拼接参数
var builder = [];
var query = [];
for (var val in jsonparam) {
builder.push(val);
builder.push(jsonparam[val]);
query.push(val + "=");
query.push(encodeURIComponent(jsonparam[val]) + "&");
}
var data = builder.join('');
var querystring = query.join('');
querystring = querystring.substring(0, querystring.length - 1);
//数字签名
var signatureStr = encodeURIComponent(timestamp + nonce + uid + token + data).toUpperCase();
var signature = $.md5(signatureStr).toUpperCase();
$.ajax({
type: "get",
url: url + "?" + querystring,
beforeSend: function (request) {
request.setRequestHeader("timestamp", timestamp);
request.setRequestHeader("nonce", nonce);
request.setRequestHeader("signature", signature);
request.setRequestHeader("uid", uid);
request.setRequestHeader("token", token);
},
success: function (data, status) {
alert(data);
},
error: function (e) {
console.log(e)
}
});
}
</script>
</head>
<body>
<input id="Button1" type="button" onclick="login();" value="登录" />
<input id="Button3" type="button" onclick="Getdata();" value="GET请求数据" />
</body>
</html>
三、服务器端验证合法性
在调用请求的API接口之前,系统首先进入ActionFilterAttribute类进行拦截,所以我们定义一个类继承此类。可以作为接口拦截器来判断http请求的合法性。上服务器代码如下:
/// <summary>
/// 调用接口过滤器
/// </summary>
public class ApiSecurityAttribute : ActionFilterAttribute
{
/// <summary>
/// 判断http请求的合法性
/// </summary>
/// <param name="actionContext"></param>
public override void OnActionExecuting(System.Web.Http.Controllers.HttpActionContext actionContext)
{
ResultMsg resultMsg = null;
var request = actionContext.Request;
string method = request.Method.Method;
string token = string.Empty,
uid = String.Empty,
timestamp = string.Empty,
nonce = string.Empty,
signature = string.Empty;
if (request.Headers.Contains("token"))
{
token = HttpUtility.UrlDecode(request.Headers.GetValues("token").FirstOrDefault());
}
if (request.Headers.Contains("uid"))
{
uid = HttpUtility.UrlDecode(request.Headers.GetValues("uid").FirstOrDefault());
}
if (request.Headers.Contains("timestamp"))
{
timestamp = HttpUtility.UrlDecode(request.Headers.GetValues("timestamp").FirstOrDefault());
}
if (request.Headers.Contains("nonce"))
{
nonce = HttpUtility.UrlDecode(request.Headers.GetValues("nonce").FirstOrDefault());
}
if (request.Headers.Contains("signature"))
{
signature = HttpUtility.UrlDecode(request.Headers.GetValues("signature").FirstOrDefault());
}
//判断请求头是否包含以下参数
if (string.IsNullOrEmpty(token) ||
string.IsNullOrEmpty(uid) ||
string.IsNullOrEmpty(timestamp) ||
string.IsNullOrEmpty(nonce) ||
string.IsNullOrEmpty(signature))
{
resultMsg = new ResultMsg();
resultMsg.StatusCode = (int)StatusCodeEnum.ParameterError;
resultMsg.Info = StatusCodeEnum.ParameterError.GetEnumText();
resultMsg.Data = "";
actionContext.Response = HttpResponseExtension.ToJson(JsonConvert.SerializeObject(resultMsg));
base.OnActionExecuting(actionContext);
return;
}
//判断timespan是否有效
double ts1 = 0;
double ts2 = (DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0)).TotalMilliseconds;
bool timespanvalidate = double.TryParse(timestamp, out ts1);
double ts = ts2 - ts1;
//超过两分钟重复提交无效
bool falg = ts > int.Parse(WebSettingsConfig.UrlExpireTime) * 1000;//时间间隔2分钟
if (falg || (!timespanvalidate))
{
resultMsg = new ResultMsg();
resultMsg.StatusCode = (int)StatusCodeEnum.URLExpireError;
resultMsg.Info = StatusCodeEnum.URLExpireError.GetEnumText();
resultMsg.Data = "";
actionContext.Response = HttpResponseExtension.ToJson(JsonConvert.SerializeObject(resultMsg));
base.OnActionExecuting(actionContext);
return;
}
//判断token是否有效
Token mytoken = (Token)HttpRuntime.Cache.Get(token);
string signtoken = string.Empty;
if (!CacheManager.TokenIsExist(token))
{
resultMsg = new ResultMsg();
resultMsg.StatusCode = (int)StatusCodeEnum.TokenInvalid;
resultMsg.Info = StatusCodeEnum.TokenInvalid.GetEnumText();
resultMsg.Data = "";
actionContext.Response = HttpResponseExtension.ToJson(JsonConvert.SerializeObject(resultMsg));
base.OnActionExecuting(actionContext);
return;
}
bool result = false;
switch (method)
{
case "POST":
Stream stream = HttpContext.Current.Request.InputStream;
string responseJson = string.Empty;
StreamReader streamReader = new StreamReader(stream);
result = SignExtension.ValidatePost(timestamp, nonce, uid, token, signature);
break;
case "GET":
result = SignExtension.ValidateGet(timestamp,nonce,uid,token,signature);
break;
default:
resultMsg = new ResultMsg();
resultMsg.StatusCode = 1;
resultMsg.Info ="";
resultMsg.Data =null;
actionContext.Response = HttpResponseExtension.ToJson(JsonConvert.SerializeObject(resultMsg));
base.OnActionExecuting(actionContext);
return;
}
if (!result)
{
resultMsg = new ResultMsg();
resultMsg.StatusCode = (int)StatusCodeEnum.HttpRequestError;
resultMsg.Info = StatusCodeEnum.HttpRequestError.GetEnumText();
resultMsg.Data = "";
actionContext.Response = HttpResponseExtension.ToJson(JsonConvert.SerializeObject(resultMsg));
base.OnActionExecuting(actionContext);
return;
}
else
{
base.OnActionExecuting(actionContext);
}
}
/// <summary>
///
/// </summary>
/// <param name="actionExecutedContext"></param>
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
base.OnActionExecuted(actionExecutedContext);
}
}
文章相关源码下载地址:源码下载,希望对大家有帮助,谢谢。欢迎留言指正!!!