● 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_keyiv 在后端解密
3. 前端无法解密,保证了数据安全
iv (初始化向量) 解密 encryptedData 的「盐」或「偏移量」 微信客户端(与 encryptedData 同时生成) 前端获取 → 传给后端 一次性(解密后即失效) 1. 解密算法的必要参数,确保相同明文每次加密结果不同
2. 前端可安全传输,本身不暴露敏感信息
3. 必须与 encryptedDatasession_key 一起发送给后端才能解密

OAuth2.0的四种模式:

模式名称 核心作用(通俗版) 适用场景 核心流程(极简版) 优点 缺点
1. 授权码模式 (Authorization Code) 第三方应用安全获取用户授权(最常用、最标准) 所有有后端的「用户登录」场景(如微信/QQ/GitHub第三方登录) 1. 前端跳转至授权页
2. 用户同意后,携带 code 返回
3. 后端code + AppId + Secret 换取 token
1. 最安全SecretToken不暴露在前端)
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) 后端直接使用自己的 AppIdSecret 向授权服务器请求一个 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]

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐