● 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. 流程最简单,一次请求即可获得Token1. 极度不安全,第三方应用会接触到用户的明文密码
2. 违背OAuth“不暴露密码”的设计初衷
3. 仅适用于自家应用,无法用于真正的第三方
4. 客户端模式 (Client Credentials)服务器对服务器间的授权(无用户参与)后端服务间接口调用(如微服务A调用微服务B,或调用阿里云API)后端直接使用自己的 AppIdSecret 向授权服务器请求一个 Token1. 无用户参与,流程简单高效
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编程工具,助力开发者即刻编程。

更多推荐