编辑 | blame | 历史 | 原始文档

企业微信登录与通知 - 前端联调方案

涉及页面

  • 登录页(src/views/_core/authentication/login.vue)
  • 第三方授权回调中转页(src/views/_core/authentication/social-login.vue)
  • 二维码登录页(src/views/_core/authentication/qrcode-login.vue)

本次变更概述

变更 影响前端
企业微信首次扫码**自动注册**账号,不再报"未绑定账号" 是 —— 登录页需新增企业微信登录入口,并去掉首次登录的绑定弹窗分支
登录成功后向用户企业微信推送**登录通知** 否 —— 后端异步发送,前端无感知
新增企业微信**回调接口**(URL 验证 + 消息接收) 否 —— 企业微信服务器调用,与前端无关

API

方法 路径 说明 前端是否调用
GET /admin-api/system/auth/social-auth-redirect 获取企业微信授权跳转地址 是
POST /admin-api/system/auth/social-login 使用授权 code 换取登录令牌 是
POST /admin-api/system/auth/login 账号密码登录(可附带社交绑定参数) 是(保留)
GET /admin-api/wxcp/callback 企业微信回调 URL 验证 否
POST /admin-api/wxcp/callback 企业微信消息/事件接收 否

1. 获取企业微信授权地址

请求参数:

参数 类型 必填 说明
type Integer 是 社交类型,企业微信固定传 30
redirectUri String 是 授权回调地址,即前端 social-login 页面完整地址

响应:

{
  "code": 0,
  "data": "https://open.work.weixin.qq.com/wwopen/sso/qrConnect?appid=xxx&agentid=xxx&redirect_uri=xxx&state=xxx"
}

前端拿到 data 后直接跳转该地址,用户扫码确认后企业微信会带 code 和 state 回跳到 redirectUri。

2. 使用 code 换取登录令牌

请求参数:

参数 类型 必填 说明
type Integer 是 社交类型,企业微信固定传 30
code String 是 企业微信回调带回的授权码
state String 是 企业微信回调带回的 state

响应(首次登录与再次登录结构一致):

{
  "code": 0,
  "data": {
    "userId": 123,
    "accessToken": "xxx",
    "refreshToken": "xxx",
    "expiresTime": "2026-09-29T12:00:00"
  }
}

业务流程与数据带入

  1. 用户点击「企业微信登录」→ 调用 social-auth-redirect(type=30)→ 拿到跳转地址 → 浏览器跳转到企业微信扫码页
  2. 用户扫码确认 → 企业微信回跳到 redirectUri,URL 携带 code 和 state → 由 social-login 页面承接
  3. social-login 页面读取 URL 上的 type、code、state → 调用 social-login
  4. 后端判断该企业微信账号是否已绑定系统用户:
  • 已绑定 → 直接签发令牌返回
  • 未绑定(首次) → 自动创建系统用户并绑定 → 签发令牌返回
  1. 前端拿到 accessToken → 写入访问令牌存储 → 拉取权限信息 → 跳转首页

关键点:第 4 步无论首次还是再次登录,返回结构完全一致,前端不需要区分两条分支。

字段展示规则

字段 展示位置 说明
accessToken 登录成功后写入访问令牌存储 后续请求头携带
refreshToken 登录成功后写入访问令牌存储 令牌过期时刷新
userId 登录成功后写入用户信息存储 用于个人中心等场景

业务规则说明

场景 规则
企业微信首次扫码(未绑定) 自动创建系统用户,用户名为 wx_ + 8 位随机十六进制,昵称取企业微信昵称,账号状态为启用,随机生成密码(用户无需知晓,仅用于占位)
首次注册后的默认权限 绑定角色 wxcp_user,**仅授予「会员管理」整棵子树**(根菜单 1075490 + 子菜单 + 孙菜单,共 15 条)。首页是前端静态路由、不依赖菜单表,任何账号都可见,无需授权。角色缺失时代码只记 warn 日志、不阻断登录,表现为「能进系统但菜单全空」
企业微信再次扫码(已绑定) 直接登录,不创建新用户
非企业微信的社交平台首次登录 仍返回 1_002_000_005「未绑定账号,需要进行绑定」,需引导用户走账号密码登录并绑定
账号密码登录时携带社交参数 走原有绑定逻辑,登录成功后该社交账号与账号绑定
登录成功通知 通过企业微信发送一条文本卡片消息给该用户,内容含登录时间与登录方式;用户未绑定企业微信时跳过发送,不影响登录
通知发送失败 仅记录日志,**不阻塞、不回滚登录**,用户仍可正常进入系统

注意事项

  • 企业微信授权登录要求**已备案域名**且备案主体与企业微信认证主体一致,本地 localhost 调试需用内网穿透并保证域名备案。
  • 首次自动注册会跳过"注册功能已关闭"配置项(system.user.register-enabled),但**仍受租户账号配额限制**,配额用尽时返回 USER_COUNT_MAX。
  • 自动注册生成的用户名以 wx_ 开头,便于与人工创建的账号区分;如需限制企业微信登录的用户范围,需在业务侧按用户名前缀或角色做二次管控。
  • 默认角色 wxcp_user 必须先在库里存在。该角色及其「会员管理」授权已写进 docs/sql/wxcp_menu.sql 末尾(合并脚本 config_export_all_<日期>.sql 阶段三末尾同步了一份),新库初始化会自动建出来;**已建过的老库要补执行这一段**,否则扫码新账号能登录但菜单空白。代码按**角色编码**查找而非角色编号,换库不会失效。
  • 回调接口 /admin-api/wxcp/callback 已加入免登录白名单,**不需要也不应该带前端令牌**。
  • 前端原有的「未绑定 → 弹出绑定账号密码弹窗」逻辑,对企业微信场景不再触发;若产品上仍希望首次登录先绑定既有账号,需要另行明确,当前实现为直接自动注册。

部署配置清单(缺一不可)

企业微信登录要跑通,以下三处配置**必须同时存在**:

# 配置位置 内容 缺失后果
1 application-<profile>.yaml → wx.cp.* corp-id、agent-id、secret、token、aes-key 回调接口无法解密,URL 验证失败
2 application.yaml → justauth.type.WECHAT_ENTERPRISE(**全局,不随 profile 变**) client-id、client-secret、agent-id、**ignore-check-redirect-uri: true** 调用 social-auth-redirect 抛 AuthException: Illegal redirect uri [WECHAT_ENTERPRISE]
3 数据库 system_social_client social_type=30、**user_type=2**、client_id=CorpId、client_secret=Secret、agent_id、status=0、deleted=0 只用 yaml 里的密钥,无法在后台改;填错 user_type 则该行查不到

第 2 项容易漏掉:AuthRequestFactory 需要先从 yaml 拿到"社交平台基类配置"才能构造出对应的 AuthRequest 实例,之后才会用数据库里的密钥覆盖它。所以即使密钥全都配在数据库,yaml 里也必须保留一份占位配置。

第 2 项里 ignore-check-redirect-uri: true 最容易漏。yudao 的设计是 redirect-uri 不写死——SocialClientServiceImpl.getAuthorizeUrl 最后会用 HttpUtils.replaceUrlQuery 把 redirect_uri 换成前端按当前浏览器地址传进来的值。但 JustAuth 的 AuthDefaultRequest 构造时会调 AuthChecker.checkConfig(AuthConfig, AuthSource):

if (config.isIgnoreCheckRedirectUri()) {
    return;                                    // 有这个开关才放行
}
if (StringUtils.isEmpty(redirectUri)) {
    throw new AuthException(AuthResponseStatus.ILLEGAL_REDIRECT_URI, source);
}

也就是说**不写 redirect-uri 的前提就是打开这个开关**,两者缺一不可。只删 redirect-uri 而漏了开关,扫码时会在 AuthRequestFactory.getDefaultRequest 构造 AuthWeChatEnterpriseQrcodeRequest 的那一刻抛 AuthException: Illegal redirect uri [WECHAT_ENTERPRISE]——异常发生在构造期,早于用数据库密钥覆盖的那一步,所以配置了 system_social_client 也救不了。

第 2 项为什么放 application.yaml 而不是某个 profile:YudaoJustAuthConfiguration 上的 @ConditionalOnProperty(prefix = "justauth", value = {"enabled"}, havingValue = "true", matchIfMissing = true) 让 AuthRequestFactory 无论配没配都会创建。缺配置不等于"关闭社交登录",而是一个运行期才暴露的坑:请求走到 AuthRequestFactory.get() 时先按 justauth.type 查不到,再回落到 justauth.extend 分支,而 JustAuthProperties.extend 默认就是 null,直接 NullPointerException。

这段配置历史上只存在于 application-local.yaml,导致用 dev / test / chdl / kyzn / wtxc / yzfx / zsjc 任一 profile 启动时,企业微信扫码登录必炸,并且报的是 AuthRequestFactory.get 第 104 行的 NPE,很难一眼看出是配置缺失。上移到全局配置文件后,这一类问题从结构上被消除。

代码层兜底:AuthRequestFactory.get() 已加判空守卫——properties.getExtend() 为 null 时跳过自定义分支,由既有逻辑抛出可读的 AuthException,不再抛 NullPointerException。

回调 URL 配置

在企业微信管理后台 → 应用管理 → 自建应用 → 「接收消息」→「设置API接收」:

  • URL:https://<已备案域名>/admin-api/wxcp/callback
  • Token:与 wx.cp.token 完全一致
  • EncodingAESKey:与 wx.cp.aes-key 完全一致

点「保存」时企业微信会立即发起一次 GET 验证,返回 echostr 明文即为通过。