Web API应用架构设计分析(2)
在上篇随笔《Web API应用架构设计分析(1)》,我对Web API的各种应用架构进行了概括性的分析和设计,Web API 是一种应用接口框架,它能够构建HTTP服务以支撑更广泛的客户端(包括浏览器,手机和平板电脑等移动设备)的框架,本篇继续这个主题,介绍如何利用ASP.NET Web API 来设计Web API层以及相关的调用处理。
1、Web API的接口访问分类
Web API接口的访问方式,大概可以分为几类:
1)一个是使用用户令牌,通过Web API接口进行数据访问。这种方式,可以有效识别用户的身份,为用户接口返回用户相关的数据,如包括用户信息维护、密码修改、或者用户联系人等与用户身份相关的数据。
2)一种是使用安全签名进行数据提交。这种方式提交的数据,URL连接的签名参数是经过安全一定规则的加密的,服务器收到数据后也经过同样规则的安全加密,确认数据没有被中途篡改后,再进行数据修改处理。因此我们可以为不同接入方式,如Web/APP/Winfrom等不同接入方式指定不同的加密秘钥,但是秘钥是双方约定的,并不在网络连接上传输,连接传输的一般是这个接入的AppID,服务器通过这个AppID来进行签名参数的加密对比,这种方式,类似微信后台的回调处理机制,它们就是经过这样的处理。
3)一种方式是提供公开的接口调用,不需要传入用户令牌、或者对参数进行加密签名的,这种接口一般较少,只是提供一些很常规的数据显示而已。
下面图示就是这几种接入方式的说明和大概应用场景。
2、Web API使用安全签名的实现
首先我们为用户注册的时候,需要由我们认可的终端发起,也就是它们需要进行安全签名,后台确认签名有效性,才能正常实现用户注册,否则遭到伪造数据,系统就失去原有的意义了。
- /// <summary>
- /// 注册用户信息接口
- /// </summary>
- public interface IUserApi
- {
- /// <summary>
- /// 注册用户处理,包括用户名,密码,身份证号,手机等信息
- /// </summary>
- /// <param name="json">注册用户信息</param>
- /// <param name="signature">加密签名字符串</param>
- /// <param name="timestamp">时间戳</param>
- /// <param name="nonce">随机数</param>
- /// <param name="appid">应用接入ID</param>
- /// <returns></returns>
- ResultData Add(UserJson json,
- string signature, string timestamp, string nonce, string appid);
- }
其实我们获得用户的令牌,也是需要进行用户安全签名认证的,这样我们才有效保证用户身份令牌获取的合法性。
- /// <summary>
- /// 系统认证等基础接口
- /// </summary>
- public interface IAuthApi
- {
- /// <summary>
- /// 注册用户获取访问令牌接口
- /// </summary>
- /// <param name="username">用户登录名称</param>
- /// <param name="password">用户密码</param>
- /// <param name="signature">加密签名字符串</param>
- /// <param name="timestamp">时间戳</param>
- /// <param name="nonce">随机数</param>
- /// <param name="appid">应用接入ID</param>
- /// <returns></returns>
- TokenResult GetAccessToken(string username, string password,
- string signature, string timestamp, string nonce, string appid);
- }
上面介绍到的参数,我们提及了几个参数,一个是加密签名字符串,一个是时间戳,一个是随机数,一个是应用接入ID,我们一般的处理规则如下所示。
1)Web API 为各种应用接入,如APP、Web、Winform等接入端分配应用AppID以及通信密钥AppSecret,双方各自存储。
2)接入端在请求Web API接口时需携带以下参数:signature、 timestamp、nonce、appid,签名是根据几个参数和加密秘钥生成。
3) Web API 收到接口调用请求时需先检查传递的签名是否合法,验证后才调用相关接口。
加密签名在服务端(Web API端)的验证流程参考微信的接口的处理方式,处理逻辑如下所示。
1)检查timestamp 与系统时间是否相差在合理时间内,如10分钟。
2)将appSecret、timestamp、nonce三个参数进行字典序排序
3)将三个参数字符串拼接成一个字符串进行SHA1加密
4)加密后的字符串可与signature对比,若匹配则标识该次请求来源于某应用端,请求是合法的。
C#端代码校验如下所示。
- /// <summary>
- /// 检查应用接入的数据完整性
- /// </summary>
- /// <param name="signature">加密签名内容</param>
- /// <param name="timestamp">时间戳</param>
- /// <param name="nonce">随机字符串</param>
- /// <param name="appid">应用接入Id</param>
- /// <returns></returns>
- public CheckResult ValidateSignature(string signature, string timestamp, string nonce, string appid)
- {
- CheckResult result = new CheckResult();
- result.errmsg = "数据完整性检查不通过";
- //根据Appid获取接入渠道的详细信息
- AppInfo channelInfo = BLLFactory<App>.Instance.FindByAppId(appid);
- if (channelInfo != null)
- {
- #region 校验签名参数的来源是否正确
- string[] ArrTmp = { channelInfo.AppSecret, timestamp, nonce };
- Array.Sort(ArrTmp);
- string tmpStr = string.Join("", ArrTmp);
- tmpStr = FormsAuthentication.HashPasswordForStoringInConfigFile(tmpStr, "SHA1");
- tmpStr = tmpStr.ToLower();
- if (tmpStr == signature && ValidateUtil.IsNumber(timestamp))
- {
- DateTime dtTime = timestamp.ToInt32().IntToDateTime();
- double minutes = DateTime.Now.Subtract(dtTime).TotalMinutes;
- if (minutes > timspanExpiredMinutes)
- {
- result.errmsg = "签名时间戳失效";
- }
- else
- {
- result.errmsg = "";
- result.success = true;
- result.channel = channelInfo.Channel;
- }
- }
- #endregion
- }
- return result;
- }
一旦我们完成对安全签名进行成功认证,也就是我们对数据提交的来源和完整性进行了确认,就可以进行更多和安全性相关的操作了,如获取用户的访问令牌信息的操作如下所示。
第一步是验证用户的签名是否符合要求,符合要求后进行用户信息的比对,并生成用户访问令牌数据JSON,返回给调用端即可。
3、Web API使用安全令牌的实现
通过上面的接口,我们获取到的用户访问令牌,以后和用户相关的信息调用,我们就可以通过这个令牌参数进行传递就可以了,这个令牌带有用户的一些基础信息,如用户ID,过期时间等等,这个Token的设计思路来源于JSON Web Token (JWT),具体可以参考http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html,以及GitHub上的项目https://github.com/jwt-dotnet/jwt。
由于Web API的调用,都是一种无状态方式的调用方式,我们通过token来传递我们的用户信息,这样我们只需要验证Token就可以了。
JWT的令牌生成逻辑如下所示
令牌生成后,我们需要在Web API调用处理前,对令牌进行校验,确保令牌是正确有效的。
检查的代码,就是把令牌生成的过程逆反过来,获取相应的信息,并且对令牌签发的时间进行有效性判断,一般可以约定一个失效时间,如1天或者7天,也不用设置太短。
- /// <summary>
- /// 检查用户的Token有效性
- /// </summary>
- /// <param name="token"></param>
- /// <returns></returns>
- public CheckResult ValidateToken(string token)
- {
- //返回的结果对象
- CheckResult result = new CheckResult();
- result.errmsg = "令牌检查不通过";
- if (!string.IsNullOrEmpty(token))
- {
- try
- {
- string decodedJwt = JsonWebToken.Decode(token, sharedKey);
- if (!string.IsNullOrEmpty(decodedJwt))
- {
- #region 检查令牌对象内容
- dynamic root = JObject.Parse(decodedJwt);
- string username = root.name;
- string userid = root.iss;
- int jwtcreated = (int)root.iat;
- //检查令牌的有效期,7天内有效
- TimeSpan t = (DateTime.UtcNow - new DateTime(, , ));
- int timestamp = (int)t.TotalDays;
- if (timestamp - jwtcreated > expiredDays)
- {
- throw new ArgumentException("用户令牌失效.");
- }
- //成功校验
- result.success = true;
- result.errmsg = "";
- result.userid = userid;
- #endregion
- }
- }
- catch (Exception ex)
- {
- LogTextHelper.Error(ex);
- }
- }
- return result;
- }
一般来说,访问令牌不能永久有效,对于访问令牌的重新更新问题,可以设置一个规则,只允许最新的令牌使用,并把它存储在接口缓存里面进行对比,应用系统退出的时候,就把内存里面的Token移除就可以了。
4、ASP.NET Web API的开发
上面我们定义了一般的Web API接口,以及实现相应的业务实现,如果我们需要创建Web API层,还需要构建一个Web API项目的。
创建好相应的项目后,可以为项目添加一个Web API基类,方便控制共同的接口。
然后我们就可以在Controller目录上创建更多的应用API控制器了。
最后我们为了统一所有的API接口都是返回JSON方式,我们需要对WebApiConfig里面的代码进行设置下。
- public static class WebApiConfig
- {
- public static void Register(HttpConfiguration config)
- {
- // Web API 配置和服务
- config.SetCorsPolicyProviderFactory(new CorsPolicyFactory());
- config.EnableCors();
- // Web API 路由
- config.MapHttpAttributeRoutes();
- config.Routes.MapHttpRoute(
- name: "DefaultApi",
- routeTemplate: "api/{controller}/{action}/{id}",
- defaults: new { action = "post", id = RouteParameter.Optional }
- );
- // Remove the JSON formatter
- //config.Formatters.Remove(config.Formatters.JsonFormatter);
- // Remove the XML formatter
- config.Formatters.Remove(config.Formatters.XmlFormatter);
- }
- }
5、Web API 接口的测试
接下来我们要做的就是需要增加业务接口,以便进行具体的测试了,建议使用Winform项目,对每个接口进行一个测试,或者也可以考虑使用单元测试的方式,看个人喜好吧。
例如我们如果要测试用户登陆的接口的话,我们的测试代码如下所示。
- /// <summary>
- /// 生成签名字符串
- /// </summary>
- /// <param name="appSecret">接入秘钥</param>
- /// <param name="timestamp">时间戳</param>
- /// <param name="nonce">随机数</param>
- private string SignatureString(string appSecret, string timestamp, string nonce)
- {
- string[] ArrTmp = { appSecret, timestamp, nonce };
- Array.Sort(ArrTmp);
- string tmpStr = string.Join("", ArrTmp);
- tmpStr = FormsAuthentication.HashPasswordForStoringInConfigFile(tmpStr, "SHA1");
- return tmpStr.ToLower();
- }
- private TokenResult GetTokenResult()
- {
- string timestamp = DateTime.Now.DateTimeToInt().ToString();
- string nonce = new Random().NextDouble().ToString();
- string signature = SignatureString(appSecret, timestamp, nonce);
- string appended = string.Format("&signature={0}×tamp={1}&nonce={2}&appid={3}", signature, timestamp, nonce, appId);
- string queryUrl = url + "Auth/GetAccessToken?username=test&password=123456" + appended;
- HttpHelper helper = new HttpHelper();
- string token = helper.GetHtml(queryUrl);
- Console.WriteLine(token);
- TokenResult tokenResult = JsonConvert.DeserializeObject<TokenResult>(token);
- return tokenResult;
- }
如果我们已经获得了令牌,我们根据令牌传递参数给连接,并获取其他数据的测试处理代码如下所示。
- //获取访问令牌
- TokenResult tokenResult = GetTokenResult();
- string queryUrl = url + "/Contact/get?token=" + tokenResult.access_token;
- HttpHelper helper = new HttpHelper();
- string result = helper.GetHtml(queryUrl);
- Console.WriteLine(result);
如果需要POST数据的话,那么调用代码如下所示。
- //使用POST方式
- var data = new
- {
- name = "张三",
- certno = "",
- };
- var postData = data.ToJson();
- queryUrl = url + "/Contact/Add?token=" + tokenResult.access_token;
- helper = new HttpHelper();
- helper.ContentType = "application/json";
- result = helper.GetHtml(queryUrl, postData, true);
- Console.WriteLine(result);
Web API后台,会自动把POST的JSON数据转换为对应的对象的。
如果是GET方式,我们可能可以直接通过浏览器进行调试,如果是POST方式,我们需要使用一些协助工具,如Fiddler等处理工具,但是最好的方式是自己根据需要弄一个测试工具,方便测试。
以下就是我为了自己Web API 接口开发的需要,专门弄的一个调试工具,可以自动组装相关的参数,包括使用安全签名的参数,还可以把所有参数数据进行存储。
Web API应用架构设计分析(2)的更多相关文章
- Web API应用架构设计分析(1)
Web API 是一种应用接口框架,它能够构建HTTP服务以支撑更广泛的客户端(包括浏览器,手机和平板电脑等移动设备)的框架, ASP.NET Web API 是一种用于在 .NET Framewor ...
- Web API应用架构在Winform混合框架中的应用(1)
在<Web API应用架构设计分析(1)>和<Web API应用架构设计分析(2)>中对WebAPI的架构进行了一定的剖析,在当今移动优先的口号下,传统平台都纷纷开发了属于自己 ...
- Web API应用架构在Winform混合框架中的应用(5)--系统级别字典和公司级别字典并存的处理方式
在我这个系列中,我主要以我正在开发的云会员管理系统为例进行介绍Web API的应用,由于云会员的数据设计是支持多个商家公司,而每个公司又可以包含多个店铺的,因此一些字典型的数据需要考虑这方面的不同.如 ...
- Web API应用架构在Winform混合框架中的应用(4)--利用代码生成工具快速开发整套应用
前面几篇介绍了Web API的基础信息,以及如何基于混合框架的方式在WInform界面里面整合了Web API的接入方式,虽然我们看似调用过程比较复杂,但是基于整个框架的支持和考虑,我们提供了代码生成 ...
- Web API应用架构在Winform混合框架中的应用(3)--Winfrom界面调用WebAPI的过程分解
最近一直在整合WebAPI.Winform界面.手机短信.微信公众号.企业号等功能,希望把它构建成一个大的应用平台,把我所有的产品线完美连接起来,同时也在探索.攻克更多的技术问题,并抽空写写博客,把相 ...
- Web API应用架构在Winform混合框架中的应用(2)--自定义异常结果的处理
在上篇随笔<Web API应用架构在Winform混合框架中的应用(1)>中我介绍了关于如何在Winfrom里面整合WebAPI,作为一个新型数据源的接入方式,从而形成了三种不同的数据提供 ...
- Web API项目中使用Area对业务进行分类管理
在之前开发的很多Web API项目中,为了方便以及快速开发,往往把整个Web API的控制器放在基目录的Controllers目录中,但随着业务越来越复杂,这样Controllers目录中的文件就增加 ...
- Web API数据传输加密
http://www.cnblogs.com/wuhuacong/p/4620300.html Web API应用架构设计分析(2) 在上篇随笔<Web API应用架构设计分析(1)>, ...
- Web API接口 安全验证
在上篇随笔<Web API应用架构设计分析(1)>,我对Web API的各种应用架构进行了概括性的分析和设计,Web API 是一种应用接口框架,它能够构建HTTP服务以支撑更广泛的客户端 ...
随机推荐
- C语言#自动生成四则运算的编程
#include <iostream> #include <stdio.h> #include <stdlib.h> #include <time.h> ...
- JavaScript函数编程-Ramdajs
在JavaScript语言世界,函数是第一等公民.JavaScript函数是继承自Function的对象,函数能作另一个函数的参数或者返回值使用,这便形成了我们常说的高阶函数(或称函数对象).这就构成 ...
- 剑英陪你玩转图形学(五)focus
很久没来和大家交流业务(zhuangbi)水平了,最近实在是很忙,报名了小游戏大赛,一点时间都抽不出,已经坑了. 今天抓紧时间和大家介绍一个小效果: 新手引导的时候,我们会需要一种全屏幕黑掉,只有一个 ...
- 获取当前请求的URL的地址、参数、参数值、各种属性
//URL: http://localhost:1897/User/Press/UserContent.aspx/9878?id=1#toc Request.ApplicationPath; //结果 ...
- angular利用ui-router登录检查
angular利用ui-router登录检查 SAP都会有这个问题,session过期或者页面被刷新的情况下应该进入登录页. 监听ui-router的satte事件可以实现当state切换的时候检查登 ...
- javaweb回顾第八篇如何创建自定义标签
前言:在javaweb开发中自定义标签的用处还是挺多的.今天和大家一起看自定义标签是如何实现的. 1:什么是标签 标签是一种XML元素,通过标签可以使JSP页面变得简介易用,而且标签具有很好的复用性. ...
- 咱们来聊聊JS中的异步,以及如何异步,菜鸟版
为什么需要异步?why?来看一段代码. 问题1: for(var i=0;i<100000;i++){ } alert('hello world!!!'); 这段代码的意思是执行100...次后 ...
- 使用AssetsLibrary.Framework创建多图片选择控制器(翻译)
系统的UIImagePickerController只能让用户选择单图片,而一般情况下,我们需要上传多张图片,这时应该可以同时选择多张图片,否则用户体验会很差.因此多图片选择器就诞生了. 在类库中,苹 ...
- 绘制 ToggleButton --重写view
package com.example.compoundbuttonview.view; import com.example.compoundbuttonview.R; import android ...
- 浏览器兼容处理(HTML条件注释、CSSHack和JS识别)
前面的话 本文中所有IEx+代表包含x及x以上:IEx-代表包含x及x以下,仅个人习惯.例:IE7+代表IE7.IE8…… 本文中所有例子全部经过测试,欢迎交流. HTML识别 条件注释法(IE10+ ...