微信小程序登录

微信各接口定义(残缺版):

package com.cong.security.core.properties;

public class WXConstant {
	/** 获取access_token. */
	public static String GET_ACCESS_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s";
	/** 根据openId获取用户信息 */
	public static String URL_GET_USER_INFO = "https://api.weixin.qq.com/sns/userinfo?openid=";
	/** 用oauth2获取用户信息. */
	public static String OAUTH2_USERINFO_URL = "https://api.weixin.qq.com/sns/userinfo?access_token=%s&openid=%s&lang=%s";
	/** 验证oauth2的access token是否有效. */
	public static String OAUTH2_VALIDATE_TOKEN_URL = "https://api.weixin.qq.com/sns/auth?access_token=%s&openid=%s";
	
	/** 微信小程序code换取openId接口 */
	public static String CODE_CHANGE_OPENID = "https://api.weixin.qq.com/sns/jscode2session?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code";
	/** 微信小程序消息回复发送接口 */
	public static String SEND_MSG = "https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=";
}

微信小程序登录

返回值封装(code换取openId)
package com.cong.security.core.social.weixin.mini.connect;
import java.io.Serializable;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
public class WxminiRes implements Serializable{

	private static final long serialVersionUID = 1L;

	private String openId;
	
	private String sessionKey;

	public WxminiRes(String openId, String sessionKey) {
		super();
		this.openId = openId;
		this.sessionKey = sessionKey;
	}
}
微信小程序获取openID
package com.cong.security.core.social.weixin.mini.connect;

import java.security.Security;
import org.apache.commons.lang3.StringUtils;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import com.cong.security.core.code.CodeException;
import com.cong.security.core.properties.WXConstant;
import org.springframework.web.client.RestTemplate;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class WxMiniOauth2Template {

	static {
		// BouncyCastle是一个开源的加解密解决方案,主页在http://www.bouncycastle.org/
		Security.addProvider(new BouncyCastleProvider());
	}

	private String appId;

	private String appSecret;

	public WxMiniOauth2Template(String appId, String appSecret) {
		super();
		this.appId = appId;
		this.appSecret = appSecret;
	}

	/**
	 * 微信授权码获取openId
	 * 
	 * @param code
	 *            授权码
	 * @return 微信openId
	 * @author single-聪
	 * @date 2020年7月30日
	 * @version 1.7.2
	 */
	public WxminiRes getOpenId(String code) {
		RestTemplate restTemplate = new RestTemplate();
		String url = String.format(WXConstant.CODE_CHANGE_OPENID, this.appId, this.appSecret, code);
		String res = restTemplate.getForObject(url, String.class);
		log.info("微信小程序登录返回值为:[{}]", res);
		JSONObject data = JSONObject.parseObject(res);
		String openId = data.getString("openid");
		// 返回错误码时直接返回空
		if ((data.getString("errcode") == null || !"0".equals(data.getString("errcode")))
				&& StringUtils.isNotBlank(openId)) {
			// 额外的session_key字段
			return new WxminiRes(openId, data.getString("session_key"));
		} else {
			throw new CodeException("登录失败");
		}
	}
}

根据前端传输的code换取微信小程序openId,CodeException为自定义异常,目的是为了异常捕获,自己定义一个即可。拿到用户openId即可实现登录。

用户信息解密

主要用于用户不存在时设置默认信息、此时需要用到获取openId是返回的session_key,所以上一步需要合理保存。

返回值封装
package com.cong.security.core.social.weixin.mini.connect;

import java.io.Serializable;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
public class WeiXinMiniUserInfo implements Serializable{

	private static final long serialVersionUID = 1L;
	/** 用户唯一标识 */
	private String openId;
	/** 用户昵称 */
	private String nickName;
	/** 用户性别 */
	private String gender;
	/** 普通用户个人资料填写的城市 */
	private String city;
	/** 普通用户个人资料填写的省份 */
	private String province;
	/** 国家,如中国为CN */
	private String country;
	/** 用户头像,最后一个数值代表正方形头像大小(有0、46、64、96、132数值可选,0代表640*640正方形头像),用户没有头像时该项为空 */
	private String avatarUrl;
	/** 用户统一标识。针对一个微信开放平台帐号下的应用,同一用户的unionid是唯一的。 */
	private String unionId;
}
解密工具类
package com.cong.util;

import java.security.AlgorithmParameters;
import java.security.Security;
import java.util.Arrays;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base64;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import com.cong.security.core.social.weixin.mini.connect.WeiXinMiniUserInfo;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class AesUtil {
	static {
		// BouncyCastle是一个开源的加解密解决方案,主页在http://www.bouncycastle.org/
		Security.addProvider(new BouncyCastleProvider());
	}

	public static WeiXinMiniUserInfo decrypt(String sessionKey, String encryptedData, String iv) {
		// 被加密的数据
		byte[] dataByte = Base64.decodeBase64(encryptedData);
		// 加密秘钥
		byte[] keyByte = Base64.decodeBase64(sessionKey);
		// 偏移量
		byte[] ivByte = Base64.decodeBase64(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");
			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");
				log.info("解析出用户数据为:[{}]", result);
				WeiXinMiniUserInfo userInfo = JSONObject.parseObject(result, WeiXinMiniUserInfo.class);
				return userInfo;
			}
		} catch (Exception e) {
			log.info("微信小程序数据解析异常:[{}]", e.getMessage());
		}
		return null;
	}
}
解密所需参数

获取用户信息

参数解释获取
encryptedData包括敏感数据在内的完整用户信息的加密数据wx.getUserInfo(Object object)
iv加密算法的初始向量wx.getUserInfo(Object object)
session_key会话密钥code换取openId时返回

注意:可以在上一个接口中返回session_key,但是小程序官网不建议将session_key返回给前端、所以后端需要存储并且在两次接口请求中能够辨别是同一个用户,从而获取到正确的session_key,获取到正确的session_key有有效期、好像只可以使用一次而且只有最新的有效!!!所以前端的login和getUserInfo接口调用有顺序而且有次数规则。(本文为Java后端开发,前端调用简单介绍)

因为是第三方登录,所有有两种情况需要考虑:

  • 用户已注册:此时小程序用户信息解密失败也是没什么问题,毕竟不会使用这个数据覆盖自己系统中的用户信息
  • 用户未注册:此时需要考虑用户信息解密失败的情况,为了良好的用户体验,即使解密失败(可能由于网络等原因解密失败、前一步code2session验证通过实际上这个用户基本上就是真实用户了)也应该设置默认值而不是提示用户注册失败

本文使用的登录是基于Oauth2协议使用JWT生成令牌,不同项目代码嵌入方式不同,所以只封装了和微信那边的对接,至于在自己代码中的业务逻辑自行编写即可。

对接有几天了,可能部分代码会缺失,如有缺失请留言。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值