從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,在該類上邊寫上標簽,然後其他控制器繼承該基類,即可快速實現所需功能。至此,服務器端接口就被簡單保護起來了。客戶端的代碼實現等下一篇再寫吧。