身份验证技术方案

身份验证技术方案


  • 1、身份认证流程
    • 1.1 账号密码验证身份
    • 1.2 签名算法
    • 1.3 数据加密
  • 2、身份认证接口
    • 2.1 请求方式
    • 2.2 请求参数
    • 2.3 数据返回
  • 3、附件
    • 3.1 PHP 加解密
    • 3.2 Java 加解密

1、身份认证流程

1.1 账号密码验证身份

身份认证是其他应用的基础,认证方(即:校方)需要提供一个验证接口,具体身份绑定流程如下:


图片描述

身份认证绑定步骤:

  • 学生在微信客户端打开应用,触发微信公众号授权(授权页面提示授权给腾讯微校)。
  • 微信公众号授权后,回调跳转到微校身份绑定页面,输入校园账号(例如学号)以及相应的密码,
  • 微校页面数据发送到微校后台(注:微校后台不会保存学生的账号和密码),微校后台把对应的信息加密同时附上签名发送到认证方的认证接口(认证方需提供校验接口)。
  • 认证方验证签名、解密,验证学生身份,返回加密后的校验结果。
  • 微校收到对应的验证消息,若成功则跳转到对应的应用页面,失败则做出相应提示。

1.2 签名算法

签名采用微信支付后端签名算法

1.2.1 签名生成的通用步骤

设所有发送或者接收到的数据为集合M,将集合M内非空参数值的参数按照参数名ASCII码从小到大排序(字典序),使用URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串stringA。
特别注意以下重要规则:

  • 参数名ASCII码从小到大排序(字典序);
  • 如果参数的值为空不参与签名;
  • 参数名区分大小写;
  • 验证调用返回或微信主动通知签名时,传送的sign参数不参与签名,将生成的签名与该sign值作校验。

在stringA最后拼接上key得到stringSignTemp字符串,并对stringSignTemp进行MD5运算,再将得到的字符串所有字符转换为大写,得到sign值signValue。
<br />
签名算法示例(签名验证地址):

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

由微校生成,每个公众号拥有一对唯一的APP_KEYAPP_SECRET
APP_KEYAPP_SECRET 与公众号的关系是一对一。也就是说,不同的公众号,访问同一个身份认证接口,所用的APP_KEYAPP_SECRET 是不一样的。

1.3 数据加密

采用AES对称加密算法(AES/CBC/ZeroPadding 128位模式),具体算法见附件。

KEY = APP_KEY
IV = APP_SECRET 前16位。

2、身份认证接口

2.1 请求方式

身份验证接口采用POST的方式向认证方发送数据。

2.2 请求参数

原始数据 R

{
    "card_num":"3109005843",
    "password":"helloworld",
    "sign":"9A0A8659F005D6984697E2CA0A9CF3B7"//签名
}

通过加密R可以得到R'R' = AES_CBC_ENCRYPT(R)

{
    "raw_data":R',
    "key":APP_KEY
}

微校会把上面的数据以POST的方式发送到认证方提供的认证接口。

加密算法见附件。

2.3 数据返回

返回数据:

{
    "code":0,
    "message":"success",
    "raw_data":R',
    "key":APP_KEY
}

关键点:

  • 认证成功,认证方需保证返回的code 为 0。
  • 认证失败,认证方需保证返回的code 不为 0。
  • message 字段在 code 不为 0 时才会有意义。
  • key 跟请求时的 key 保持一致。
  • raw_data 对应的 R' 为加密后的数据。
    通过解密R' 可得到 RR = AES_CBC_DECRYPT(R·)
{
    "card_num":"07302590", //校园账号,一般是学号
    "name":"张三丰", //学生姓名,必填
    "grade":"2016", //年级,必填
    "college":"信息科学与技术学院", //学院
    "profession":"计算机系", // 专业
    "id_card":"4XXX***7", // 身份证号码
    "telephone":"137***8", // 手机号
    "sign":"9A0A8659F005D6984697E2CA0A9CF3B7"//签名
}

其中namegrade 为必填项。

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个字母和数字组成,最好不要用保留字符,
         * 虽然不会错,至于怎么裁决,个人看情况而定
         *
         * key == AppSecret
         */
        String cKey = "1234567890123456";
        // 需要加密的字串
        String cSrc = "{\"code\":\"0\",\"error_msg\":\"密码错误\",\"weixiao_openid\":\"12345678\",\"student_num\":\"888888888888\",\"name\":\"洪丹丹测试\",\"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("加密耗时:" + lUseTime + "毫秒");
        // 解密
        lStart = System.currentTimeMillis();
        String DeString = AES.Decrypt(enString, cKey, cIv);
        System.out.println("解密后的字串是:" + DeString);
        lUseTime = System.currentTimeMillis() - lStart;
        System.out.println("解密耗时:" + 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();
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,544评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,430评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,764评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,193评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,216评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,182评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,063评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,917评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,329评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,543评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,722评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,425评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,019评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,671评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,825评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,729评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,614评论 2 353

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,652评论 18 139
  • 国家电网公司企业标准(Q/GDW)- 面向对象的用电信息数据交换协议 - 报批稿:20170802 前言: 排版 ...
    庭说阅读 10,958评论 6 13
  • Guide to BluetoothSecurity原文 本出版物可免费从以下网址获得:https://doi.o...
    公子小水阅读 7,977评论 0 6
  • 1. 实习的时候,因年龄相仿,和两个同事走得比较近。中午一起吃饭,同事A提议喝奶茶。B自告奋勇,收集口味信息后便出...
    写给自己的救赎阅读 426评论 0 6
  • 微信里的重聚 13年底,开通了微信,加了很多新老朋友;原先大学毕业后失去联系的同学、曾经的同事、邻居、友人在朋友圈...
    七点起床阅读 239评论 0 1