一,小程序登录
小程序登录 | 微信开放文档
接口应在服务器端调用,详细说明参见服务端API。
接口说明
接口英文名
code2Session
功能描述
登录凭证校验。通过 wx.login 接口获得临时登录凭证 code 后传到开发者服务器调用此接口完成登录流程。更多使用方法详见小程序登录。
调用方式
HTTPS 调用
GET https://api.weixin.qq.com/sns/jscode2session 
 
请求参数
| 属性 | 类型 | 必填 | 说明 | 
|---|---|---|---|
| appid | string | 是 | 小程序 appId | 
| secret | string | 是 | 小程序 appSecret | 
| js_code | string | 是 | 登录时获取的 code,可通过wx.login获取 | 
| grant_type | string | 是 | 授权类型,此处只需填写 authorization_code | 
返回参数
| 属性 | 类型 | 说明 | 
|---|---|---|
| session_key | string | 会话密钥 | 
| unionid | string | 用户在开放平台的唯一标识符,若当前小程序已绑定到微信开放平台账号下会返回,详见 UnionID 机制说明。 | 
| errmsg | string | 错误信息 | 
| openid | string | 用户唯一标识 | 
| errcode | int32 | 错误码 | 
调用示例
示例说明: HTTPS请求
请求数据示例
GET https://api.weixin.qq.com/sns/jscode2session?appid=APPID&secret=SECRET&js_code=JSCODE&grant_type=authorization_code 
 
返回数据示例
{
"openid":"xxxxxx",
"session_key":"xxxxx",
"unionid":"xxxxx",
"errcode":0,
"errmsg":"xxxxx"
}
 
 
错误码
| 错误码 | 错误码取值 | 解决方案 | 
|---|---|---|
| 40029 | code 无效 | js_code无效 | 
| 45011 | api minute-quota reach limit mustslower retry next minute | API 调用太频繁,请稍候再试 | 
| 40226 | code blocked | 高风险等级用户,小程序登录拦截 。风险等级详见用户安全解方案 | 
| -1 | system error | 系统繁忙,此时请开发者稍候再试 | 
二,小程序登录流程图
小程序登录 | 微信开放文档
小程序可以通过微信官方提供的登录能力方便地获取微信提供的用户身份标识,快速建立小程序内的用户体系。
登录流程时序

说明:
- 调用 wx.login() 获取 临时登录凭证code ,并回传到开发者服务器。
 - 调用 auth.code2Session 接口,换取 用户唯一标识 OpenID 、 用户在微信开放平台账号下的唯一标识UnionID(若当前小程序已绑定到微信开放平台账号) 和 会话密钥 session_key。
 
之后开发者服务器可以根据用户标识来生成自定义登录态,用于后续业务逻辑中前后端交互时识别用户身份。
注意:
- 会话密钥 
session_key是对用户数据进行 加密签名 的密钥。为了应用自身的数据安全,开发者服务器不应该把会话密钥下发到小程序,也不应该对外提供这个密钥。 - 临时登录凭证 code 只能使用一次
 
三,接口调用频率规范
接口调用频率规范 | 微信开放文档
概念介绍
小程序wx接口可分为“普通接口”和“限频接口”。
“限频接口”指的是一个用户在一段时间内不允许频繁调用的wx接口,此类接口一般会调用到微信后台系统资源,为了保护系统,同时防止用户资源被滥用,开发者需要对此类接口做适度的频率限制,不能无节制地调用。
平台会对小程序内“限频接口”的调用情况做监控,如果小程序对此类接口的调用频率超出平台的规范,将会收到站内信提醒。系统会在资源紧张的情况下优先保障合理使用的小程序的服务。
开发者可登录小程序管理后台-开发管理-接口设置中查看“限频接口”调用情况。
目前,“限频接口”包括以下接口:
- wx.login
 - wx.checkSession
 - wx.getSetting
 - wx.getUserInfo
 - wx.getUserProfile
 
优化方法
开发者可以参考以下方法对“限频接口”的调用频率做优化:
- 把上一次调用接口的返回结果缓存下来以供后续逻辑复用,而不是重新调用接口
 - 避免在定时循环的逻辑内重复调用“限频接口”
 - 避免在页面初始化事件
onLoad、onShow、onReady中调用限频接口,应该在小程序初始化事件onLaunch中调用 
四,登录流程及服务端缓存方案
登录流程
- 打开小程序,前端调用 wx.login() 获取 临时登录凭证code ,并回传到开发者服务器。
 - 调用 auth.code2Session 接口,换取 用户唯一标识 OpenID 、 用户在微信开放平台账号下的唯一标识UnionID(若当前小程序已绑定到微信开放平台账号) 和 会话密钥 session_key。
 
实际项目中登录及服务端缓存方案
1. 打开小程序,前端调用 wx.login() 获取 临时登录凭证code ,并回传到开发者服务器。
2.调用登录服务端接口,参数如下(登录请求入参),后台做如下判断
1>判断code是否为空,
不为空
调用微信接口code2Session,获取openID及会话密钥session_key,并缓存,key=前缀+session_key,value = openID,过期时间10min
为空
根据请求中,sessionKey直接从缓存中获取openID
空,抛出异常非法登录
非空,继续后续流程
想一下什么时候code为空,什么时候不为空
打开小程序,如下二图,调用登录服务端登录接口,入参只有一个code
再次点击‘允许’时,再次调用登录服务端登录接口,入参没有code,有其他参数如下
{
     "mobileEncryptedData":"pRQAS9OpYTlfJIWnznDpHV6QWUmBQNDnrx9pym1cBkoknXz+B3sCrdAyF8crXR2QeqO3A0x3OUUyKYqtfVPC7dtluvIKxdoWDMkEnlMgaaO7E16YDR9KPA85oWtCmTVDsvmxdBvq7uuc+TVx5CfU+JIqDlDFO6M4YRf++uvET0pkHvgA3fXuHA//sMgGNzzbm/rEFAq0u1A1c9zlOdhS2A==",
     "iv":"czh4XUrTs0egv0CVsbSFBg==",
     "openId":"oDCsO5MY_eiI0hgDABslcGQ03JeE",
     "sessionKey":"XmOmQ77cR01vByTcchk9tQ==",
     "tlLoginLog":{
         "deviceBrand":"devtools",
         "deviceModel":"iPhone 5",
         "systemVersion":"iOS 10.0.1",
         "systemPlatform":"devtools",
         "basicLibraryVersion":"3.0.0",
         "systemLanguage":"zh_CN",
         "appVersion":"8.0.5",
         "wifi":1,
         "bluetooth":1,
         "geographical":1,
         "logonChannel":"miniprogram-wechat",
         "applicationType":"",
         "appCode":"jghc",
         "appEditions":"1.7.11"
     }
 }


登录请求入参
public class MobileDTO {
    @ApiModelProperty(name = "mobileEncryptedData", value = "加密字符串")
     private String mobileEncryptedData;
    @ApiModelProperty(name = "iv", value = "偏移量")
     private String iv;
    @ApiModelProperty(name = "pic", value = "pic")
     private String pic;
    @ApiModelProperty(name = "code", value = "code")
     private String code;
    @ApiModelProperty(name = "sessionKey", value = "sessionKey")
     private String sessionKey;
    @ApiModelProperty(name = "appCode", value = "每个平台的code")
     private String appCode;
    @ApiModelProperty(name = "tlLoginLog", value = "新增设备信息")
     private TlLoginLog tlLoginLog;
    @ApiModelProperty(name = "toc", value = "1,2")
     private String toc;
 }
五,关于手机号加解密
参考
对称加密算法 - 廖雪峰的官方网站
1,前端加密
AES对称加密,密钥sessionKey,偏移量iv
注意加密后的手机号密文,sessionkey,iv都进行了base64编码
2,后端解密
根据入参密钥sessionKey,偏移量iv,解密
代码如下
public static JSONObject getUserInfo(String encryptedData, String sessionKey, String iv) {
        // 被加密的数据
        byte[] dataByte = Base64.decode(encryptedData);
        // 加密秘钥
        byte[] keyByte = Base64.decode(sessionKey);
        // 偏移量
        byte[] ivByte = Base64.decode(iv);
        try {
            // 如果密钥不足16位,那么就补足.  这个if 中的内容很重要
            int base = 16;
            if (keyByte.length % base != 0) {
                int groups = keyByte.length / base + (keyByte.length % base != 0 ? 1 : 0);
                byte[] temp = new byte[groups * base];
                Arrays.fill(temp, (byte) 0);
                System.arraycopy(keyByte, 0, temp, 0, keyByte.length);
                keyByte = temp;
            }
            // 初始化
            Security.addProvider(new BouncyCastleProvider());
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding", "BC");
            SecretKeySpec spec = new SecretKeySpec(keyByte, "AES");
            AlgorithmParameters parameters = AlgorithmParameters.getInstance("AES");
            parameters.init(new IvParameterSpec(ivByte));
            cipher.init(Cipher.DECRYPT_MODE, spec, parameters);// 初始化
            byte[] resultByte = cipher.doFinal(dataByte);
            if (null != resultByte && resultByte.length > 0) {
                String result = new String(resultByte, "UTF-8");
                return JSONObject.parseObject(result);
            }
        } catch (Exception e) {
            log.error("微信用户信息解密失败!", e);
        }
        return null;
    } 
 
                


















