支付寶WAP支付接口開發

支付寶WAP支付接口開發

因項目須要,要增長支付寶手機網站支付功能,找了支付寶的樣例代碼和接口說明,折騰兩天搞定,謹以此文做爲這兩天摸索的總結。因爲公司有本身的支付接口,並不直接使用這個接口,因此晚些時候打算把測試代碼整理好放到Github上。

 

1. 開發前準備

  1. 到官網瞭解此接口的信息,下載樣例代碼(只有ASP.NET和PHP)以便隨時參考。
  2. 一個經過實名認證的企業支付寶帳號,並申請開通手機WAP支付功能,個人測試帳號是拿公司的,申請流程不清楚,官網有說怎麼申請,各位各顯神通吧。
  3. 公網域名和node.js環境。下面的代碼大多用coffee來表達,不過本文不會貼太多代碼,即便對coffee不熟悉也沒什麼關係。關於coffee能夠參考這裏

github上有兩個開源小項目(搜索 alipay ), 但都沒有WAP支付功能,能夠拿來當參考,能夠認爲是示例代碼的js移植版,結構很相像。我原打算在其中一個項目基礎上繼續開發,看了代碼和接口文檔後, 仍是決定從頭開發一個。由於原有代碼層次不夠清晰,有點過分設計的感受,並且支付寶的接口很簡單,重寫工做量不大。node

吐槽下: 官網的示例代碼真只是示例級(test)而已,跟產品級(production)還隔比較遠,感受還談不上SDK。接口文檔至關的坑爹,正因如此我才以爲有必要好好寫篇文章總結。ios

2. 流程

接口開發最重要的應該是理解數據交互流程了,流程弄清了,並理解爲什麼這麼設計,開發起來也是事半功倍git

首先,要準備下面幾個參數:github

  1. 企業支付寶帳號的PID(也叫ParnerID)和KEY,若是使用RSA簽名而不是MD5的話,還要把RSA私鑰準備好
  2. 支付時用戶看到的東西:商品名稱(subject)、支付總額(total_fee)、購買數量(一般都是1吧)
  3. 交易後的跳轉地址,交易成功後用戶能夠手工點擊,或頁面延遲自動跳轉到這個地址(return_url)
  4. 交易狀態異步通知地址,交易成功或交易關閉會把消息POST到這個地址(notify_url)

而後,看這幅流程圖(不錯吧,推薦下這個網站:)算法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Alipay WAP pay flow

Browzer->+Site: 1. HTTP GET                
note over Site: 2. create a new trade      
Site->+Alipay: 3. create redirect          
Alipay->-Site: 4. Token                    
note over Site: 5. build auth url          
Site->-Browzer: 6. redirect to auth url    
Browzer->Alipay: 7. redirect               
Alipay->Browzer: 8. trade info             
Browzer->Alipay: 9. auth and pay           
Alipay->+Site: 10. HTTP POST notify        
note over Site: 11. process trade          
Site->-Alipay: 12. reply "success"         
Alipay->Browzer: 13. pay success           
Browzer->Site: 14. goto return url

這個流程圖基本囊括了整個交互過程,下面是說明:sql

  1. 用戶點擊購買按鈕(或其餘形式),向網站發起購買請求
  2. 網站建立訂單,指派一個惟一訂單號
  3. 網站把訂單號、企業支付寶帳號、交易金額、數量等信息,用私鑰簽名發送給支付寶
  4. 支付寶建立一個交易訂單,返回一個交易令牌(token)
  5. 網站按照指定要求,用token和本身的私鑰,構造一個重定向獲得支付地址
  6. 網站把重定向地址返回給瀏覽器
  7. 瀏覽器自動重定向到該地址,即包含了token、網站簽名的支付寶交易頁面
  8. 支付寶顯示當前交易金額、數量、賣家等信息
  9. 用戶用本身的支付寶帳號支付這筆金額
  10. 支付寶把用戶支付成功(或失敗)這個消息和訂單號加上支付寶的簽名,使用HTTP POST的方式通知網站(失敗的話,會隔段時間從新發送)
  11. 網站處理交易後續邏輯(發貨、訂單狀態存儲之類的)
  12. 網站返回"success"字符串給支付寶,表示該通知已經處理,不用再重發
  13. 支付寶顯示支付成功頁面給用戶(這一步和第10步是不分前後發生的)
  14. 支付成功頁面延遲自動跳轉,或用戶點擊「返回商戶頁面」,跳轉到網站的支付結束頁面(此時不必定成功處理支付寶發來的通知),但會在URL帶上當前的訂單號和狀態。

能夠發現,整個流程有點像OAuth(哎呀,以前那篇文章還沒寫完呢!),主要分三步:api

一是申請支付寶交易號(獲取token),這一步能夠理解爲,讓支付寶驗證網站的有效性、讓網站指定該交易要支付多少錢 二是用戶到支付寶頁面付款,這一步能夠理解爲,讓支付寶驗證用戶有效性,讓用戶在一個不受網站監視的環境下進行支付 三是用戶付款後,處理結果頁面告訴用戶支付成功(同步通知),另外異步通知網站服務器該訂單已支付。瀏覽器

支付寶的接口文檔說只有兩個步驟,感受不是很好理解,三步仍是比較準確的(收錢確定要辦事的嘛)。安全

好睏,細節問題下期繼續。。。服務器


2013-07-24

3. 細節

3.1 網站向支付寶申請新訂單

網站的訂單系統先產生一個新訂單,而後請求支付寶建立一個支付寶訂單.

申請新訂單的service是 alipay.wap.trade.create.direct 須要提交的關鍵參數包括:

3.1.1 用戶在支付寶看到的訂單信息:

subject: 商品名稱

total_fee: 總金額

seller_account_name: 賣家支付寶帳號(估計跟私鑰綁定的)

merchant_url: 商品展現URL(彷佛這個並不是必要)

3.1.2 支付寶通知網站時將附帶的信息:

out_trade_no: 該次交易對應網站的訂單號(要求惟一)

call_back_url: 交易成功後,支付寶頁面上「返回到商家頁面」的地址(同步回調)

notify_url: 交易狀態變動後,支付寶通知網站的回調地址(異步通知)

支付寶驗證經過後,將返回新建立的支付寶訂單號,網站可將該訂單號與本身訂單系統的的訂單號綁定在一塊兒。支付寶同時返回的還有該次交易的token,用於(3.2)用戶支付。

3.2 用戶在支付寶網站,查看訂單消息,經過驗證並支付

網站返回跳轉到支付寶的地址,service是alipay.wap.auth.authAndExecute,包含(3.1)返回的token和網站對跳轉地址的簽名

這是個HTTPS頁面,基本認爲是安全的。固然前提是瀏覽器沒被動手腳,安卓很多應用被捆綁廣告那是常有的事,手機瀏覽器對HTTPS也不像PC那樣有明顯提示,這些也是我不怎麼信任手機支付的緣由。

用戶跳轉到支付寶頁面後,能夠在該頁面裏看到當前支付的訂單的名稱和金額,這些是3.1申請時由網站指定的,讓用戶在支付寶的頁面確認一次再付款是合理的。

3.3 支付寶通知網站支付成功,網站收錢作事

這個過程是支付寶通知網站,網站處理後通知用戶已到帳,共包括兩個並行部分:

3.3.1 異步通知

用戶支付後,支付寶經過HTTP協議通知網站該訂單交易結果。說白了就是支付寶悄悄地告訴網站「這個訂單已經已經付款啦」

值得注意的是,異步通知有重發機制,支付寶須要獲得響應爲"success"才認爲該通知成功被接收,不然會間隔一段時間重發,依次間隔2m,10m,10m,1h,2h,6h,15h,最多8次通知,由notify_id說明是同一個通知。8次通知都接收失敗怎麼辦?額orz...文檔沒說,用那個支付寶訂單號登陸支付寶去查帳吧。

3.3.2 同步通知

用戶支付後,支付寶頁面提示「支付成功」,可點擊返回商家頁面,也可等待一段時間自動跳回

我的認爲,網站跳到這個頁面後,若是仍未收到(3.3.1)異步通知,而且使用的是MD5簽名,應該把狀態從「待付款」調整爲「等待對帳」,而不該該貿然 相信該通知的結果。緣由是這個回調地址用戶是能夠知道的,MD5簽名仍是有被僞造的可能(4.3)。固然額外再作個token之類的理論上也行(須要放在 urlpath而不是querystring)。

假如接口調用出錯,通知是不會簽名的。不簽名的緣由我懷疑是防止有人惡意收集請求-簽名樣本,見(4.3)。

4. 簽名與加密

簡單的說,簽名防篡改,加密防竊聽。上面的兩種請求(3.1和3.2)和兩個通知(3.3.1和3.3.2)都被要加簽名,支付寶支持下面兩類簽名:

4.1 MD5: 業務數據不加密,防篡改

  • 優勢: 相對較簡單(固然是相對DSA/RSA來講),計算速度快,明文更直觀

  • 缺點: 可抵賴,可能被竊聽、安全性不如非對稱加密

4.2 DSA/RSA: 業務數據加密,也防篡改

  • 優勢:不可抵賴,安全性較高

  • 缺點:相對較複雜,解密速度慢

一開始我想不懂,支付寶既然支持RSA爲什麼還要支持MD5,後來有人說RSA太慢,想一想支付寶的業務量就釋然了。因爲每一個商家的私鑰都不一樣,而且跟商家的支付寶帳號綁定,即便商家的私鑰被破解了,用戶支付時HTTPS協議基本能夠保證用戶支付的目標仍是商家的帳號。

4.3 使用MD5簽名可能存在的風險

如下情景僅是個人推斷,沒有嘗試過,所謂道高一尺魔高一丈,但願讀者也別以身犯險。

在用戶支付的步驟(3.2)和支付成功響應頁面(3.3),用戶能夠獲得一個明文請求內容和對應簽名。因爲網站和支付寶直接通訊共用同一個密鑰,通常長期不變,雙方均可以對同一段數據產生簽名,這就有可能抵賴的風險:

網站:「這個數據是你發過來的,上面有你的簽名。」

支付寶:「不是我發的。這個數據是你僞造的,簽名是你籤的。」

另外,當攻擊者收集到足夠多的樣本,是有可能破解出密鑰的,繼而可僞造網站或支付寶任意一方。

4.3.1 惡意消耗商家的訂單號

攻擊者僞造大量未使用的訂單號(很多網站的訂單號都是遞增的純數字,並公開給用戶,且很容易推測到後面的數字),向支付寶請求訂單,直到超時。因爲商家對 此並不知情(回調地址和通知地址均篡改掉),其餘用戶下單時假如商家用了被僞造過的訂單號,就可能被支付寶認爲提交了重複訂單,結果支付失敗。

4.3.2 欺騙商家已支付訂單

由同步通知(3.3.2)返回的參數能夠看到,網站訂單標識和交易token和都是能夠獲得的。這樣的話,關於步驟(3.1),用戶不知道的參數包括 notify_url和out_user_no,假如網站的用戶id自己就是公開的,通知回調地址(3.3.1)被得知或同步通知(3.3.2)實現的不 好,就能夠經過僞造支付通知,欺騙商家訂單已支付。

待續,下期補充實現代碼


2013-07-28

5. 代碼

5.1 簽名

我只作了MD5簽名,項目裏沒用到RSA簽名,就沒作那方面。按照文檔說明和demo源代碼,很容易就能夠寫出下面的簽名代碼:

1
2
3
4
5
6
7
getSign = (obj,key) -> return null unless obj arr = ([k,v] for k,v of obj when k isnt 'sign' and v? and v isnt '') arr.sort() src = ("#{i[0]}=#{i[1]}" for i in arr).join '&' src = "#{src}#{key}" crypto.createHash('md5').update(src,'utf8').digest 'hex' 

支付寶發到網站的通知(3.3)的簽名算法跟上面有點不同,文檔有這麼段說明:

這裏說要按通知的參數的本來順序計算簽名。因此我就把上面的arr.sort()去掉而後計算簽名,結果發通知發來的簽名和我本身計算的不一致,糾結半天后仔細看文檔的樣例說明,看到下面段:

仔細跟實際接收的數據比較以後發現,文檔和樣例都說發來的參數順序是(service,v,sec_id,notify_data),但我實際收到的並非按這個順序,只要按照文檔的參數順序從新排列再計算簽名就正確了,最終通知的簽名算法以下( 真是個蛋疼的大坑orz):

1
2
3
4
5
getNotitySign = (obj,key) -> return null unless obj src = ("#{k}=#{obj[k]}" for k in ["service","v","sec_id","notify_data"]).join '&' src = "#{src}#{key}" crypto.createHash('md5').update(src,'utf8').digest 'hex' 
  • 文檔裏有說到字符編碼參數_input_charset,我用的是utf8編碼,發現不用傳這個參數也能夠,看來支付寶默認的字符編碼就是utf8了

  • 若是使用RSA簽名,須要先解密再計算簽名

5.2 輔助方法

爲了代碼層次更清晰,我把簽名、url拼接等方法抽出到一個單獨模塊(alipay_api.coffee):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
api_url = "http://wappaygw.alipay.com/service/rest.htm"
regexTokenXml = /<request_token>(.*)<\/request_token>/ module.exports = api = services: create: "alipay.wap.trade.create.direct" auth: "alipay.wap.auth.authAndExecute" toReqData: (name,obj) -> arr = ["<#{name}>"] arr.push "<#{k}>#{v}</#{k}>" for k,v of obj arr.push "</#{name}>" arr.join '' createReq: (service,partner,req_data) -> service : service format : 'xml' v : '2.0' partner : partner sec_id : 'MD5' sign : null req_data: req_data parseTokenFromXml: (xml) -> return null unless xml m = regexTokenXml.exec xml m?[1]?.trim() getSign: (obj,key='') -> return null unless obj arr = ([k,v] for k,v of obj when k isnt 'sign' and v? and v isnt '') arr.sort() src = ("#{i[0]}=#{i[1]}" for i in arr).join '&' src = "#{src}#{key}" crypto.createHash('md5').update(src,'utf8').digest 'hex' getNotitySign: (obj,key='') -> return null unless obj src = ("#{k}=#{obj[k]}" for k in ["service","v","sec_id","notify_data"]).join '&' src = "#{src}#{key}" crypto.createHash('md5').update(src,'utf8').digest 'hex' sendCreate: (req,done) -> opt = url: createCreateUrl req request.get opt, (err,res,body) -> return done err if err body = "" unless body ret = querystring.parse body body = null done null,ret createAuthUrl: (token='',key='') -> req = api.createReq api.services.auth req.req_data = "<auth_and_execute_req><request_token>#{token}</request_token></auth_and_execute_req>" req.sign = api.getSign req, yes createAuthUrl req createCreateUrl = (req) -> url = "#{api_url}?" url += "req_data=#{encodeURIComponent req.req_data}" url += "&service=#{encodeURIComponent req.service}" url += "&sec_id=#{encodeURIComponent req.sec_id}" url += "&partner=#{encodeURIComponent req.partner}" url += "&req_id=#{encodeURIComponent req.req_id}" url += "&sign=#{encodeURIComponent req.sign}" url += "&format=#{encodeURIComponent req.format}" url += "&v=#{encodeURIComponent req.v}" url createAuthUrl = (req) -> url = "#{api_url}?" url += "req_data=#{encodeURIComponent req.req_data}" url += "&service=#{encodeURIComponent req.service}" url += "&sec_id=#{encodeURIComponent req.sec_id}" url += "&partner=#{encodeURIComponent req.partner}" url += "&sign=#{encodeURIComponent req.sign}" url += "&format=#{encodeURIComponent req.format}" url += "&v=#{encodeURIComponent req.v}" url 

5.3 業務部分

5.3.1 購買(buy)

購買的邏輯對應於(2)流程圖的(2,3,4,5),建立惟一請求ID,填充本次交易信息,發送到支付寶並獲取token,而後拼接支付url並簽名,而後重定向。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
demo.buy = (info,done) -> return done 'bad user' unless info?.user_id?.length>10 req = api.createReq api.services.create, info.partner ret = redirect: '' token: null async.series [ (cb) -> getRequestId req.service,(err,req_id) -> req.req_id = req_id cb err (cb) -> createTrade info,req.req_id,(err, tradeId) -> return cb err if err req.req_data = subject : info.subject # 商品名稱 out_trade_no : tradeId.toString() # 網站訂單號 total_fee : info.total_fee # 價錢(number),單位元,例如 0.01 表明1分錢 seller_account_name: info.seller_account_name # 支付寶帳號 call_back_url : info.call_back_url # 支付成功後瀏覽器跳轉地址 notify_url : info.notify_url # 支付成功支付寶的通知將異步發送到此地址 out_user : info.user_id # 網站的用戶標識 merchant_url : info.merchant_url # 商品展現頁面, 只是實際測試時(ios)發現支付時沒地方能夠跳到這個頁面 req.pay_expire = info.pay_expire if info.pay_expire? # 支付過時時間 req.req_data = api.toReqData 'direct_trade_create_req',req.req_data req.sign = api.getSign req, info.key cb null (cb) -> api.sendCreate req, (err,res) -> return cb err if err return cb 'bad sign from alipay server' unless req.sign is api.getSign req ret.token = api.parseTokenFromXml res.res_data ret.redirect = api.createAuthUrl ret.token storeTradeInfo req.out_trade_no, req.total_fee, ret.token, (err,success) -> return cb err if err cb if success then null else 'store trade info fail' ],(err) -> done err, ret.redirect 

其中用到的幾個方法跟存儲相關,我用的是MySQL:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
getRequestId = (service, done) -> sql = "insert into alipay_requests(service,create_time,state) values(?,now(),'CREATE')" db.queryAll sql,[service],(err,result) -> done err, result?.insertId createTrade = (info,req_id,callback) -> sql = "insert into alipay_trades(user,req_id,create_time) values(?,?,now())" db.queryAll sql,[info.user_id,req_id],(err,result) -> callback err, result?.insertId storeTradeInfo = (tradeId,rmb,token,callback) -> sql = "update alipay_trades set rmb=?,token=?,state='WAIT_PAY' where id = ?" args = [rmb,token,tradeId] db.queryAll sql,args,(err,updateResult) -> callback err,updateResult?.affectedRows is 1 

5.3.2 通知(notify)

用戶支付後就等着通知了,按道理應該在TRADE_SUCCESS時處理用戶支付成功的邏輯,但我實際測試發現至發送了TRADE_FINISHED事件來,因此乾脆兩個一併處理了,反正只會有一次成功。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
xmlreader = require 'xmlreader'rr,updateResult?.affectedRows is 1 demo.onNotify = (req,callback) -> xmlreader.read req.notify_data,(err,xdoc) -> return done err if err notify = xdoc.notify notify_id = notify?.notify_id?.text() return done 'bad notify_data' unless notify_id done = (response) -> unless 'string' is typeof response console.error "response notify error: " + (response?.stack ? response ? '') response = 'server error' console.error "response notify: #{response}" unless response is 'success' storeNotifyResponse notify_id,response, (err) -> callback if response is 'success' then err else response storeNotifyDetails notify_id, notify, req, (err,success) -> return done err if err unless success return done "store detail error" trade_status = notify.trade_status.text() if trade_status is 'TRADE_FINISHED' or trade_status is 'TRADE_SUCCESS' unless req.sign is api.getNotitySign req return done 'bad sign' out_trade_no = notify.out_trade_no.text() getTradeUser out_trade_no,(err,user) -> return done err if err user_id = user?.user return done 'unknown user' unless user_id onPayed user_id,(err,success) -> return done err if err return done "onPayed error" unless success storeTradeFinalState out_trade_no, no, console.error done 'success' else # TRADE_PENDING, TRADE_CLOSED, WAIT_BUYER_PAY, etc return done 'unknown trade status' 

跟存儲相關的幾個方法以下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
storeTradeFinalState = (tradeId,isError,callback) -> state = if isError then 'FAILURE' else 'SUCCESS' sql = "update alipay_trades set state=?,close_time=now() where id = ?" args = [state,tradeId] db.queryAll sql,args,(err,updateResult) -> callback err,updateResult?.affectedRows is 1 getTradeUser = (out_trade_no,callback) -> sql = "select id,user from alipay_trades where id=?" db.queryOne sql,[out_trade_no],callback onPayed = (user_id,callback) -> sql = "update users set vip=1 where id=? and vip=0 limit 1" db.queryAll sql,[user_id],(err,updateResult) -> callback err, updateResult?.affectedRows is 1 storeNotifyDetails = (notify_id,notify,raw,callback) -> sql = "insert ignore into alipay_notifies(id,recv_time,subject,trade_no,gmt_create, quantity,out_trade_no,notify_time,total_fee,buyer_email,trade_status, gmt_payment,gmt_close,raw) values(?,now(),?,?,?,?,?,?,?,?,?,?,?,? )" args = [ notify_id, notify.subject?.text() notify.trade_no?.text() notify.gmt_create?.text() notify.quantity?.text() notify.out_trade_no?.text() notify.notify_time?.text() notify.total_fee?.text() notify.buyer_email?.text() notify.trade_status?.text() notify.gmt_payment?.text() notify.gmt_close?.text() JSON.stringify raw ] db.queryAll sql,args,(err,updateResult) -> callback err,updateResult?.affectedRows is 1 storeNotifyResponse = (notify_id,response,done) -> sql = "update alipay_notifies set response=? where id=?" db.queryAll sql,[response,notify_id],done 

6. 後記

有些公司有本身的支付平臺,封裝了一層,結果調用流程變成下面這樣:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Alipay WAP using platform pay flow

Browzer->+Site: 1. HTTP GET
note over Site: 2. create a new trade
Site->-Browzer: 3. trade info
Browzer->+Platform: 4. redirect
note over Platform: 5. create a new trade
Platform->+Alipay: 6. create redirect
Alipay->-Platform: 7. Token
note over Platform: 8. build auth url
Platform->-Browzer: 9. redirect to auth url
Browzer->Alipay: 10. redirect
Alipay->Browzer: 11. trade info
Browzer->Alipay: 12. auth and pay
Alipay->+Platform: 13. HTTP POST notify
Platform->Site: 14. proxy
note over Site: 15. process trade
Site->Platform: 16. reply "success"
Platform->-Alipay: 17. reply "success"
Alipay->Browzer: 18. pay success
Browzer->Site: 19. goto return url

咋一看,挺方便的,Site只須要作個跳轉便可,細節都被Platform隱藏起來了,切換不一樣的支付方式也變得很方便。

但是,這個平臺的接口並不完善,這些數據都是明文而且沒有要求Site作簽名,因而就有一個風險。跟(2)的流程圖對比,能夠發現這個流程多了(3,4,14,16)四個步驟。後面兩步只是個代理包裝,沒什麼問題,問題在於步驟3和4。

能夠看到,交易細節被放到了重定向url中了,即用戶能夠得知交易內容並篡改裏面的數據。舉個例子,假設重定向地址相似這樣

http://payment.mysite.com/api/pay?tradeno=10000&rmb=100.00&siteid=1

因爲該地址沒有驗籤機制,因此攻擊者很容易就能夠發現這裏面能夠隨意篡改數據。

  • 方法1: 將rmb=100.00改爲0.01,即將100元的支付變成1分錢,而後支付。結果Site獲得通知的時候,就須要額外處理這種支付款項和要求款項不一 致的狀況。固然最簡單就是金額不足就不退款而且支付失敗了。假如沒作這種判斷,那就至關把100元的商品以1分錢賣出去了。

  • 方法2: 僞造大量tradeno,而後請求,而且不支付款項。因爲tradeno要求惟一,並只能使用一次,這樣就至關於消耗掉了site的交易ID,而且 site對此絕不知情。結果正經常使用戶要購買時,建立的訂單號對於site來講是未使用的,但對platform來講已是使用過了,會返回支付失敗。

要避免這個問題,須要在重定向的地址增長簽名,簽名異常的請求都拋棄掉。

(全文完)

轉自:http://neutra.github.io/2013/%E6%94%AF%E4%BB%98%E5%AE%9DWAP%E6%94%AF%E4%BB%98%E6%8E%A5%E5%8F%A3%E5%BC%80%E5%8F%91/

相關文章
相關標籤/搜索