温馨提示:配合小程序授权登录食用效果更佳
我这里在完成登录的时候实现解密,同时返回sessionId(后台生成)给小程序端,小程序端将sessionId存在storage缓存中,之后发起业务请求时带上sessionId检验登录状态。
登录流程时序
1、小程序前端调用wx.login(),获取登录凭证code。
2、前端调用wx.getUserInfo(),获取敏感数据(encryptedData)和偏移量(iv)。
3、前端请求开发者服务器,参数为code、encryptedData、iv
4、开发者服务器首先用code,再加上appId、secretkey和grant_type为参数,请求微信服务器得到用户的openid和session_key。
5、开发者服务器拿到session_key后,生成一个sessionId。以sessionId为key,openid + session_key为value存到redis中;(为了数据不被篡改,开发者不应该把 session_key 传到小程序客户端等服务器外的环境)。
sessionId的作用:
1.将sessionId返回给前端,维护小程序登录态。
2.前端进行其它业务请求时,带上sessionId,通过sessionId可以找到用户session_key和openid。
6、对encryptedData进行解密操作
7、返回sessionId给小程序端,存在storage中。
8、后面小程序向服务器进行业务请求时,带上sessionId。
7、8都是后续操作了
1-3、小程序前端
// 登录并获取用户信息
function login(){
//1.登录
wx.login({
success: function (res) {
wx.showLoading({
title: '正在登录...',
mask:true
})
var code = res.code;//登录凭证
if (code) {
//2、调用获取用户信息接口
wx.getUserInfo({
success: function (res) {
console.log({ encryptedData: res.encryptedData, iv: res.iv, code: code })
console.log({ res.userInfo })
App.globalData.userInfo = res.userInfo //获取用户信息。如果用不到unionId等,只需要用户基本信息,那就不需要解密了
//3.请求自己的服务器,解密用户信息 获取unionId等加密信息
const data = {
encryptedData: res.encryptedData,
iv: res.iv,
code: code,
doctor_id: 1, //这个是我项目里用到的
}
api.getTokenAndCode({ //这里是封装过的接口请求方法
data,
success: (data) => {
//4.解密成功后 获取自己服务器返回的结果
if (data.data.status == 1) {
console.log(data.data.userInfo)
console.log(data.data.sessionId)
wx.setStorageSync('sessionId', data.data.sessionId) //将sessionId存储到storage
if (getCurrentPages().length == 1){
//转到首页
wx.redirectTo({
url: '../home/home',
})
}else{
//返回前一页面
wx.navigateBack({
delta: 1
})
}
wx.showToast({
title: '登录成功',
icon: 'success',
duration: 1000,
})
} else {
console.log('解密失败')
}
},
fail: function () {
console.log('登录接口调用失败')
}
})
},
fail: function () {
console.log('获取用户信息失败')
}
})
} else {
console.log('用户登录失败!')
}
},
fail: function () {
console.log('登录失败')
}
})
}
4-6、java后端
pom.xml引入Maven依赖
<dependency>
<groupId>commons-codecgroupId>
<artifactId>commons-codecartifactId>
<version>1.10version>
dependency>
<dependency>
<groupId>org.bouncycastlegroupId>
<artifactId>bcprov-jdk15onartifactId>
<version>1.60version>
dependency>
HttpRequest类
public class HttpRequest {
public static void main(String[] args) {
//发送 GET 请求
String s=HttpRequest.sendGet("http://v.qq.com/x/cover/kvehb7okfxqstmc.html?vid=e01957zem6o", "");
System.out.println(s);
// //发送 POST 请求
// String sr=HttpRequest.sendPost("http://www.toutiao.com/stream/widget/local_weather/data/?city=%E4%B8%8A%E6%B5%B7", "");
// JSONObject json = JSONObject.fromObject(sr);
// System.out.println(json.get("data"));
}
/**
* 向指定URL发送GET方法的请求
*
* @param url
* 发送请求的URL
* @param param
* 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
* @return URL 所代表远程资源的响应结果
*/
public static String sendGet(String url, String param) {
String result = "";
BufferedReader in = null;
try {
String urlNameString = url + "?" + param;
URL realUrl = new URL(urlNameString);
// 打开和URL之间的连接
URLConnection connection = realUrl.openConnection();
// 设置通用的请求属性
connection.setRequestProperty("accept", "*/*");
connection.setRequestProperty("connection", "Keep-Alive");
connection.setRequestProperty("user-agent","Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
// 建立实际的连接
connection.connect();
/* 获取所有响应头字段 */
Map<String, List<String>> map = connection.getHeaderFields();
// 遍历所有的响应头字段
for (String key : map.keySet()) {
System.out.println(key + "--->" + map.get(key));
}
// 定义 BufferedReader输入流来读取URL的响应
in = new BufferedReader(new InputStreamReader( connection.getInputStream()));
String line;
while ((line = in.readLine()) != null) {
result += line;
}
} catch (Exception e) {
System.out.println("发送GET请求出现异常!" + e);
e.printStackTrace();
}
// 使用finally块来关闭输入流
finally {
try {
if (in != null) {
in.close();
}
}catch (Exception e2) {
e2.printStackTrace();
}
} return result;
}
/**
* 向指定 URL 发送POST方法的请求
*
* @param url
* 发送请求的 URL
* @param param
* 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
* @return 所代表远程资源的响应结果
*/
public static String sendPost(String url, String param) {
PrintWriter out = null;
BufferedReader in = null;
String result = "";
try {
URL realUrl = new URL(url);
// 打开和URL之间的连接
URLConnection conn = realUrl.openConnection();
// 设置通用的请求属性
conn.setRequestProperty("accept", "*/*");
conn.setRequestProperty("connection", "Keep-Alive");
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()));
String line;
while ((line = in.readLine()) != null) {
result += line;
}
}catch (Exception e) {
System.out.println("发送 POST 请求出现异常!"+e);
e.printStackTrace();
}
// 使用finally块来关闭输出流、输入流
finally{
try{
if(out!=null){
out.close();
}
if(in!=null){
in.close();
}
}
catch(IOException ex){
ex.printStackTrace();
}
}
return result;
}
}
AES解密类
public class AES {
public static boolean initialized = false;
/**
* AES解密
* @param content 密文
* @return
* @throws InvalidAlgorithmParameterException
* @throws NoSuchProviderException
*/
public byte[] decrypt(byte[] content, byte[] keyByte, byte[] ivByte) throws InvalidAlgorithmParameterException {
initialize();
try {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
Key sKeySpec = new SecretKeySpec(keyByte, "AES");
cipher.init(Cipher.DECRYPT_MODE, sKeySpec, generateIV(ivByte));// 初始化
byte[] result = cipher.doFinal(content);
return result;
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (NoSuchPaddingException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
} catch (BadPaddingException e) {
e.printStackTrace();
} catch (NoSuchProviderException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
public static void initialize(){
if (initialized) return;
Security.addProvider(new BouncyCastleProvider());
initialized = true;
}
//生成iv
public static AlgorithmParameters generateIV(byte[] iv) throws Exception{
AlgorithmParameters params = AlgorithmParameters.getInstance("AES");
params.init(new IvParameterSpec(iv));
return params;
}
}
controller
@Controller
@RequestMapping("/User")
public class UserController {
//注入redis模板
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private UserService userService;
@RequestMapping(value = "/decodeUserInfo", method = RequestMethod.GET)
@ResponseBody
public Map<String, Object> decodeUserInfo(String encryptedData, String iv, String code, Integer doctor_id) {
Map<String, Object> map = new HashMap<String, Object>();
//登录凭证不能为空
if (code == null || code.length() == 0) {
map.put("status", 0);
map.put("msg", "code 不能为空");
return map;
}
//小程序唯一标识 (在微信小程序管理后台获取)
String wxspAppid = "xxxxxxxxxxxxxxxxxx";
//小程序的 app secret (在微信小程序管理后台获取)
String wxspSecret = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
//授权(必填)
String grant_type = "authorization_code";
//////////////// 1、向微信服务器 使用登录凭证 code 获取 session_key 和 openid ////////////////
//请求参数
String params = "appid=" + wxspAppid + "&secret=" + wxspSecret + "&js_code=" + code + "&grant_type=" + grant_type;
//发送请求
String sr = HttpRequest.sendGet("https://api.weixin.qq.com/sns/jscode2session", params);
//解析相应内容(转换成json对象)
JSONObject json = JSONObject.fromObject(sr);
//获取会话密钥(session_key)
String session_key = json.get("session_key").toString();
//用户的唯一标识(openid)
String openid = (String) json.get("openid");
/////////////////1.5、以sessionId为key,openid+session_key为value存入redis中/////////////////////////
//随机生成3rd_session
String sessionId = UUID.randomUUID().toString().replaceAll("-","");
//存入redis缓存中
redisTemplate.opsForValue().set(sessionId, openid + "#" + session_key);
//设置redis过期时间
//redisTemplate.expire(sessionId, 1800, TimeUnit.SECONDS); //设置超时时间1800秒 第三个参数控制时间单位,详情查看TimeUnit
//////////////// 2、对encryptedData加密数据进行AES解密 ////////////////
/**从缓存中获取session_key。这里是模拟重新发请求的情况,session_key完全可以直接用上面的session_key变量***/
Object wxSessionObj = redisTemplate.opsForValue().get(sessionId);
if(null == wxSessionObj){
map.put("status", 0);
map.put("msg", "从Redis获取sessionId失败");
return map;
}
String wxSessionStr = (String)wxSessionObj;
String sessionKey = wxSessionStr.split("#")[1];
/***************************************************************/
try {
AES aes = new AES();
//先进行base64解码,再AES解密
byte[] resultByte = aes.decrypt(Base64.decodeBase64(encryptedData), Base64.decodeBase64(sessionKey), Base64.decodeBase64(iv));
if(null != resultByte && resultByte.length > 0){
map.put("status", 1);
map.put("msg", "解密成功");
/* ******************************* 这里涉及到业务需求,将这些信息存入数据库,不需要可略过
* 解析encrypteData中敏感数据
* */
String result = new String(resultByte, "UTF-8");
JSONObject userInfoJSON = JSONObject.fromObject(result);
String nickName = (userInfoJSON.get("nickName")).toString();
Integer gender = Integer.parseInt((userInfoJSON.get("gender")).toString());
String city = String.valueOf(userInfoJSON.get("city"));
String province = String.valueOf(userInfoJSON.get("province"));
String country = String.valueOf(userInfoJSON.get("country"));
String avatarUrl = (userInfoJSON.get("avatarUrl")).toString();
String unionId = String.valueOf(userInfoJSON.get("unionId"));
/*
* 给userBasic赋值
* */
UserBasic userBasic = new UserBasic();
userBasic.setOpen_id(openid);
userBasic.setNick_name(nickName);
userBasic.setGender(gender);
userBasic.setCity(city);
userBasic.setProvince(province);
userBasic.setCountry(country);
userBasic.setAvatarUrl(avatarUrl);
userBasic.setUnion_id(unionId);
userBasic.setDoctor_id(doctor_id);
//如果数据库有此用户(根据openId判断),判断数据是否有更新并操作;如果没有,向数据库中添加用户信息
userService.addUser(userBasic);
Map<String, Object> userInfo = new HashMap<String, Object>();
userInfo.put("openId", userInfoJSON.get("openId"));
userInfo.put("nickName", nickName);
userInfo.put("gender", gender);
userInfo.put("city", city);
userInfo.put("province", province);
userInfo.put("country", country);
userInfo.put("avatarUrl", avatarUrl);
userInfo.put("unionId", userInfoJSON.get("unionId"));
/*************************************************************************/
map.put("userInfo", userInfo);
map.put("sessionId", sessionId); //返回给前端sessionId
return map;
}
} catch (InvalidAlgorithmParameterException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
map.put("status", 0);
map.put("msg", "解密失败");
return map;
}
}
1、主要参考、数据解密
2、openid与unionid、UnionID 机制说明官方文档
3、浏览器地址测试接口时,报错java.lang.IllegalArgumentException: Illegal base64 character 20
微信小程序用户授权登录button
小程序HTTPS 网络请求wx.request封装