從eclipse到android studio的安卓開發經驗告訴我原聲開發才是硬道理,其實以前很抵觸html5開發app的,雖然沒有去瞭解過,但是冥冥中就覺得它運行速度太慢了,載入渲染根本比不上原生開發,並且如果系統與硬體交互比較深的話就更沒法使用html5了。一個偶然機會,我開始接觸html5開發 ...
從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,在該類上邊寫上標簽,然後其他控制器繼承該基類,即可快速實現所需功能。至此,伺服器端介面就被簡單保護起來了。客戶端的代碼實現等下一篇再寫吧。