Java第三方登录API[微信小程序登录][OAuth2][HttpClient]
·
● OAuth2.0
OAuth2.0其实就是第三方服务器(后端服务器)和微信服务器在和用户之间进行登录、鉴权、安全的一种调用协议和使用规范,就是如果用户想通过小程序进行微信登录,后端就必须按照OAuth2.0的协议规则进行接口调用与使用微信登录的API
OAuth2.0的流程(基于Authorization-Code模式):
- 用户打开登录页面
- 点击登录按钮此时前端执行wx.login()获得一个临时凭证js_code(code是与用户信息和设备绑定的,换别的设备就无法使用而且他是一次性的)
- 在执行授权按钮或者自动提交授权请求,wx.getUserInfo()
- 此时根据用户确认授权或者直接提交code获取到encryptedData(用户信息密文)和iv(偏移向量)
- 紧接着将这三个数据(code、encryptedData、iv)交给后端服务器接口
- 后端根据code与appid、appsecret(后端服务器唯一标识)去微信服务器获取session_key和openid
- 再拿着session_key和iv去解密encryptedData
- 拿到用户解密数据
其实不如直接看微信官方文档[微信开发平台]
为什么不直接拿着用户的Session和appid、AppSecret拿用户信息,中间为什么还要走这么多复杂流程
- 如果前端直接在授权页面拿到微信用户信息,那黑客也可以拿到,如果前端在授权页面拿到用户信息的加密encryptedData和vi偏移量,而后端通过,那他可以发给原本的接口伪造登录
- 在早些版本的微信登录中,前端使用wx.logon()获取临时凭证code,然后wx.getUserInfo根据当前用户信息获取encryptedData和vi,这时候黑客就可以在前端截取你的encryptedData和vi然后配合code一起发给后端(重放攻击),后端这时候用appsecret+appid+code换取sessionKey然后解密,这样就可以伪造登录,接管你的账号
核心组件:
| 字段名称 | 核心作用(通俗版) | 生成方 | 存储位置 | 有效期 | 核心特点 / 注意事项 |
|---|---|---|---|---|---|
| AppId(应用 ID) | 第三方应用在微信的「身份证号」 | 微信开放平台 / 公众平台 | 前端 + 后端(可公开) | 永久(应用不注销则不变) | 1. 所有用户登录该应用共用同一个 AppId 2. 公开安全,可写在前端代码里 |
| AppSecret(应用秘钥) | 第三方应用的「登录密码」 | 微信开放平台 / 公众平台(和 AppId 成对生成) | 仅后端(绝对保密) | 永久(可手动重置) | 1. 用于验证「应用是 AppId 的合法拥有者」 2. 绝不能出现在前端,泄露会被冒充,造成数据泄露和经济损失 |
| 微信用户 Session(微信登录态) | 微信识别「你是哪个用户」的凭证 | 微信服务器(用户登录微信后生成) | 微信客户端 + 微信服务器 | 用户退出微信前有效 | 1. 仅微信能读取,第三方应用无法获取 2. 是微信识别用户的核心依据,存在于微信生态内部 |
| Code(授权码) | 「用户授权 + 应用身份」的临时凭证 | 微信授权服务器(用户同意授权后生成) | 先返回 前端 → 再传给 后端(前端仅中转) | 极短(约 5 分钟)、一次性 | 1. 仅绑定「单个用户 + 单个 AppId」 2. 用后即废,即使泄露,有效期极短,风险极低 |
| AccessToken(访问令牌) | 访问「该用户微信信息」的临时通行证 | 微信授权服务器(后端用 Code+AppId+AppSecret 换取) | 仅后端(可缓存) | 短期(微信默认 2 小时) | 1. 绑定「用户 + AppId + 权限范围」 2. 仅能查该用户的授权信息,过期失效,需通过 RefreshToken 刷新 |
| OpenId | 用户在「该 AppId 下」的唯一标识 | 微信服务器(换 AccessToken 时同步返回) | 仅后端(可存入数据库) | 永久(用户 + AppId 不变则不变) | 1. 同一用户在不同 AppId 下 OpenId 不同 2. 仅标识用户,无任何权限,是后续业务关联的关键 ID |
| UnionId | 用户在「同一开放平台账号下」所有应用的唯一标识 | 微信开放平台(用户关注或登录后生成) | 仅后端(建议作为核心用户ID存入数据库) | 永久(用户绑定开放平台后不变) | 1. 打通关键:同一用户在不同公众号、小程序、移动应用中的 UnionId 相同 2. 获取前提:多个应用必须在同一微信开放平台账号下绑定,且用户已授权 3. 业务核心:是实现跨应用用户识别、数据互通和统一运营的基石 |
| session_key (会话密钥) | 用于解密用户敏感数据的「密钥」 | 微信服务器(小程序登录时生成) | 仅后端(需安全存储) | 短期(微信可能定期刷新) | 1. 用于解密 encryptedData 获取用户手机号等敏感信息2. 绝不能传给前端,需在后端妥善保管 3. 可能会过期,需要机制重新获取 |
| encryptedData (加密数据) | 加密后的用户敏感信息(如手机号) | 微信客户端(用户授权后加密生成) | 前端获取 → 传给后端 | 一次性(解密后即失效) | 1. 加密内容包括用户信息、时间戳等 2. 必须配合 session_key 和 iv 在后端解密3. 前端无法解密,保证了数据安全 |
| iv (初始化向量) | 解密 encryptedData 的「盐」或「偏移量」 |
微信客户端(与 encryptedData 同时生成) |
前端获取 → 传给后端 | 一次性(解密后即失效) | 1. 解密算法的必要参数,确保相同明文每次加密结果不同 2. 前端可安全传输,本身不暴露敏感信息 3. 必须与 encryptedData、session_key 一起发送给后端才能解密 |
OAuth2.0的四种模式:
| 模式名称 | 核心作用(通俗版) | 适用场景 | 核心流程(极简版) | 优点 | 缺点 |
|---|---|---|---|---|---|
| 1. 授权码模式 (Authorization Code) | 第三方应用安全获取用户授权(最常用、最标准) | 所有有后端的「用户登录」场景(如微信/QQ/GitHub第三方登录) | 1. 前端跳转至授权页 2. 用户同意后,携带 code 返回3. 后端用 code + AppId + Secret 换取 token |
1. 最安全(Secret和Token不暴露在前端)2. 权限可控,支持细粒度授权 3. 支持刷新令牌 ( RefreshToken) |
1. 流程稍长,需前后端两次交互 2. 必须要有后端服务器支持 |
| 2. 简化模式 (Implicit) | 纯前端应用获取权限(无后端参与) | 纯前端单页应用 (SPA),且后端仅为静态资源服务 | 1. 前端直接跳转至授权页 2. 授权后,授权服务器直接将 Token 以 URL 片段 (#) 形式返回给前端 |
1. 流程短,无需后端参与换码 2. 适合纯粹的无后端应用 |
1. 极不安全,Token直接暴露在浏览器地址栏和历史记录中2. 不支持刷新令牌,过期需重新授权 3. 令牌易被中间人攻击窃取 |
| 3. 密码模式 (Password) | 用户直接将账号密码交给受信任的第三方 | 自家开发的、绝对信任的官方客户端(如自家移动App) | 1. 用户在自家客户端输入资源所有者(如微信)的账号密码 2. 客户端直接将账号密码发给授权服务器换取 Token |
1. 流程最简单,一次请求即可获得Token |
1. 极度不安全,第三方应用会接触到用户的明文密码 2. 违背OAuth“不暴露密码”的设计初衷 3. 仅适用于自家应用,无法用于真正的第三方 |
| 4. 客户端模式 (Client Credentials) | 服务器对服务器间的授权(无用户参与) | 后端服务间接口调用(如微服务A调用微服务B,或调用阿里云API) | 后端直接使用自己的 AppId 和 Secret 向授权服务器请求一个 Token |
1. 无用户参与,流程简单高效 2. 适合机器对机器的通信 |
1. 完全无用户上下文,获取的Token仅代表应用自身,与任何具体用户无关2. 不能用于访问任何用户受保护的资源 |
Implicit模式流程(简化模式):
- 用户点击登录,前端跳转到授权页(带 AppId、response_type=token);
- 用户授权后,微信直接把 AccessToken 拼在前端跳转地址的哈希值里(如 #access_token=xxx);
- 前端解析哈希值拿到 token,直接调用资源接口。
- 关键风险:token 暴露在前端,抓包 / 调试就能拿到,完全不安全。
Password(密码模式):
- 用户在「微信自家 APP」里输入微信账号密码;
- APP 后端拿着「用户账号 + 密码 + AppId」调用微信接口;
- 微信验证密码正确,直接返回 AccessToken;
- APP 用 token 访问用户信息。
- 关键风险:如果是第三方应用,用户把密码给它,相当于把微信账号交给对方,极端危险。
Client-Credentials模式(客户端模式):
- 你的后端需要调用「微信开放平台的统计接口」(和具体用户无关);
- 后端直接拿着 AppId + AppSecret 调用微信接口;
- 微信验证通过,返回 AccessToken(绑定应用,不绑定用户);
- 后端用这个 token 调用统计接口。
- 特点:没有任何用户参与,token 仅代表「你的应用有调用该接口的权限」。
注:京东、淘宝、QQ、微信、支付宝、Github、微博这些的登录API都遵守OAuth2.0协议,也就是说调用他们的登录API和微信的一模一样、举一反N
● OKHttp
他是一个发送请求然后接收响应的微客户端,他可以发get\post等请求到指定服务器端口然后可以接收响应,他的底层就是用的NIO主从reactor只不过是客户端版本的,他是connect事件+read事件+writer事件而不是accpet
他的核心作用就是带着东西发请求然后接收响应,在这里的工作就是带着code和appid、appsecret发给微信服务器然后拿到session_key和openid就相当于一个跑腿员
在流程中的使用:
声明Http连接模板:
做全局唯一单例类,因为他和servlet.service()一样只提供一个模板用,而没有线程安全问题
package com.note.utils;
import okhttp3.OkHttpClient;
import java.util.concurrent.TimeUnit;
public class HttpClientUtil {
public static final String HOST = "api.weixin.qq.com"; //请求的域名
public static final String SNS = "sns"; //第一段路径
public static final String JS_CODE2_SESSION = "jscode2session"; //第二段路径
// 全局单例的 OkHttpClient
private static final OkHttpClient OK_HTTP_CLIENT = new OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS) // 连接超时
.readTimeout(10, TimeUnit.SECONDS) // 读取超时
.writeTimeout(10, TimeUnit.SECONDS) // 写入超时
.build();
public static OkHttpClient getInstance() {
return OK_HTTP_CLIENT;
}
}
具体使用:
//构建请求url+参数
HttpUrl url = new HttpUrl.Builder()
.scheme("https") //必须是s加密 微信不支持http
.host(HttpClientUtil.HOST)
.addPathSegment(HttpClientUtil.SNS) //第一段路径
.addPathSegment(HttpClientUtil.JS_CODE2_SESSION) //第二段路径
//添加get参数 k v 按照官方文档的接口参数来
.addQueryParameter("js_code", jsCode)
.addQueryParameter("grant_type", "authorization_code")
.addQueryParameter("appid", wechatProperties.getAppId())
.addQueryParameter("secret", wechatProperties.getAppSecret())
.build();
//构建发送微信服务器的请求体
Request request = new Request.Builder()
.url(url) //url 地址
.get() //请求类型
.build();
//发送请求获取响应体
try (Response response = HttpClientUtil.getInstance().newCall(request).execute()) {
if (response.isSuccessful() && response.body() != null) {
String body = response.body().string();
//响应体转为json 拿取数据
return JSONObject.parseObject(body, WxUserSession.class);
}
} catch (IOException e) {
throw new RuntimeException("获取微信秘钥失败");
}
● EncryptedData解密
// 静态代码块注册(推荐,项目启动时执行一次)
static {
// 注册 BouncyCastle 加密提供者
Security.addProvider(new BouncyCastleProvider());
}
/**
* 对encryptedData进行解密
*
* @param encryptedData 加密用户信息
* @param iv 偏移向量
* @param sessionKey 微信服务器sessionKey
* @return 解密结果字符串 json格式
*/
public static String encrypt(String encryptedData, String iv, String sessionKey) throws IllegalBlockSizeException, BadPaddingException, NoSuchAlgorithmException, NoSuchPaddingException, NoSuchProviderException, InvalidParameterSpecException, InvalidAlgorithmParameterException, InvalidKeyException {
// 1. Base64解码:sessionKey、encryptedData、iv
byte[] sessionKeyBytes = Base64.getDecoder().decode(sessionKey);
byte[] encryptedDataBytes = Base64.getDecoder().decode(encryptedData);
byte[] ivBytes = Base64.getDecoder().decode(iv);
// 2. 初始化 AES 解密器(AES-128-CBC + PKCS7Padding)
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding", "BC");
SecretKeySpec secretKeySpec = new SecretKeySpec(sessionKeyBytes, "AES");
AlgorithmParameters params = AlgorithmParameters.getInstance("AES");
params.init(new IvParameterSpec(ivBytes));
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, params);
// 3. 执行解密并转成字符串
byte[] decryptBytes = cipher.doFinal(encryptedDataBytes);
return new String(decryptBytes, StandardCharsets.UTF_8);
}
● 笔记项目[OAuth2.0、OkHttp]
更多推荐




所有评论(0)