上篇文章说了微信扫码登录的原理,趁热打铁,说一下微信小程序授权登录,这个还是比较简单的,在平时的工作中,也经常遇到;
基础术语:
1. code: 调用wx.login后返回的临时登录凭证,可请求微信服务器换取openId和session_key
2. openId:用户唯一标识,同一用户在不同的应用中不一致
3. session_key:对用户数据进行加密签名的密钥
4. appId:小程序唯一标识,申请小程序成功后获得分配
5. unionId:用户在开放平台的唯一标识符,同一用户在同一账号下的所有应用中一致
小程序登录流程示意图:
根据微信官方提供的登录流程时序图可以清楚的了解小程序登录需要多少个步骤,下面我们来总结下:
- 小程序启动,通过wx.login()获取code
- 开发者服务器需要提供一个登录的接口,参数就是小程序获取的code
- 登录接口收到code后,调用微信提供的接口进行code的验证
- 得到验证结果,成功后能得到一个session_key和openid
- 生成一个自定义的key, 将session_key和openid跟自定义的key关联起来
- 将自定义的key返回给小程序
- 每次请求都带上key, 后端根据key获取openid识别当前用户身份
对,你没有看错,就是这么的简单!这里我只分析后台代码(java)。
首先code是微信给的,如果你随意生成code去验证肯定是无效的,只有微信给的code才有效。code传到开发者自己的服务后,再去问微信:
通过前台传过来的code,我们怎么知道,这个code是否有效?
这个是WechatUtil代码:
/** * @ClassName WechatUtil * @Description * @Author crismao * @Date 9:44 2019/3/20 * @Version 1.0 */@Configurationpublic class WechatUtil { @Value("${wx.miniapp.appId}") private String appid; @Value("${wx.miniapp.secret}") private String secret; @Value("${wx.miniapp.sessionHost}") private String sessionHost; //微信小程序登录 public JSONObject getSessionKeyOrOpenId(String code) { //String requestUrl = "https://api.weixin.qq.com/sns/jscode2session"; String requestUrl = sessionHost; Map<String, String> requestUrlParam = new HashMap<>(); // https://mp.weixin.qq.com/wxopen/devprofile?action=get_profile&token=164113089&lang=zh_CN //小程序appId requestUrlParam.put("appid", appid); //小程序secret requestUrlParam.put("secret",secret); //小程序端返回的code requestUrlParam.put("js_code", code); //默认参数 requestUrlParam.put("grant_type", "authorization_code"); //发送post请求读取调用微信接口获取openid用户唯一标识 JSONObject jsonObject = JSON.parseObject(HttpClientUtil.doPost(requestUrl, requestUrlParam)); return jsonObject; } //获取微信小程序的AccessToken public JSONObject getAccessToken() { String requestUrl = "https://api.weixin.qq.com/cgi-bin/token"; Map<String, String> requestUrlParam = new HashMap<>(); // https://mp.weixin.qq.com/wxopen/devprofile?action=get_profile&token=164113089&lang=zh_CN //默认参数 requestUrlParam.put("grant_type", "client_credential"); //小程序appId requestUrlParam.put("appid", appid); //小程序secret requestUrlParam.put("secret",secret); //发送post请求读取调用微信接口获取openid用户唯一标识 JSONObject jsonObject = JSON.parseObject(HttpClientUtil.doPost(requestUrl, requestUrlParam)); return jsonObject; }}
这个是HttpClientutil代码:
public class HttpClientUtil { public static String doGet(String url, Map<String, String> param) { // 创建Httpclient对象 CloseableHttpClient httpclient = HttpClients.createDefault(); String resultString = ""; CloseableHttpResponse response = null; try { // 创建uri URIBuilder builder = new URIBuilder(url); if (param != null) { for (String key : param.keySet()) { builder.addParameter(key, param.get(key)); } } URI uri = builder.build(); // 创建http GET请求 HttpGet httpGet = new HttpGet(uri); // 执行请求 response = httpclient.execute(httpGet); // 判断返回状态是否为200 if (response.getStatusLine().getStatusCode() == 200) { resultString = EntityUtils.toString(response.getEntity(), "UTF-8"); } } catch (Exception e) { e.printStackTrace(); } finally { try { if (response != null) { response.close(); } httpclient.close(); } catch (IOException e) { e.printStackTrace(); } } return resultString; } public static String doGet(String url) { return doGet(url, null); } public static String doPost(String url, Map<String, String> param) { // 创建Httpclient对象 CloseableHttpClient httpClient = HttpClients.createDefault(); CloseableHttpResponse response = null; String resultString = ""; try { // 创建Http Post请求 HttpPost httpPost = new HttpPost(url); // 创建参数列表 if (param != null) { List paramList = new ArrayList<>(); for (String key : param.keySet()) { paramList.add(new BasicNameValuePair(key, param.get(key))); } // 模拟表单 UrlEncodedFormEntity entity = new UrlEncodedFormEntity(paramList); httpPost.setEntity(entity); } // 执行http请求 response = httpClient.execute(httpPost); resultString = EntityUtils.toString(response.getEntity(), "utf-8"); } catch (Exception e) { e.printStackTrace(); } finally { try { if (ObjectUtil.isNotNull(response)){ response.close(); } } catch (IOException e) { e.printStackTrace(); } } return resultString; } public static String doPost(String url) { return doPost(url, null); } public static String doPostJson(String url, String json) { // 创建Httpclient对象 CloseableHttpClient httpClient = HttpClients.createDefault(); CloseableHttpResponse response = null; String resultString = ""; try { // 创建Http Post请求 HttpPost httpPost = new HttpPost(url); // 创建请求内容 StringEntity entity = new StringEntity(json, ContentType.APPLICATION_JSON); httpPost.setEntity(entity); // 执行http请求 response = httpClient.execute(httpPost); resultString = EntityUtils.toString(response.getEntity(), "utf-8"); } catch (Exception e) { e.printStackTrace(); } finally { try { response.close(); } catch (IOException e) { e.printStackTrace(); } } return resultString; }}
code有效的情况下微信还会给你一个用户的标识,也就是openid,同时还会有一个session_key,也就是会话的key。session_key的有效期默认是2小时,当用户一直在使用小程序的话会自动刷新,这个是由微信这边来维护的。
注意:
- 会话密钥 session_key 是对用户数据进行 加密签名 的密钥。为了应用自身的数据安全,开发者服务器不应该把会话密钥下发到小程序,也不应该对外提供这个密钥。
- 临时登录凭证 code 只能使用一次
拿到openid之后,就可以处理我们的业务逻辑了,我推荐2种方式来做关联:
第一种:随机生成key, 关联openid,存入redis中,当请求带入key,直接从redis中获取openid得到当前用户信息,这个其实也就是我们自己去维护了会话信息
第二种:采用JWT生成token,将openid绑定到token中,将token返回给小程序,请求的时候带上token,通过解析token得到用户信息。