基于springboot的微信小程序登录态维持
小程序登录过程
小程序用户登录时,先判断storage中的sessionid是否存在,如果存在,则将sessionid传到后台,判断用户信息是否失效。
-----若失效,前台将sessionid缓存删除,重新调用登录接口。
-----若未失效,返回原页面,后台更新缓存失效时间。
如果不存在,前台调用wx.login获取code,调用getUserInof获取用户信息。
-----如果成功,用户将唯一标识和会话id写入storage缓存。
-----如果失败,用户清除缓存中的唯一标识和会话id
(code用来换取openid和session_key,encryptedDataheiv用来获得并解密用户信息)
如果后台获取用户信息成功,返回sessionid,登录成功。
用户授权登录
bindGetUserInfo: function(e) {
console.log("用户授权"+JSON.stringify(e))
if (!e.detail.userInfo) {
return;
}
if (app.globalData.isConnected) {
wx.setStorageSync('userInfo', e.detail.userInfo)
this.login();
} else {
wx.showToast({
title: '当前无网络',
icon: 'none',
})
}
}
小程序获取code后,向第三方服务器发送请求
微信小程序调用wx.login接口
login: function() {
let that = this;
let sessionid = wx.getStorageSync("sessionid");
console.log("sessionid" + sessionid);
if (sessionid) {
console.log("如果sessionid存在");
wx.request({
url: 'https://127.0.0.1/Api/wx/wxLogin',
header: {
//设置请求头部,sessionid是后台返回的唯一标识,session存放session会话id(例如:JSESSIONID=35EA60A066CE3349A87E524C6B865146; Path=/; Secure; HttpOnly)
"sessionid": wx.getStorageSync("sessionid"),
"Cookie": wx.getStorageSync("session")
},
method: "POST",
success: function(res) {
if (res.data === "登录失败") {
console.log("后台缓存失效,小程序清空sessionid和userInfo");
wx.removeStorageSync("sessionid");
wx.removeStorageSync("session")
//重新登录
that.login();
} else {
console.log("后台缓存中的信息存在,直接返回");
//省略登录,跳转页面
}
},
})
} else {
console.log("如果sessionid不存在");
//调用wx.login接口获得code
wx.login({
success: res => {
if (res.code) {
//获取用户信息
wx.getUserInfo({
success: datas => {
console.log("data", datas);
//向第三方服务器发送code,并获得唯一标识和会话id
wx.request({
url: 'https://127.0.0.1/Api/wx/getOpenId',
data: {
code: res.code,
//encryptedData和iv是用于用户信息解密
encryptedData: datas.encryptedData,
iv: datas.iv
},
header: {
'content-type': 'application/x-www-form-urlencoded'
},
method: 'post',
success: function(res) {
if (!res.data) {
//后台登录失败,删除小程序storage中的唯一标识和会话id
wx.removeStorageSync("sessionid", res.data)
wx.removeStorageSync("session", res.cookies[0])
return;
}
//后台登录成功,将唯一标识和会话id保存在微信小程序中
wx.setStorageSync("sessionid", res.data)
wx.setStorageSync("session", res.cookies[0]
}
})
}
})
}
}
})
}
}
第三方服务器使用code向微信服务器请求,换取session_key和openid
使用到的相关工具类附在最后
@PostMapping("/getOpenId")
public String getSessionByCode(String code,String encryptedData,String iv) {
ServletRequestAttributes requestAttributes =
(ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = requestAttributes.getRequest();
HttpSession session = request.getSession();
//从配置文件中读取相关值
appId = WxUtil.getValue("APP_ID");
appSecret = WxUtil.getValue("APP_SECRET");
grantType = WxUtil.getValue("GRANT_TYPE");
requestUrl = WxUtil.getValue("requestUrl");
String url = requestUrl
+ "?appid=" + appId
+ "&secret=" + appSecret
+ "&js_code=" + code
+ "&grant_type=" + grantType;
// 发送请求
String data = HttpUtil.get(url);
ObjectMapper mapper = new ObjectMapper();
Map<String, String> json = null;
try {
json = mapper.readValue(data, Map.class);
} catch (Exception e) {
e.printStackTrace();
}
//持久化操作(省略)
//从返回结果中取出openid和session_key
String openid = json.get("openid");
String session_key = json.get("session_key");
//对encryptedData解密
JSONObject user = WxSecurityHelper.getUserInfo(encryptedData,session_key,iv);
//对openid加密,并以键值形式存放在第三方服务器的session中
String value = session_key+','+openid;
String key = WxSecurityHelper.authcode(openid,session_key);
session.setAttribute(key,value);
//以秒为单位,即在没有活动30分钟后,session将失效
session.setMaxInactiveInterval(30*60);
String content = (String) session.getAttribute(key);
//将key作为唯一标识,并赋给sessionid
String sessionid = key;
//返回sessionid
return sessionid;
}
第三方服务器登录验证
@PostMapping("/wxLogin")
public String wxLogin() {
//获取request
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = requestAttributes.getRequest();
String sessionid = request.getHeader("sessionid");
//根据request和sessionid校验
if (WxSecurityHelper.verify(request,sessionid)){
return "登录成功";
}
return "登录失败";
}
配置文件
wx.properties
APP_ID = "你的appid"
APP_SECRET = "你的appsecret"
GRANT_TYPE = authorization_code
requestUrl = https://api.weixin.qq.com/sns/jscode2session
工具类
WxUtil.java
读取配置文件中的常量(wx.properties)
package com.cisdi.info.simple.util;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Properties;
public class WxUtil {
private static Properties props = new Properties();
static
{
try
{
props.load(Thread.currentThread().getContextClassLoader().getResourceAsStream("wx.properties"));
}
catch (FileNotFoundException e)
{
e.printStackTrace();
}
catch (IOException e)
{
e.printStackTrace();
}
}
public static String getValue(String key)
{
return props.getProperty(key);
}
public static void updateProperties(String key, String value)
{
props.setProperty(key, value);
}
}
WxSecurityHelper.java
加密解密
package com.cisdi.info.simple.util;
import com.alibaba.fastjson.JSONObject;
import com.sun.org.apache.xerces.internal.impl.dv.util.Base64;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.security.AlgorithmParameters;
import java.security.MessageDigest;
import java.security.Security;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.Objects;
public class WxSecurityHelper {
private Long expire = 1800L; //session过期时间
/**
*
* @return String 加密结果
*/
public static String authcode(String str, String session_key) {
String key = Objects.requireNonNull(md5(session_key)).substring(5, 8);
str = Objects.requireNonNull(md5(str)).substring(8, 10);
return md5(key + str);
}
/**
* 小程序登录验证
* @return
*/
public static boolean verify(HttpServletRequest request, String sessionid) {
if(request.getSession(false)==null){
System.out.println("sessionid已过期");
return false;
}else{
HttpSession session = request.getSession();
// 获取session中所有的键值
Enumeration<?> enumeration = session.getAttributeNames();
String code;
code = (String) session.getAttribute(sessionid);
if (code != null){
Integer splitIndex = code.indexOf(',');
String session_key = code.substring(0,splitIndex);
String openid = code.substring(splitIndex+1);
String secode = authcode(openid,session_key);
System.out.print("secode"+secode);
if (secode.equals(sessionid)) {
//更新有效时间
session.setMaxInactiveInterval(1000*60);
return true;
}
}
// request.removeAttribute(sessionid);
return false;
}
}
/**
* MD5加密算法抽取
*
* @param string 待加密字符串
* @return 加密结果
*/
private static String md5(String string) {
char hexDigits[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'a', 'b', 'c', 'd', 'e', 'f'};
try {
byte[] strTemp = string.getBytes();
MessageDigest mdTemp = MessageDigest.getInstance("MD5");
mdTemp.update(strTemp);
byte[] md = mdTemp.digest();
int j = md.length;
char str[] = new char[j * 2];
int k = 0;
for (byte byte0 : md) {
str[k++] = hexDigits[byte0 >>> 4 & 0xf];
str[k++] = hexDigits[byte0 & 0xf];
}
return new String(str);
} catch (Exception e) {
return null;
}
}
//用户信息解密
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) {
e.printStackTrace();
}
return null;
}
}
HttpUtil.java
package com.cisdi.info.simple.util;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.message.BasicNameValuePair;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class HttpUtil {
private static final String Charset = "utf-8";
/**
* 发送请求,如果失败,会返回null
* @param url
* @param map
* @return
*/
public static String post(String url, Map<String, String> map) {
// 处理请求地址
try {
HttpClient client = HttpClientBuilder.create().build();
URI uri = new URI(url);
HttpPost post = new HttpPost(uri);
// 添加参数
List<NameValuePair> params = new ArrayList<NameValuePair>();
for (String str : map.keySet()) {
params.add(new BasicNameValuePair(str, map.get(str)));
}
post.setEntity(new UrlEncodedFormEntity(params, Charset));
// 执行请求
HttpResponse response = client.execute(post);
if (response.getStatusLine().getStatusCode() == 200) {
// 处理请求结果
StringBuffer buffer = new StringBuffer();
InputStream in = null;
try {
in = response.getEntity().getContent();
BufferedReader reader = new BufferedReader(new InputStreamReader(in,Charset));
String line = null;
while ((line = reader.readLine()) != null) {
buffer.append(line);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 关闭流
if (in != null)
try {
in.close();
} catch (Exception e) {
e.printStackTrace();
}
}
return buffer.toString();
} else {
return null;
}
} catch (Exception e1) {
e1.printStackTrace();
}
return null;
}
/**
* 发送请求,如果失败会返回null
* @param url
* @param str
* @return
*/
public static String post(String url, String str) {
// 处理请求地址
try {
HttpClient client = HttpClientBuilder.create().build();
URI uri = new URI(url);
HttpPost post = new HttpPost(uri);
post.setEntity(new StringEntity(str, Charset));
// 执行请求
HttpResponse response = client.execute(post);
if (response.getStatusLine().getStatusCode() == 200) {
// 处理请求结果
StringBuffer buffer = new StringBuffer();
InputStream in = null;
try {
in = response.getEntity().getContent();
BufferedReader reader = new BufferedReader(new InputStreamReader(in,"utf-8"));
String line = null;
while ((line = reader.readLine()) != null) {
buffer.append(line);
}
} finally {
// 关闭流
if (in != null)
in.close();
}
return buffer.toString();
} else {
return null;
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static String get(String url) {
try {
System.out.println("URL:"+url);
HttpClient client = HttpClientBuilder.create().build();
URI uri = new URI(url);
HttpGet get = new HttpGet(uri);
HttpResponse response = client.execute(get);
if (response.getStatusLine().getStatusCode() == 200) {
StringBuffer buffer = new StringBuffer();
InputStream in = null;
try {
in = response.getEntity().getContent();
BufferedReader reader = new BufferedReader(new InputStreamReader(in,Charset));
String line = null;
while ((line = reader.readLine()) != null) {
buffer.append(line);
}
} finally {
if (in != null)
in.close();
}
return buffer.toString();
} else {
return null;
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}