一、SpringBoot整合微信登录
小程序的使用已经是一个普遍的现象,对于刚学习SpringBoot的我们来说完全的写好一套微信登录的案例有一定的难度,这里结合自己的一些学习经验,把微信登录这一功能模块附上:
1、准备工作
申请注册一个属于自己的小程序,或者是能够使用的(白嫖别人的…)
2、创建SpringBootg工程并编写配置
server:
port: 8087
spring:
application:
name: applet
profiles:
active: dev
jackson:
date-format: yyyy-MM-dd HH:mm:ss
locale: zh_CN
time-zone: GMT+8
servlet:
multipart:
max-file-size: 200MB
max-request-size: 200MB
weixin:
login:
info:
# appid
appid: wx*************
# 秘钥
appsecret: e**************************
后面会用到的一些依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.76</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
</dependency>
3、微信登录业务处理
这里我们就由外到内的进行,基于业务需求出发,先编写Controller层
@RestController
@RequestMapping(value = "/wx")
public class WeChatLoginController {
@Resource
private LoginService loginService;
@ApiOperation("授权登录,获取电话号码")
@PostMapping(value = "/Authorization")
public R<Object> authorization(@RequestBody Login login) {
return loginService.AuthorizationLogin(login);
}
}
具体实现
/**
* @description: 微信登录
* @author: XXXLOMG
* @create: 2021-07-05 22:06
*/
@Service
public class LoginService {
@Value("${weixin.login.info.appid}")
private String appid;
@Value("${weixin.login.info.appsecret}")
private String appsecret;
@Resource
private UserMapper userMapper;
//微信授权登录获取电话号码
public R<Object> AuthorizationLogin(Login login) {
JSONObject jsonObject = getSessionKey(login.getCode());
String json = wxDecrypt(login.getEncryptedData(), jsonObject.getString("session_key"), login.getIv());
Phone phoneDTO = JSON.parseObject(json, Phone.class);
String openid = jsonObject.getString("openid");//openId
String phone = phoneDTO.getPhoneNumber();//电话号码
//TODO 自行实现自己的用户逻辑
......
JSONObject result = new JSONObject()
result.put("openid",openid);
result.put("phone",phone);
return RUtils.success(result);
}
public JSONObject getSessionKey(String code) {
String params = "appid=" + appid + "&secret=" + appsecret + "&js_code=" + code + "&grant_type=authorization_code";
String sr = HttpRequestUtils.sendGet(loginUrl, params);
return JSON.parseObject(sr);
}
}
其中的DTO
@Data
public class Phone {
/**
* 用户绑定的手机号(国外手机号会有区号)
*/
private String phoneNumber;
/**
* 没有区号的手机号
*/
private String purePhoneNumber;
/**
* 区号
*/
private String countryCode;
}
HttpRequest的请求工具类
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.util.List;
import java.util.Map;
/**
* @author XXXLOMG
*/
public class HttpRequestUtils {
public static String sendGet(String url, String param) {
StringBuilder result = new StringBuilder();
BufferedReader in = null;
try {
String urlNameString = url + "?" + param;
URL realUrl = new URL(urlNameString);
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));
}
in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
String line;
while ((line = in.readLine()) != null) {
result.append(line);
}
} catch (Exception e) {
System.out.println("发出GET请求异常" + e);
e.printStackTrace();
} finally {
try {
if (in != null) {
in.close();
}
} catch (Exception e2) {
e2.printStackTrace();
}
}
return result.toString();
}
public static String sendPost(String url, String param) {
PrintWriter out = null;
BufferedReader in = null;
StringBuilder result = new StringBuilder();
try {
URL realUrl = new 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)");
conn.setDoOutput(true);
conn.setDoInput(true);
out = new PrintWriter(conn.getOutputStream());
out.print(param);
out.flush();
in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
String line;
while ((line = in.readLine()) != null) {
result.append(line);
}
} catch (Exception e) {
System.out.println("发出POST请求异常" + e);
e.printStackTrace();
} finally {
try {
if (out != null) {
out.close();
}
if (in != null) {
in.close();
}
} catch (IOException ex) {
ex.printStackTrace();
}
}
return result.toString();
}
}
二、关于微信小程序前端的数据处理
首先先了解springmvc的机制和spring中的注解含义
大多情况下的数据处理都是在controller中用@RequestBody的注解来解析json字符串,用JSONObject加以处理,这里的工具类是阿里的,当然你可以用hutool
JSONObject jsonObject = JSON.parseObject(JsonString);
或是直接用实体类接受 (@RequestBody User user)
1、@RequestBody
注解@RequestBody接收的参数是来自requestBody中,即请求体。一般用于处理非 Content-Type: application/x-www-form-urlencoded
编码格式的数据,比如:application/json
、application/xml
等类型的数据。
就application/json
类型的数据而言,使用注解@RequestBody可以将body里面所有的json数据传到后端,后端再进行解析。
GET请求中,因为没有HttpEntity,所以@RequestBody并不适用。
POST请求中,通过HttpEntity传递的参数,必须要在请求头中声明数据的类型Content-Type,SpringMVC通过使用
HandlerAdapter 配置的HttpMessageConverters来解析HttpEntity中的数据,然后绑定到相应的bean上。
2、@RequestParam
注解@RequestParam接收的参数是来自HTTP请求体或请求url的QueryString中。
RequestParam可以接受简单类型的属性,也可以接受对象类型。
@RequestParam有三个配置参数:
-
required
表示是否必须,默认为true
,必须。 -
defaultValue
可设置请求参数的默认值。 -
value
为接收url的参数名(相当于key值)。
@RequestParam用来处理 Content-Type
为 application/x-www-form-urlencoded
编码的内容,Content-Type
默认为该属性**。
@RequestParam也可用于其它类型的请求,例如:POST、DELETE等请求**。
所以在postman中,要选择body的类型为 x-www-form-urlencoded
,这样在headers中就自动变为了 Content-Type
: application/x-www-form-urlencoded
编码格式。
小程序在传递表单数据时,通常会创建一个对象,然后把表单数据绑定赋给这个对象上
小程序的表单传递数据是以x-www-form-urlencoded
的格式传递的
var form = JSON.stringify(e.detail.value)
在springboot的框架下,获取的x-www-form-urlencoded
的数据是需要通过@RequestParam的注解来获取,以获取微信小程序发送的form表单数据为例:
@RequestMapping(value = "/weChatUserInsert")
public String weChatUserInsert(@RequestParam("form") String form)
{
return form;
}
在微信中的常用的解码工具类,这里针对授权登陆
import org.apache.commons.codec.binary.Base64;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.AlgorithmParameters;
import java.security.Key;
import java.security.Security;
/**
* @author XXXLOMG
*/
public class AES {
// 算法名
public static final String KEY_NAME = "AES";
// 加解密算法/模式/填充方式
// ECB模式只用密钥即可对数据进行加密解密,CBC模式需要添加一个iv
public static final String CIPHER_ALGORITHM = "AES/CBC/PKCS7Padding";
/**
* 微信 数据解密<br/>
* 对称解密使用的算法为 AES-128-CBC,数据采用PKCS#7填充<br/>
* 对称解密的目标密文:encrypted=Base64_Decode(encryptData)<br/>
* 对称解密秘钥:key = Base64_Decode(session_key),aeskey是16字节<br/>
* 对称解密算法初始向量:iv = Base64_Decode(iv),同样是16字节<br/>
*
* @param encrypted 目标密文
* @param session_key 会话ID
* @param iv 加密算法的初始向量
*/
public static String wxDecrypt(String encrypted, String session_key, String iv) {
String json = null;
byte[] encrypted64 = Base64.decodeBase64(encrypted);
byte[] key64 = Base64.decodeBase64(session_key);
byte[] iv64 = Base64.decodeBase64(iv);
byte[] data;
try {
init();
json = new String(decrypt(encrypted64, key64, generateIV(iv64)));
} catch (Exception e) {
e.printStackTrace();
}
return json;
}
/**
* 初始化密钥
*/
public static void init() throws Exception {
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
KeyGenerator.getInstance(KEY_NAME).init(128);
}
/**
* 生成iv
*/
public static AlgorithmParameters generateIV(byte[] iv) throws Exception {
// iv 为一个 16 字节的数组,这里采用和 iOS 端一样的构造方法,数据全为0
// Arrays.fill(iv, (byte) 0x00);
AlgorithmParameters params = AlgorithmParameters.getInstance(KEY_NAME);
params.init(new IvParameterSpec(iv));
return params;
}
/**
* 生成解密
*/
public static byte[] decrypt(byte[] encryptedData, byte[] keyBytes, AlgorithmParameters iv)
throws Exception {
Key key = new SecretKeySpec(keyBytes, KEY_NAME);
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
// 设置为解密模式
cipher.init(Cipher.DECRYPT_MODE, key, iv);
return cipher.doFinal(encryptedData);
}
}
结语:
基于SpringBoot微信登录到这里就结束啦!若有不对的地方,欢迎指正。