身份驗(yàn)證技術(shù)方案
- 1、身份認(rèn)證流程
- 1.1 賬號(hào)密碼驗(yàn)證身份
- 1.2 簽名算法
- 1.3 數(shù)據(jù)加密
- 2叠荠、身份認(rèn)證接口
- 2.1 請(qǐng)求方式
- 2.2 請(qǐng)求參數(shù)
- 2.3 數(shù)據(jù)返回
- 3、附件
- 3.1 PHP 加解密
- 3.2 Java 加解密
1扫责、身份認(rèn)證流程
1.1 賬號(hào)密碼驗(yàn)證身份
身份認(rèn)證是其他應(yīng)用的基礎(chǔ)榛鼎,認(rèn)證方(即:校方)需要提供一個(gè)驗(yàn)證接口,具體身份綁定流程如下:
身份認(rèn)證綁定步驟:
- 學(xué)生在微信客戶端打開(kāi)應(yīng)用鳖孤,觸發(fā)微信公眾號(hào)授權(quán)(授權(quán)頁(yè)面提示授權(quán)給騰訊微校)者娱。
- 微信公眾號(hào)授權(quán)后,回調(diào)跳轉(zhuǎn)到微校身份綁定頁(yè)面苏揣,輸入校園賬號(hào)(例如學(xué)號(hào))以及相應(yīng)的密碼黄鳍,
- 微校頁(yè)面數(shù)據(jù)發(fā)送到微校后臺(tái)(注:微校后臺(tái)不會(huì)保存學(xué)生的賬號(hào)和密碼),微校后臺(tái)把對(duì)應(yīng)的信息加密同時(shí)附上簽名發(fā)送到認(rèn)證方的認(rèn)證接口(認(rèn)證方需提供校驗(yàn)接口)平匈。
- 認(rèn)證方驗(yàn)證簽名框沟、解密,驗(yàn)證學(xué)生身份吐葱,返回加密后的校驗(yàn)結(jié)果街望。
- 微校收到對(duì)應(yīng)的驗(yàn)證消息,若成功則跳轉(zhuǎn)到對(duì)應(yīng)的應(yīng)用頁(yè)面弟跑,失敗則做出相應(yīng)提示灾前。
1.2 簽名算法
簽名采用微信支付后端簽名算法
1.2.1 簽名生成的通用步驟
設(shè)所有發(fā)送或者接收到的數(shù)據(jù)為集合M,將集合M內(nèi)非空參數(shù)值的參數(shù)按照參數(shù)名ASCII碼從小到大排序(字典序)孟辑,使用URL鍵值對(duì)的格式(即key1=value1&key2=value2…)拼接成字符串stringA哎甲。
特別注意以下重要規(guī)則:
- 參數(shù)名ASCII碼從小到大排序(字典序);
- 如果參數(shù)的值為空不參與簽名饲嗽;
- 參數(shù)名區(qū)分大小寫(xiě)炭玫;
- 驗(yàn)證調(diào)用返回或微信主動(dòng)通知簽名時(shí),傳送的sign參數(shù)不參與簽名貌虾,將生成的簽名與該sign值作校驗(yàn)吞加。
在stringA最后拼接上key得到stringSignTemp字符串,并對(duì)stringSignTemp進(jìn)行MD5運(yùn)算,再將得到的字符串所有字符轉(zhuǎn)換為大寫(xiě)衔憨,得到sign值signValue叶圃。
<br />
簽名算法示例(簽名驗(yàn)證地址):
private static function sign($param_array){
$names = array_keys($param_array);
sort($names, SORT_STRING);
$item_array = array();
foreach ($names as $name){
$item_array[] = "{$name}={$param_array[$name]}";
}
$secret_key = APP_SECRET;
$str = implode('&', $item_array) . '&key=' . APP_KEY;
return strtoupper(md5($str));
}
1.2.2 APP_KEY & APP_SECRET
由微校生成,每個(gè)公眾號(hào)擁有一對(duì)唯一的APP_KEY
和 APP_SECRET
践图。
APP_KEY
和 APP_SECRET
與公眾號(hào)的關(guān)系是一對(duì)一掺冠。也就是說(shuō),不同的公眾號(hào)码党,訪問(wèn)同一個(gè)身份認(rèn)證接口德崭,所用的APP_KEY
跟 APP_SECRET
是不一樣的。
1.3 數(shù)據(jù)加密
采用AES對(duì)稱加密算法(AES/CBC/ZeroPadding 128位模式)揖盘,具體算法見(jiàn)附件。
KEY = APP_KEY
IV = APP_SECRET 前16位扣讼。
2、身份認(rèn)證接口
2.1 請(qǐng)求方式
身份驗(yàn)證接口采用POST
的方式向認(rèn)證方發(fā)送數(shù)據(jù)椭符。
2.2 請(qǐng)求參數(shù)
原始數(shù)據(jù) R
:
{
"card_num":"3109005843",
"password":"helloworld",
"sign":"9A0A8659F005D6984697E2CA0A9CF3B7"http://簽名
}
通過(guò)加密R
可以得到R'
:R' = AES_CBC_ENCRYPT(R)
。
{
"raw_data":R',
"key":APP_KEY
}
微校會(huì)把上面的數(shù)據(jù)以POST
的方式發(fā)送到認(rèn)證方提供的認(rèn)證接口销钝。
加密算法見(jiàn)附件。
2.3 數(shù)據(jù)返回
返回?cái)?shù)據(jù):
{
"code":0,
"message":"success",
"raw_data":R',
"key":APP_KEY
}
關(guān)鍵點(diǎn):
- 認(rèn)證成功蒸健,認(rèn)證方需保證返回的
code
為 0座享。 - 認(rèn)證失敗,認(rèn)證方需保證返回的
code
不為 0似忧。 -
message
字段在code
不為 0 時(shí)才會(huì)有意義渣叛。 -
key
跟請(qǐng)求時(shí)的key
保持一致。 -
raw_data
對(duì)應(yīng)的R'
為加密后的數(shù)據(jù)盯捌。
通過(guò)解密R'
可得到R
:R = AES_CBC_DECRYPT(R·)
{
"card_num":"07302590", //校園賬號(hào)淳衙,一般是學(xué)號(hào)
"name":"張三豐", //學(xué)生姓名,必填
"grade":"2016", //年級(jí)饺著,必填
"college":"信息科學(xué)與技術(shù)學(xué)院", //學(xué)院
"profession":"計(jì)算機(jī)系", // 專業(yè)
"id_card":"4XXX***7", // 身份證號(hào)碼
"telephone":"137***8", // 手機(jī)號(hào)
"sign":"9A0A8659F005D6984697E2CA0A9CF3B7"http://簽名
}
其中name
箫攀、grade
為必填項(xiàng)。
3幼衰、附件
3.1 PHP 加解密
<?php
class AES
{
public static function decrypt($str, $key, $iv)
{
$decrypt = '';
for ($i = 0; $i < strlen($str); $i += 2) {
$decrypt .= chr(hexdec(substr($str, $i, 2)));
}
$decrypt = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, $decrypt, MCRYPT_MODE_CBC, $iv);
$str = '';
$decrypt = str_split($decrypt);
for ($i = 0; $i < count($decrypt); $i++) {
ord($decrypt[$i]) === 0 or $str .= $decrypt[$i];
}
return $str;
}
public static function encrypt($str, $key, $iv)
{
$encrypt = '';
$encrypt = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $str, MCRYPT_MODE_CBC, $iv);
$encrypt = bin2hex($encrypt);
return $encrypt;
}
}
3.2 Java 加解密
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public class AES
{
public static void main(String args[]) throws Exception {
/**
* 加密用的Key 可以用26個(gè)字母和數(shù)字組成靴跛,最好不要用保留字符,
* 雖然不會(huì)錯(cuò)渡嚣,至于怎么裁決梢睛,個(gè)人看情況而定
*
* key == AppSecret
*/
String cKey = "1234567890123456";
// 需要加密的字串
String cSrc = "{\"code\":\"0\",\"error_msg\":\"密碼錯(cuò)誤\",\"weixiao_openid\":\"12345678\",\"student_num\":\"888888888888\",\"name\":\"洪丹丹測(cè)試\",\"sign\":\"5C6E844C23C8F0C15AF382081D0663DC\"}";
// MD5(SHCOOL_ID) 取前16位;
String cIv = "0123456789123456";
System.out.println(cSrc);
// 加密
long lStart = System.currentTimeMillis();
String enString = AES.Encrypt(cSrc, cKey, cIv);
System.out.println("加密后的字串是:" + enString);
long lUseTime = System.currentTimeMillis() - lStart;
System.out.println("加密耗時(shí):" + lUseTime + "毫秒");
// 解密
lStart = System.currentTimeMillis();
String DeString = AES.Decrypt(enString, cKey, cIv);
System.out.println("解密后的字串是:" + DeString);
lUseTime = System.currentTimeMillis() - lStart;
System.out.println("解密耗時(shí):" + lUseTime + "毫秒");
}
public static String Encrypt(String sSrc, String sKey, String sIv) throws Exception {
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
int blockSize = cipher.getBlockSize();
byte[] dataBytes = sSrc.getBytes();
int plaintextLength = dataBytes.length;
if (plaintextLength % blockSize != 0) {
plaintextLength = plaintextLength + (blockSize - (plaintextLength % blockSize));
}
byte[] plaintext = new byte[plaintextLength];
System.arraycopy(dataBytes, 0, plaintext, 0, dataBytes.length);
SecretKeySpec keyspec = new SecretKeySpec(sKey.getBytes(), "AES");
IvParameterSpec ivspec = new IvParameterSpec(sIv.getBytes());
cipher.init(Cipher.ENCRYPT_MODE, keyspec, ivspec);
byte[] encrypted = cipher.doFinal(plaintext);
return byte2hex(encrypted).toLowerCase();
}
public static String Decrypt(String sSrc, String sKey, String sIv) throws Exception {
byte[] encrypted1 = hex2byte(sSrc);
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
SecretKeySpec keyspec = new SecretKeySpec(sKey.getBytes(), "AES");
IvParameterSpec ivspec = new IvParameterSpec(sIv.getBytes());
cipher.init(Cipher.DECRYPT_MODE, keyspec, ivspec);
byte[] original = cipher.doFinal(encrypted1);
String originalString = new String(original);
return originalString;
}
public static byte[] hex2byte(String strhex) {
if (strhex == null) {
return null;
}
int l = strhex.length();
if (l % 2 == 1) {
return null;
}
byte[] b = new byte[l / 2];
for (int i = 0; i != l / 2; i++) {
b[i] = (byte) Integer.parseInt(strhex.substring(i * 2, i * 2 + 2),
16);
}
return b;
}
public static String byte2hex(byte[] b) {
String hs = "";
String stmp = "";
for (int n = 0; n < b.length; n++) {
stmp = (java.lang.Integer.toHexString(b[n] & 0XFF));
if (stmp.length() == 1) {
hs = hs + "0" + stmp;
} else {
hs = hs + stmp;
}
}
return hs.toUpperCase();
}
}