微信web开发只需要code值,但是小程序需要一个code值,一个encryptData,一个iv
首先先看图
箭头部分为微信给我们的,就是前端需要传过来的。
步骤为:
- 小程序客户端调用wx.login,回调里面包含js_code。
- 然后将js_code发送到服务器A(开发者服务器),服务器A向微信服务器发起请求附带js_code、appId、secretkey和grant_type参数,以换取用户的openid和session_key(会话**)。
- 服务器A拿到session_key后,生成一个随机数我们叫3rd_session,以3rdSessionId为key,以session_key + openid为value缓存到redis或memcached中;因为微信团队不建议直接将session_key在网络上传输,由开发者自行生成唯一键与session_key关联。其作用是:
- 将3rdSessionId返回给客户端,维护小程序登录态。
- 通过3rdSessionId找到用户session_key和openid。
- 客户端拿到3rdSessionId后缓存到storage,
- 通过wx.getUserIinfo可以获取到用户敏感数据encryptedData 。
- 客户端将encryptedData、3rdSessionId和偏移量一起发送到服务器A
- 服务器A根据3rdSessionId从缓存中获取session_key
- 在服务器A使用AES解密encryptedData,从而实现用户敏感数据解密
重点在6、7、8三个环节。
AES解密三个参数:
- 密文 encryptedData
- ** aesKey
- 偏移向量 iv
概念性的东西就这些,下面看代码
首先前端需要传给我们的东西就是三个一个code值,一个encryptData,一个iv,
先写方法
package com.everest.academy.util;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.encoders.Base64;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.URL;
import java.net.URLConnection;
import java.security.AlgorithmParameters;
import java.security.Security;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
/**
* @ClassName XcxUtils
* @Description 微信小程序方法
* @Author 田野
* @Data 22:14
* @Version 1.0
**/
@Slf4j
public class XcxUtils {
/**
* 获取微信小程序 session_key 和 openid
*
* @author zhy
* @param code
* 调用微信登陆返回的Code
* @return
*/
public static JSONObject getSessionKeyOropenid(String code, String appid, String secret) {
String requestUrl = "https://api.weixin.qq.com/sns/jscode2session"; // 请求地址
Map<String, String> requestUrlParam = new HashMap<String, String>();
requestUrlParam.put("appid", appid); // 开发者设置中的appId
requestUrlParam.put("secret", secret); // 开发者设置中的appSecret
requestUrlParam.put("js_code", code); // 小程序调用wx.login返回的code
requestUrlParam.put("grant_type", "authorization_code"); // 默认参数
// 发送post请求读取调用微信 https://api.weixin.qq.com/sns/jscode2session
// 接口获取openid用户唯一标识
JSONObject jsonObject = JSON.parseObject(sendPost(requestUrl, requestUrlParam));
System.out.println(jsonObject);
return jsonObject;
}
/**
* 解密用户敏感数据获取用户信息
*
* @author zhy
* @param sessionKey
* 数据进行加密签名的**
* @param encryptedData
* 包括敏感数据在内的完整用户信息的加密数据
* @param iv
* 加密算法的初始向量
* @return
*/
public static JSONObject getUserInfo(String encryptedData, String sessionKey, String iv) {
encryptedData=encryptedData.replace(" ", "+");
sessionKey=sessionKey.replace(" ", "+");
iv=iv.replace(" ", "+");
// 被加密的数据
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 JSON.parseObject(result);
}
} catch (Exception e) {
log.error(e.getMessage(), e);
}
return null;
}
/**
* 向指定 URL 发送POST方法的请求
*
* @param url
* 发送请求的 URL
* @param
*
* @return 所代表远程资源的响应结果
*/
public static String sendPost(String url, Map<String, ?> paramMap) {
PrintWriter out = null;
BufferedReader in = null;
String result = "";
String param = "";
Iterator<String> it = paramMap.keySet().iterator();
while (it.hasNext()) {
String key = it.next();
param += key + "=" + paramMap.get(key) + "&";
}
try {
URL realUrl = new URL(url);
// 打开和URL之间的连接
URLConnection conn = realUrl.openConnection();
// 设置通用的请求属性
conn.setRequestProperty("accept", "*/*");
conn.setRequestProperty("connection", "Keep-Alive");
conn.setRequestProperty("Accept-Charset", "utf-8");
conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
// 发送POST请求必须设置如下两行
conn.setDoOutput(true);
conn.setDoInput(true);
// 获取URLConnection对象对应的输出流
out = new PrintWriter(conn.getOutputStream());
// 发送请求参数
out.print(param);
// flush输出流的缓冲
out.flush();
// 定义BufferedReader输入流来读取URL的响应
in = new BufferedReader(new InputStreamReader(conn.getInputStream(), "UTF-8"));
String line;
while ((line = in.readLine()) != null) {
result += line;
}
} catch (Exception e) {
log.error(e.getMessage(), e);
}
// 使用finally块来关闭输出流、输入流
finally {
try {
if (out != null)
{
out.close();
}
if (in != null)
{
in.close();
}
} catch (IOException ex) {
ex.printStackTrace();
}
}
return result;
}
}
注解写的很明白的,应该没什么看不懂。
之后就是service层
package com.everest.academy.service.impl;
import com.alibaba.fastjson.JSONObject;
import com.everest.academy.business.dto.LoginUserDTO;
import com.everest.academy.business.dto.WechatTokenDto;
import com.everest.academy.framework.exception.ResourceIsNullException;
import com.everest.academy.framework.pojo.User;
import com.everest.academy.persistence.mapper.UserMapper;
import com.everest.academy.service.WechatService;
import com.everest.academy.util.WechatUtil;
import com.everest.academy.util.XcxUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Date;
/**
* @ClassName WechatServiceImpl
* @Description 微信sgervice
* @Author 田野
* @Data 20:25
* @Version 1.0
**/
@Slf4j
@Service
public class WechatServiceImpl implements WechatService {
@Autowired
UserMapper userMapper;
@Override
public LoginUserDTO getUserByCode(String code, String encryptedData, String iv)throws Exception{
log.info("传进来的值"+encryptedData+"另一个"+iv);
JSONObject shopAddress = null;
if (StringUtils.isNotEmpty(code)) {
String appid = "填自己的";
String secret = "填自己的";
shopAddress = XcxUtils.getSessionKeyOropenid(code, appid, secret);
}
assert shopAddress != null;
String openId = shopAddress.getString("openid");
String sessionKey = shopAddress.getString("session_key");
log.info("session_key为:"+sessionKey);
JSONObject user1 = XcxUtils.getUserInfo(encryptedData, sessionKey, iv);
//user这里根据用户openId查询是否有这个用户。
User user=userMapper.findByOpenId(openId);
LoginUserDTO loginUserDTO=new LoginUserDTO();
if (user!=null){
log.info("用户的状态"+user.getState());
if (user.getState()==1){
//有的话,直接就进入 ,直接将信息返回给前端
loginUserDTO.setId(user.getId());
loginUserDTO.setOpenId(openId);
return loginUserDTO;
}
throw new Exception("无法登录,账号被冻结");
}
//没有的话,创建该学生的信息,然后再传给部分数据给前端
User newUser=new User();
newUser.setOpenId(openId);
assert user1 != null;
newUser.setAddress(user1.getString("city"));
newUser.setHeadImgUrl(user1.getString("avatarUrl"));
newUser.setName(user1.getString("nickName"));
newUser.setState(1);
newUser.setBeans(0);
newUser.setBinding(0);
newUser.setCreate_at(new Date().getTime());
newUser.setCreate_by("系统创建");
userMapper.insert(newUser);
User user2=userMapper.findByOpenId(openId);
LoginUserDTO loginUserDTO1=new LoginUserDTO();
loginUserDTO1.setId(user2.getId());
log.info("id的值"+newUser.getId());
loginUserDTO1.setOpenId(newUser.getOpenId());
loginUserDTO1.setBinding(newUser.getState());
return loginUserDTO1;
}
}
也写的挺清楚的,通过openId判断是否有这个用户
controller
package com.everest.academy.controller;
import com.everest.academy.business.vo.ResponseVo;
import com.everest.academy.framework.exception.ResourceIsNullException;
import com.everest.academy.service.WechatService;
import com.everest.academy.util.ResultUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
/**
* @ClassName WechatController
* @Description 微信登录验证
* @Author 田野
* @Data 15:56
* @Version 1.0
**/
@Slf4j
@Api(tags = "WechatController",description = "微信开发API")
@RestController
@RequestMapping("/a/home")
@Validated
public class WechatController {
@Autowired
WechatService wechatService;
@ApiOperation(value = "微信登录验证",notes = "通过获取的codeId值登录")
@PostMapping("/{code}")
public ResponseVo WechatLogin( @PathVariable("code") String code,
@RequestParam("encryptedData") String encryptedData,@RequestParam("iv") String iv)throws Exception{
log.info("传进来的未"+encryptedData +"iv为" + iv);
return ResultUtil.success("微信登录成功",wechatService.getUserByCode(code,encryptedData,iv));
}
}
就有一点问题
测试的时候,每次传参都读不到+号,每次都把我的+号弄掉。
所以这里运用了一个replace,完美解决
微信网页开发,通过codeId得到access_token,通过access_token和openid获取用户基本信息
package com.everest.academy.util;
import com.everest.academy.business.dto.LoginUserDTO;
import com.everest.academy.business.dto.WechatTokenDto;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import java.util.HashMap;
import java.util.Map;
/**
* @ClassName WechatUtil
* @Description TODO
* @Author 田野
* @Data 19:25
* @Version 1.0
**/
@Slf4j
public class WechatUtil {
public final static String APPID = "自己的";
public final static String APPSECRET = "自己的";
/**
* 获取请求用户信息的access_token
* @param code
* @return
*/
public static WechatTokenDto getUserInfoAccessToken(String code) {
JsonObject object = null;
WechatTokenDto wechatTokenDto=new WechatTokenDto();
Map<String, String> data = new HashMap();
try {
String url = String.format("https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code",
APPID, APPSECRET, code);
log.info("request accessToken from url: {}", url);
CloseableHttpClient httpClient =HttpClients.createDefault();
HttpGet httpGet = new HttpGet(url);
HttpResponse httpResponse = httpClient.execute(httpGet);
HttpEntity httpEntity = httpResponse.getEntity();
String tokens = EntityUtils.toString(httpEntity, "utf-8");
Gson token_gson = new Gson();
object = token_gson.fromJson(tokens, JsonObject.class);
log.info("request accessToken success. [result={}]", object);
wechatTokenDto.setOpenid(object.get("openid").toString().replaceAll("\"", ""));
wechatTokenDto.setAccess_token(object.get("access_token").toString().replaceAll("\"", ""));
wechatTokenDto.setRefresh_token(object.get("refresh_token").toString().replaceAll("\"", ""));
wechatTokenDto.setScope(object.get("scope").toString().replaceAll("\"", ""));
wechatTokenDto.setExpires_in(Integer.parseInt(object.get("expires_in").toString().replaceAll("\"","")));
// data.put("openid", object.get("openid").toString().replaceAll("\"", ""));
// data.put("access_token", object.get("access_token").toString().replaceAll("\"", ""));
// data.put("expires_in",object.get("expires_in").toString().replaceAll("\"",""));
// data.put("refresh_token",object.get("refresh_token").toString().replaceAll("\"", ""));
// data.put("scope",object.get("scope").toString().replaceAll("\"", ""));
} catch (Exception ex) {
log.error("fail to request wechat access token. [error={}]", ex);
}
return wechatTokenDto;
}
/**
* 通过access_token和openid获取用户基本信息
* @param accessToken
* @param openId
* @return
*/
public static LoginUserDTO getUserInfo(String accessToken, String openId) {
LoginUserDTO loginUserDTO=new LoginUserDTO();
String url = "https://api.weixin.qq.com/sns/userinfo?access_token=" + accessToken + "&openid=" + openId + "&lang=zh_CN";
log.info("request user info from url: {}", url);
JsonObject userInfo = null;
try {
CloseableHttpClient httpClient =HttpClients.createDefault();
HttpGet httpGet = new HttpGet(url);
HttpResponse httpResponse = httpClient.execute(httpGet);
HttpEntity httpEntity = httpResponse.getEntity();
String response = EntityUtils.toString(httpEntity, "utf-8");
Gson token_gson = new Gson();
userInfo = token_gson.fromJson(response, JsonObject.class);
log.info("get userinfo success. [result={}]", userInfo);
loginUserDTO.setOpenId(userInfo.get("openid").toString().replaceAll("\"", ""));
loginUserDTO.setCountry(userInfo.get("country").toString().replaceAll("\"", ""));
loginUserDTO.setProvince(userInfo.get("province").toString().replaceAll("\"", ""));
loginUserDTO.setCity(userInfo.get("city").toString().replaceAll("\"", ""));
loginUserDTO.setHeadImgUrl(userInfo.get("headimgurl").toString().replaceAll("\"", ""));
loginUserDTO.setSex(Integer.valueOf(userInfo.get("sex").toString().replaceAll("\"", "")));
loginUserDTO.setNickname(userInfo.get("nickname").toString().replaceAll("\"", ""));
// data.put("openid", userInfo.get("openid").toString().replaceAll("\"", ""));
// data.put("nickname", userInfo.get("nickname").toString().replaceAll("\"", ""));
// data.put("city", userInfo.get("city").toString().replaceAll("\"", ""));
// data.put("province", userInfo.get("province").toString().replaceAll("\"", ""));
// data.put("country", userInfo.get("country").toString().replaceAll("\"", ""));
// data.put("headimgurl", userInfo.get("headimgurl").toString().replaceAll("\"", ""));
} catch (Exception ex) {
log.error("fail to request wechat user info. [error={}]", ex);
}
return loginUserDTO;
}
}
@Override
public LoginUserDTO wechat(String code)throws ResourceIsNullException{
//调用封装的微信方法,通过code值得到wechatTokenDto
WechatTokenDto wechatTokenDto=WechatUtil.getUserInfoAccessToken(code);
String accessToken = wechatTokenDto.getAccess_token();//得到accessToken
String openId = wechatTokenDto.getOpenid();//得到openId
//user这里根据用户openId查询是否有这个用户。
User user=userMapper.findByOpenId(openId);
LoginUserDTO loginUserDTO=WechatUtil.getUserInfo(accessToken,openId);
log.info("用户信息"+user);
if(user!=null){
//有的话,直接就进入 ,直接将信息返回给前端
// loginUserDTO.setOpenId(openId);
loginUserDTO.setId(user.getId());
loginUserDTO.setBinding(user.getState());
return loginUserDTO;
}else {
//没有的话,创建该学生的信息,然后再传给部分数据给前端
User newUser=new User();
newUser.setOpenId(loginUserDTO.getOpenId());
newUser.setAddress(loginUserDTO.getCity());
newUser.setHeadImgUrl(loginUserDTO.getHeadImgUrl());
newUser.setName(loginUserDTO.getNickname());
log.info("用户昵称"+loginUserDTO.getNickname());
newUser.setState(1);
newUser.setBeans(0);
newUser.setBinding(0);
newUser.setCreate_at(new Date().getTime());
newUser.setCreate_by("系统创建");
userMapper.insert(newUser);
LoginUserDTO loginUserDTO1=new LoginUserDTO();
loginUserDTO1.setId(newUser.getId());
log.info("id的值"+newUser.getId());
loginUserDTO1.setOpenId(newUser.getOpenId());
loginUserDTO1.setBinding(newUser.getState());
return loginUserDTO1;
}
}
基本就是这个样子,后续看下能不能写的更加详细。