从eclipse到android studio的安卓开发经验告诉我原声开发才是硬道理,其实以前很抵触html5开发app的,虽然没有去了解过,但是冥冥中就觉得它运行速度太慢了,加载渲染根本比不上原生开发,并且如果系统与硬件交互比较深的话就更没法使用html5了。一个偶然机会,我开始接触html5开发app,总之各有各的优缺点,如果对资金比较短缺 ,那么久先使用html5开发与一个app凑合着用吧,不过没有想象中的那么垃圾,其实优点还是蛮多的。想学的可以自己 体会一下。
APP设计之初当然要先考虑安全性问题,并且能够达到高效,我在这里简单分享一下我的做法(查阅网上的资料做的),app需要与服务器进行交互,通过调用服务器提供的api获取数据,并展示出来。如果服务器api接口没有做任何防护,那么会被他人恶意调用,轻者会加重服务器负担,重者可能造成经济损失。
首先我们需要能够识别调用者是否为合法用户,如果合法,则返回数据,不合法就直接禁止掉。(下边会将登录与未登录情况),服务器端使用的是asp.net web api。客户端发送请求时,加入sign,ts,deviceid...sign=加密算法(ts+deviceid+***),具体方法看个人,服务端获取到这些数据后,在服务端进行判断,如果时间在5分钟以为,或者签名不正确等问题,就立即禁止访问。访问级别设置为三级:1、不需要任何验证,可以随意访问。2、只能自己的客户端能访问,做签名认证。2、登录认证,必须登录才能访问。登录认证使用比较流行的token方法,当用户登录的时候,如果登录成功,服务器按照一定规则生成已串加密字符串作为token,然后发送给客户端,从那以后客户端每次请求数据都需要将token和用户名提交给服务端,有服务端确定是否为合法登录用户,如不是那么客户端跳回到登录页面,重新登录验证。
1 using System; 2 using System.Linq; 3 using System.Net; 4 using System.Net.Http; 5 using System.Web.Http.Controllers; 6 using System.Web.Http.Filters; 7 using KubuServerBLL; 8 using yxxrui; 9 10 namespace ****Server 11 { 12 public class MyApiActionFilter : ActionFilterAttribute 13 { 14 public const int NOT_AUTHENTICATION = 1; 15 public const int NOT_LOGIN = 2; 16 public const int NEED_LOGIN = 3; 17 private const string ApiPrivateKey = "aaaaaasdfadfadfgfdgjldfajooilsdkjfad***sfhdjk";//客户端和手机端保持一致 18 private readonly int _level; 19 20 public MyApiActionFilter() 21 { 22 _level = NEED_LOGIN; 23 } 24 public MyApiActionFilter(int level) 25 { 26 _level = level; 27 } 28 29 public override void OnActionExecuting(HttpActionContext context) 30 { 31 //三个级别,1、不需要验证 2、需要认证是否来自合法的客户端 3、是否正确登录 32 switch (_level) 33 { 34 case NOT_AUTHENTICATION: 35 break; 36 case NOT_LOGIN: 37 if (IsForbidden(context)) 38 { 39 context.Response = new HttpResponseMessage(HttpStatusCode.Gone); 40 } 41 break; 42 case NEED_LOGIN: 43 if (IsForbidden(context) || IsNotLogin(context)) 44 { 45 context.Response = new HttpResponseMessage(HttpStatusCode.Forbidden); 46 } 47 break; 48 } 49 //如果该请求是不合法的,那么禁止 50 base.OnActionExecuting(context); 51 //验证通过 52 53 } 54 55 private readonly UserBll _userBll = new UserBll(); 56 private bool IsNotLogin(HttpActionContext context) 57 { 58 try 59 { 60 //获取请求头信息 61 var requestHeaders = context.Request.Headers; 62 //设备ID 63 var usernameH = requestHeaders.Where(d => d.Key == "username").ToList(); 64 string username = usernameH.Any() ? usernameH.First().Value.ToArray()[0] : ""; 65 if (string.IsNullOrWhiteSpace(username)) 66 { 67 return true; 68 } 69 70 //请求签名 71 var tokenH = requestHeaders.Where(d => d.Key == "token").ToList(); 72 string token = tokenH.Any() ? tokenH.First().Value.ToArray()[0] : ""; 73 if (string.IsNullOrWhiteSpace(token)) 74 { 75 return true; 76 } 77 var isLogin = _userBll.CheckToken(username, token); 78 return !isLogin; 79 } 80 catch 81 { 82 return true; 83 } 84 } 85 86 /// <summary> 87 /// 验证请求头 88 /// </summary> 89 /// <param name="context"></param> 90 /// <returns></returns> 91 private bool IsForbidden(HttpActionContext context) 92 { 93 try 94 { 95 //获取请求头信息 96 var requestHeaders = context.Request.Headers; 97 //设备ID 98 var deviceIdH = requestHeaders.Where(d => d.Key == "deviceId").ToList(); 99 string deviceId = deviceIdH.Any() ? deviceIdH.First().Value.ToArray()[0] : ""; 100 if (string.IsNullOrWhiteSpace(deviceId)) 101 { 102 return true; 103 } 104 105 //请求签名 106 var signH = requestHeaders.Where(d => d.Key == "sign").ToList(); 107 string sign = signH.Any() ? signH.First().Value.ToArray()[0] : ""; 108 if (string.IsNullOrWhiteSpace(sign)) 109 { 110 return true; 111 } 112 113 //10位时间戳 114 var tsH = requestHeaders.Where(d => d.Key == "ts").ToList(); 115 string ts = tsH.Any() ? tsH.First().Value.ToArray()[0] : ""; 116 if (string.IsNullOrWhiteSpace(ts) || ts.Length != 10) 117 { 118 return true; 119 } 120 121 //看看是否失效,前后5分钟 122 var tsDate = ComHelper.ConvertIntDateTime(ts); 123 if (tsDate > DateTime.Now.AddMinutes(5) || tsDate < DateTime.Now.AddMinutes(-5)) 124 { 125 return true; 126 } 127 //服务器端生成的Sign 128 string mysign = ComHelper.To加密(deviceId + ts + ApiPrivateKey); 129 if (!sign.Equals(mysign, StringComparison.InvariantCultureIgnoreCase)) 130 { 131 return true; 132 } 133 } 134 catch 135 { 136 return true; 137 } 138 return false; 139 } 140 } 141 }
创建上边的类,使用方法如下:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Net; 5 using System.Net.Http; 6 using System.Web.Http; 7 using ****ServerBLL; 8 using ****ServerDAL; 9 using ****ServerModel; 10 11 namespace ****Server.Controllers.Api 12 { 13 public class NameValuesController : ApiController 14 { 15 private readonly NameValuesBll _nameValuesBll = new NameValuesBll(); 16 17 [HttpPost] 18 [MyApiActionFilter(2)]//此处设置标签拦截,级别设置为不需要登录 19 public BaseMsg GetUpdateVersion(dynamic obj) 20 { 21 string clientVersion, deviceId; 22 try 23 { 24 clientVersion = Convert.ToString(obj.clientVersion); 25 deviceId = Convert.ToString(obj.deviceId); 26 } 27 catch 28 { 29 return new BaseMsg("信息有误。"); 30 } 31 32 var versionObj = _nameValuesBll.GetValueByName("version"); 33 var urlObj = _nameValuesBll.GetValueByName("AndroidUrl"); 34 35 if (versionObj == null || urlObj == null) 36 { 37 return new BaseMsg("获取失败,请重试。"); 38 } 39 if (versionObj.value != clientVersion) 40 { 41 return new BaseMsg(new 42 { 43 Version = versionObj.value, 44 AndroidUrl = urlObj.value, 45 UpdateMemo = versionObj.other 46 }); 47 } 48 return new BaseMsg("noNewVersion","已是最新版,不需要更新。",null); 49 } 50 } 51 }
上边的方法是一个检查软件是否需要更新的接口,此方法放到服务器可以实现灰度更新,但可能会加重服务器负担。如果app需要检查更新的时候,直接调用该api即可。此接口需要签名认证。
如果希望有一个接口必须由自己做的客户端发出,并且用户成功登录过才可以访问,那么可以将方法上边的标签[MyApiActionFilter(2)]中的数字改成3或者直接删掉即可,如:
1 #region 绑定手机号码 2 [HttpPost] 3 [MyApiActionFilter] 4 public BaseMsg BindingPhone(dynamic obj) 5 { 6 string phone; 7 string username; 8 try 9 { 10 phone = Convert.ToString(obj.phone); 11 username = Convert.ToString(obj.username); 12 } 13 catch 14 { 15 return new BaseMsg("信息有误。"); 16 } 17 bool ret = _userBll.BindingPhone(username,phone); 18 if (ret) 19 { 20 return new BaseMsg(); 21 } 22 return new BaseMsg(@"该手机号已经注册过。"); 23 } 24 #endregion
如果控制器中的所有api方法都需要登录后才能使用,那么将标签放到类上方,如果有n个控制器都需要登录后访问,那么创建一个基类去继承ApiController,在该类上边写上标签,然后其他控制器继承该基类,即可快速实现所需功能。至此,服务器端接口就被简单保护起来了。客户端的代码实现等下一篇再写吧。
有问题可以联系我,我的邮箱是:yxxrui@163.com,我的网址是:http://www.yxxrui.cn