1. 程式人生 > >記寫 android 微信登入的demo歷程

記寫 android 微信登入的demo歷程

### 前言 首先看一條連結: https://github.com/Tencent/WeDemo 騰訊給了一個wedemo,微信第三方登入的例子。裡面是php和ios,ios是object寫的,php還是原來的php。 因為公司需要做android app微信第三方登入,所以我得寫個android例子。心裡是什麼想法呢? 不就是Oauth 2.0,作為一個.net 看php也不是啥難處,寫個app也沒啥,結果遇到很多坑,好吧,我承認我是一隻菜雞。 下面是個人開發歷程,如有思維錯誤請指導。 ### 正文 我首先看到的是這張圖: ![](https://img2020.cnblogs.com/blog/1289794/202008/1289794-20200820163720886-838199943.png) 上面這種圖的故事告訴我們在操作資源性api(包括登入)之前呢,應該先建立安全通道。 流程是這樣子的: 1.有一個32位位元組的祕鑰,(psk這是個通用名詞,表示加密的key),使用的是aes,32位,那麼就是aes256了。 ``` //生成key public static byte[] getAES256Key () throws NoSuchAlgorithmException { KeyGenerator kg = KeyGenerator.getInstance("AES"); kg.init(256); SecretKey sk = kg.generateKey(); //隨機生成32位加密key return sk.getEncoded(); } ``` 2.把這個生成32位位元組的祕鑰去用公鑰加密,這個公鑰是寫死在app中的,然後傳給伺服器。 ``` private String encryptedRSA(byte[] content) throws NoSuchPaddingException, NoSuchAlgorithmException, BadPaddingException, IllegalBlockSizeException, InvalidKeySpecException, InvalidKeyException, UnsupportedEncodingException, ShortBufferException { //base64編碼的公鑰 RSAPublicKey pubKey=getPublicKey(); //RSA加密 Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-1AndMGF1Padding"); cipher.init(Cipher.ENCRYPT_MODE, pubKey); Map param = Maps.newHashMap(); param.put("psk", new String(content)); String outStr = Base64.encodeBase64String(cipher.doFinal(com.alibaba.fastjson.JSON.toJSONString(param).getBytes())); return outStr; } ``` 這裡有個需要注意的就是要使用RSA/ECB/OAEPWithSHA-1AndMGF1Padding,因為服務端使用的是:OPENSSL_PKCS1_OAEP_PADDING,這個加密用的少,OAEP這種模式還是第一次聽說,然後去查java的,原來是RSA/ECB/OAEPWithSHA-1AndMGF1Padding, 對我這種加解密不熟的人來說,算是一個小坑。 傳這個流即可: base64(public_encrypted(32祕鑰)) 這裡有個非常值得注意的是,android app的base64和php的base64實現方式不一樣,當時我調了好久(1個小時),然後通過打斷點才知道base64實現不一樣。 後來我就用庫了,庫的名稱是:org.apache.commons.codec 這個庫會產生衝突,需要把原始碼拿下來,然後改空間名,然後打包jar,最好還是網上找個吧,當時我是為了保險。 3.伺服器去用私鑰解開,然後儲存psk(aes的key)。 4.將psk作為祕鑰進行temp_uni加密傳給客戶端。 第四步,如果不看原始碼估計會被坑。 php關鍵原始碼: ``` public function AES_encode($data, $key) { $data = json_encode($data); $iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC); $iv = mcrypt_create_iv($iv_size, MCRYPT_RAND); $encode = $this->AES256_cbc_encrypt($data, $key, $iv); // echo $encode; $mac_server = hash_hmac('sha256', $encode, $key, true); // 計算mac_server $encode = base64_encode($iv . $encode . $mac_server); // 加密後輸出的格式為IV+AES密文+SHA256對AES密文進行雜湊後的值 return $encode; } ``` 裡面作為幾件事: 1.生成一個16位的iv 2.用我們穿的key,和生成的iv,然後加密temp_in 3.對$encode和key進行hmac摘要。 4.iv和$encode還有hmac進行拼接,然後使用base64加密,發給客戶端。 那麼客戶端需要做的就是下面幾件事。 1.用base64解密開。 2.去處$encode,進行同樣的hmac,得到的值和傳過來的hmac比較,檢視是否被串改資料。 3.使用儲存在客戶端的key和取下來的iv進行$encode解密,會得到一個json。 {'base_resp':{'errcode':$errcode,'errmsg':$errmsg},tmp_uin:'xxx'} 要取得就是tmp_uin。 好的,那麼開始下一步。 ![](https://img2020.cnblogs.com/blog/1289794/202008/1289794-20200820170314040-1877110993.png) 取得了tmp_uin。那麼使用者就可以進行微信登入了。 使用者點選後,會跳轉到微信授權取得code。 那麼客戶端需要做的就是?因為這個圖實在不清晰,那麼我們來看下服務端做了啥,然後反推客戶端應該幹啥吧。 ``` public function AES_decode($data, $key, $to_type = '') { $data = base64_decode($data); $iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC); $iv = substr($data, 0, $iv_size); $mac_client = substr($data, -32); $encode = substr($data, $iv_size, -32); $mac_server = hash_hmac('sha256', $encode, $key, true); // 計算mac_server $decode = $this->AES256_cbc_decrypt($encode, $key, $iv); // 檢測包的合法性 if ($mac_client == $mac_server){ $decode = $this->AES256_cbc_decrypt($encode, $key, $iv); if (!$decode) { return null; } if ($to_type == 'json') { $decode = json_decode($decode, true); } return $decode; } else { return null; } } ``` 服務端解密模式和加密模式相對應,客戶端應該做的是: 1.生成一個iv 16位元組 2.使用原來的key,和生成的iv,對code進行加密,這裡標註為encode。 3.生成一個hmac,數字摘要模式為sha256,也就是32位元組的摘要。 4.拼接iv+encode+hmac進行base64位加密,然後傳送為伺服器端。 格式為: { "uin" : 3161321213,//上一步取得的temp_uni "req_buffer" : "xxxx"//上文加密的資料 } 然後就會返回給我們正式通訊後的內容,格式為: Response: { errcode : 0, "resp_buffer" :"xxxx"//加密的資料 } resp_buffer 裡面包括了loginTicket和uni,作為以後和伺服器的溝通憑據。 resp_buffer 進行解密的規則:和上文aes解密規則一致,這時候才真正的建立起正式的安全通道, 比如說獲取使用者資訊: ![](https://img2020.cnblogs.com/blog/1289794/202008/1289794-20200820172145740-1186636745.png) 按照上文的aes方法加密吧正式把uni和loignTicket 進行加密,就可以獲得資料,然後又是客戶端的解密獲取使用者資訊,重複的就沒什麼坑了。 ### 結 以上是個人遇到的坑和思路,也許會給剛入坑的人一點小小的幫助,如果思路哪裡不好,也望請