從登錄注冊說起該驗證的全部過程:
1.android 登錄時一般是這樣:
loginData.put("name", userNameText.toString());
loginData.put("password", passWordText.toString());
loginData.put("platform", "android");
//同步方式
try {
//調(diào)用登錄接口網(wǎng)絡(luò)請求
JSONObject response = okHttpManager.post(login_url, loginData);
2.在客戶端在登錄成功時 記錄 以下信息
//保存用戶信息, 常用的單獨保存
PreferenceManager pm = PreferenceManager.getInstance();
pm.setString("user_id", loginDetail.getString("user_id"));
pm.setString("app_token", loginDetail.getString("app_token"));
pm.setString("app_ticket", loginDetail.getString("ticket"));
pm.setString("userinfo", loginDetail.toString());
那服務(wù)器端返回的app_token 以及ticket 是怎么產(chǎn)生的呢 有什么作用呢羹应?????
3.服務(wù)器端 接收到登錄信息后應(yīng)該是這樣處理:
//生成token
$app_token = empty($userInfo['app_token']) ? $this->genAppToken($retParams['name'], $retParams['platform']) : '';
這個時候?qū)⒏聈sers表 的 app_token字段加派。
//生成ticket
$ticket = $this->genAppToken($userInfo['user_id'], $userInfo['mobile']);
這個時候判斷check_online表 有沒有該條數(shù)據(jù)鱼冀,如果沒有將其插入到online_user表中,該表的設(shè)計應(yīng)該是這樣:
id | user_id | ticket | expire_time | platform |
---|
(這個表的作用是記錄成功登錄 也就是正在線上的表鞠抑,如果登出的話就應(yīng)該將這條記錄刪除)
并將(userid跟ticket)做索引
(所以判斷用戶是否在線 可以通過user_id 或者通過ticket 做判斷)
這個genAppToken 可以用md5 或者sha 都行,反正就是單向加密的都可以,這個時候可以進行api 接口簽名驗證工作
客戶端邏輯跟服務(wù)器端邏輯相似
服務(wù)器端如下:
api的話需要區(qū)分是否需要登錄的接口 所以這塊需要做到免驗證判斷:如下可以實現(xiàn)
$loginArr = array('login', 'register', 'mobile_verifycode_login', 'forget_password', 'send_email', 'createSMS', 'check_exis ts', 'notifySync', 'wx_auth_register','wx_auth_login');
60 if (!in_array($method, $loginArr)) {
61 $this->load->model(array('User_model'));
62 $operator = $this->isLogin();
63 $this->operator = array(
64 'user_id' => (int)$operator['user_id'], 'ticket' => $operator['ticket']
65 );
66 }
簡單的對數(shù)組進行添加就可以實現(xiàn)添加免驗證的接口仆百,根據(jù)上面做的online_user表的工作绽淘,很顯然能知道 判斷一個用戶是否已經(jīng)登陸 只要根據(jù)他的user_id 或者ticket就能夠判斷,如果不在線 則跳到登錄頁
在免接口驗證中测萎,顯然不能通過ticket 來進行對用戶的區(qū)分亡电,所以我們用了sign簽名的方式來解決API簽名的一些問題
- 請求參數(shù)是否被篡改
- 請求來源是否合法
- 請求是否具有唯一性
所以加簽策略可以如下所示:
$sign = $params['sign'];
269 $timestamp = $params['fx_timestamp'];
270 $secret = “123sqweqweq“; //換成自己的secret
271
272 $app_ticket = $this->input->get_post(COOKIE_TICKET);
273 $app_token = '';
274 if (!empty($app_ticket)) {
275 //獲取app_token
276 $cacheData = $this->ciredis->hGetAll('online:ticket:' . $app_ticket);
277 $userInfo = $this->User_model->get(array('user_id' => $cacheData['user_id']));
278 $app_token = !empty($userInfo['app_token']) ? $userInfo['app_token'] : APPSECRET;
279 }
客戶端往服務(wù)器端傳參數(shù)的時候,對其是否傳app_ticket 做判斷硅瞧,如果有 則用這個app_ticket 對表或者緩存中去app_token (要注意 app_ticket 跟app_token不能放在一個緩存或一個表中份乒,還是需要user_id做關(guān)聯(lián))
//過濾掉sign
285 unset($params['sign'], $params['s']);
286 //排序
287 ksort($params);
288 reset($params);
289
290 //拼接字符串
291 $arg = "";
292 while (list ($key, $val) = each($params)) {
293 $arg .= $key . "=" . $val . "&";
294 }
295 $arg = rtrim($arg,'&');
先保存起來用戶傳遞的簽名,需要服務(wù)器端用自己的方法生成簽名腕唧,然后跟客戶端傳來的簽名做匹配或辖,如果成功就通過,否則就報簽名錯誤
一般服務(wù)器端驗證簽名步驟都是這樣:
1.先排序ksort
2.拼接字符串 foreach($params as $key=>$val) {
$arg .= $key.”=“.$val.”&”;
}
3.然后對拼接完的字符串再加一段隨機數(shù)進行md5 或者 sha 加簽,類似如下:
key = “123aqwqw”;
$newSign = sha1($arg.”$key”);
這個時候得到的newSign 就是服務(wù)器所生成的sign(當(dāng)然其中的加簽字符串可以設(shè)置長些復(fù)雜些);
那這個newSign 跟客戶端傳過來的sign 做匹配 枣接。相同則通過颂暇。
這樣做的好處如下:
newSign 是根據(jù)所傳遞的參數(shù)進行加密的 所以 當(dāng)其他人更改參數(shù)的時候,服務(wù)器端產(chǎn)生的newSign 必定與客戶端傳遞的sign 不同月腋,簽名則不匹配
這個時候 就會有人問 ==如果我劫持了客戶端的代碼 同時獲取了客戶端的加簽程序蟀架,不就可以更改客戶端的sign了嗎。==
是這樣的榆骚,所以android 經(jīng)常使==用jni 用c寫一套加簽的流程==片拍,這塊代碼是反編譯不能獲取不到的(==這塊代碼是編譯到so 文件中的==),所以保證了客戶端的代碼安全
客戶端代碼如下:
secret = PreferenceManager.getInstance().getString("app_token");
ShowLog.e(url);
addTicketToParams(params);
buildSign(params);
在網(wǎng)絡(luò)請求的地方進行加ticket 加 sign(網(wǎng)絡(luò)請求這塊必須要做成==公共調(diào)用==妓肢,這樣才能實現(xiàn)對接口訪問的統(tǒng)一捌省,可以做成單例模式。)
secret 作用是獲取登錄那會保存的app_token,
addTicketToParams 用來對params 進行加入ticket 碉钠,這個ticket 也是使用登錄保存的ticket
重點在==buidlSign==這個部分纲缓。
params.put("timestamp", Long.toString(timestamp));
params.put("appsecret", secret);
Map<String, String> sortedParams = new TreeMap<String, String>(params);
Set<Map.Entry<String, String>> entries = sortedParams.entrySet();
StringBuffer buffer = new StringBuffer();
byte[] bytes = null;
try {
for (Map.Entry<String, String> entry : entries) {
if (buffer.length() > 0) {
buffer.append("&");
}
buffer.append(entry.getKey()).append("=").append(entry.getValue());
獲取時間戳以及獲取之前的secret 也就是app_token
然后也跟服務(wù)器端一樣 遍歷并挨個加=以及& 然后這時候加入jni 的編寫的c語言程序加參
跟服務(wù)器端邏輯類似 只是用c 來實現(xiàn)服務(wù)器端php語言的實現(xiàn)效果
這個時候會返回一個字符串sign,然后
arams.remove("appsecret");
ShowLog.e(params.toString());
params.put("sign", doencrypt(buffer.toString()));
ShowLog.e(params.get("sign"));
將這個字符串加入sign 并與服務(wù)器端交互
這就是整個API接口簽名驗證的整個過程喊废。
這塊要注意的點在于不管是需要驗證還是免驗證的接口 必須讓app_ticket 以及app_token 成對出現(xiàn)(要么都有 要么都沒有)
文章出處:https://www.ci92.com/Index/Blog/detail?id=17