1. 为什么选择企业微信授权登录?
在开发后台管理系统时,登录认证是基础但至关重要的环节。传统账号密码方式存在诸多痛点:员工需要记忆额外密码、密码强度不足导致安全隐患、离职员工账号回收不及时等。而企业微信授权登录完美解决了这些问题:
- 安全可靠:基于OAuth2.0协议,避免密码泄露风险
- 员工零学习成本:直接使用日常办公的企业微信扫码登录
- 管理便捷:与企业组织架构实时同步,离职自动回收权限
- 审计完整:每次登录可关联具体员工,满足合规要求
以我们团队的实际案例来说,上线企业微信登录后:
- IT部门密码重置工单减少83%
- 新员工系统培训时间缩短50%
- 安全事件归零
重要提示:企业微信授权需要先完成开发者资质认证,建议提前准备营业执照、法人身份证等材料,整个审核流程通常需要3-5个工作日。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 前期准备工作
2.1 企业微信后台配置
-
创建自建应用:
-
配置可信域名:
nginx复制# 示例Nginx配置 server { listen 443 ssl; server_name oa.yourcompany.com; ssl_certificate /path/to/cert.pem; ssl_certificate_key /path/to/key.pem; location / { root /var/www/oa; index index.html; } }需确保:
- 使用HTTPS协议
- 域名备案完成
- 在企业微信"我的企业" → "企业信息"中配置
-
设置授权回调域:
- 格式:
oa.yourcompany.com(不带http/https) - 支持配置多个域名,用分号隔开
- 格式:
2.2 FastAdmin环境准备
-
安装Composer依赖:
bash复制
composer require easyswoole/wechat -
数据库新增字段:
sql复制ALTER TABLE fa_admin ADD COLUMN wx_userid VARCHAR(64) COMMENT '企业微信UserID'; ALTER TABLE fa_admin ADD COLUMN wx_avatar VARCHAR(255) COMMENT '企业微信头像'; -
修改配置文件
application/extra/site.php:php复制'wxwork' => [ 'corp_id' => '企业ID', 'agent_id' => '应用AgentId', 'secret' => '应用Secret', 'redirect_uri' => 'https://oa.yourcompany.com/auth/callback' ]
3. 核心代码实现
3.1 扫码登录页面集成
在FastAdmin的登录模板(application/admin/view/login/index.html)中添加:
html复制<div class="wx-login">
<div id="wx_qrcode"></div>
<script src="https://res.wx.qq.com/wwopen/js/jsapi/jweixin-1.2.0.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
wx.agentConfig({
corpid: '{$site.wxwork.corp_id}',
agentid: '{$site.wxwork.agent_id}',
timestamp: Date.now(),
signature: '{:wx_signature()}',
jsApiList: ['scanQRCode'],
success: function() {
wx.scanQRCode({
needResult: 1,
scanType: ["qrCode"],
success: function(res) {
window.location.href = res.resultStr;
}
});
}
});
});
</script>
</div>
<style>
.wx-login {
margin: 20px auto;
text-align: center;
}
#wx_qrcode {
width: 200px;
height: 200px;
margin: 0 auto;
}
</style>
3.2 后端授权处理
创建控制器application/admin/controller/auth/Wxwork.php:
php复制<?php
namespace app\admin\controller\auth;
use think\Controller;
use think\Db;
class Wxwork extends Controller
{
// 生成扫码跳转URL
public function redirect()
{
$params = [
'appid' => config('site.wxwork.corp_id'),
'redirect_uri' => config('site.wxwork.redirect_uri'),
'response_type' => 'code',
'scope' => 'snsapi_userinfo',
'state' => 'fastadmin_' . time()
];
$url = 'https://open.work.weixin.qq.com/wwopen/sso/qrConnect?' . http_build_query($params);
return redirect($url);
}
// 回调处理
public function callback()
{
$code = input('code');
$state = input('state');
// 验证state前缀防止CSRF
if (strpos($state, 'fastadmin_') !== 0) {
$this->error('非法请求');
}
// 获取access_token
$token = $this->getAccessToken();
// 获取用户信息
$userInfo = $this->getUserInfo($code, $token);
// 查找或创建本地账号
$admin = Db::name('admin')
->where('wx_userid', $userInfo['UserId'])
->find();
if (!$admin) {
// 首次登录自动注册
$data = [
'username' => $userInfo['UserId'],
'nickname' => $userInfo['name'],
'wx_userid' => $userInfo['UserId'],
'wx_avatar' => $userInfo['avatar'],
'password' => '',
'salt' => '',
'status' => 'normal'
];
$adminId = Db::name('admin')->insertGetId($data);
$admin = Db::name('admin')->find($adminId);
}
// FastAdmin登录处理
$auth = \app\admin\library\Auth::instance();
$auth->direct($admin['id']);
return redirect(url('/admin/index/index'));
}
private function getAccessToken()
{
$url = sprintf(
"https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=%s&corpsecret=%s",
config('site.wxwork.corp_id'),
config('site.wxwork.secret')
);
$response = file_get_contents($url);
$data = json_decode($response, true);
if ($data['errcode'] != 0) {
$this->error('获取access_token失败:' . $data['errmsg']);
}
return $data['access_token'];
}
private function getUserInfo($code, $accessToken)
{
$url = sprintf(
"https://qyapi.weixin.qq.com/cgi-bin/user/getuserinfo?access_token=%s&code=%s",
$accessToken,
$code
);
$response = file_get_contents($url);
$data = json_decode($response, true);
if ($data['errcode'] != 0) {
$this->error('获取用户信息失败:' . $data['errmsg']);
}
// 获取详细用户信息
$detailUrl = sprintf(
"https://qyapi.weixin.qq.com/cgi-bin/user/get?access_token=%s&userid=%s",
$accessToken,
$data['UserId']
);
$detailResponse = file_get_contents($detailUrl);
return json_decode($detailResponse, true);
}
}
4. 实战中的坑与解决方案
4.1 常见错误排查表
| 错误现象 | 可能原因 | 解决方案 |
|---|---|---|
| 扫码后页面空白 | 回调域名未配置HTTPS | 申请SSL证书并配置 |
| 提示"redirect_uri参数错误" | 回调域名未在企业微信后台备案 | 检查企业微信管理后台配置 |
| 获取access_token失败 | Secret密钥错误或过期 | 重新生成应用Secret |
| 用户信息获取失败 | 应用未获得通讯录权限 | 在应用权限中开启"成员信息读取" |
| 登录后无权限 | 本地数据库未关联用户 | 检查wx_userid字段是否匹配 |
4.2 性能优化建议
-
AccessToken缓存:
php复制// 使用FastAdmin缓存替代每次请求 $token = cache('wx_access_token'); if (!$token) { $token = $this->getAccessToken(); cache('wx_access_token', $token, 7000); // 企业微信token有效期7200秒 } -
用户信息同步策略:
php复制// 每天第一次登录时同步最新信息 if (date('Ymd', $admin['updatetime']) != date('Ymd')) { Db::name('admin') ->where('id', $admin['id']) ->update([ 'nickname' => $userInfo['name'], 'wx_avatar' => $userInfo['avatar'], 'updatetime' => time() ]); } -
扫码超时处理:
javascript复制// 前端增加30秒超时检测 let timer = setTimeout(() => { $('#wx_qrcode').html('<p>二维码已过期,请<a href="javascript:;" onclick="refreshQrcode()">点击刷新</a></p>'); }, 30000); function refreshQrcode() { clearTimeout(timer); initWxQrcode(); }
5. 扩展功能实现
5.1 与本地账号体系对接
对于已存在本地账号系统的场景,建议采用以下映射方案:
-
邮箱自动关联:
php复制// 通过企业微信绑定的邮箱匹配本地账号 $email = $userInfo['biz_mail'] ?? ''; if ($email) { $localUser = Db::name('admin') ->where('email', $email) ->find(); if ($localUser) { Db::name('admin') ->where('id', $localUser['id']) ->update(['wx_userid' => $userInfo['UserId']]); } } -
手动绑定界面:
html复制<!-- 在用户中心添加绑定入口 --> <div class="form-group"> <label class="control-label">企业微信绑定</label> <div> <?php if($admin['wx_userid']): ?> <span class="text-success">已绑定 (<?= $admin['wx_userid'] ?>)</span> <a href="<?= url('auth/wxwork/unbind') ?>" class="btn btn-danger btn-xs">解绑</a> <?php else: ?> <a href="<?= url('auth/wxwork/bind') ?>" class="btn btn-success btn-xs">立即绑定</a> <?php endif; ?> </div> </div>
5.2 多应用单点登录方案
当企业有多个FastAdmin应用时,可扩展实现SSO:
-
统一认证服务:
php复制// 在中央认证服务生成全局token $ssoToken = md5(uniqid().$userInfo['UserId']); Db::name('sso_tokens')->insert([ 'token' => $ssoToken, 'userid' => $userInfo['UserId'], 'expiretime' => time() + 3600 ]); // 跳转回子系统时携带token $redirectUrl = input('redirect') . '?sso_token=' . $ssoToken; return redirect($redirectUrl); -
子系统验证:
php复制$token = input('sso_token'); $ssoRecord = Db::name('sso_tokens') ->where('token', $token) ->where('expiretime', '>', time()) ->find(); if ($ssoRecord) { $userInfo = Db::name('admin') ->where('wx_userid', $ssoRecord['userid']) ->find(); if ($userInfo) { $auth = \app\admin\library\Auth::instance(); $auth->direct($userInfo['id']); } }
6. 安全加固措施
-
IP白名单控制:
php复制$allowIps = ['192.168.1.0/24', '10.0.0.0/8']; $clientIp = request()->ip(); $allowed = false; foreach ($allowIps as $ip) { if (strpos($ip, '/') !== false) { // CIDR检测 if (ip_in_range($clientIp, $ip)) { $allowed = true; break; } } else { // 精确匹配 if ($clientIp == $ip) { $allowed = true; break; } } } if (!$allowed) { $this->error('访问被拒绝:IP不在白名单内'); } -
敏感操作二次验证:
php复制// 在需要高安全等级的操作前添加 if (!session('wx_authed')) { $this->redirect(url('/admin/auth/wxwork/verify')); } // 验证页面实现 public function verify() { if (request()->isPost()) { $code = input('code'); // 调用企业微信验证接口 if ($this->verifyCode($code)) { session('wx_authed', true); return $this->success('验证成功', input('redirect')); } else { return $this->error('验证失败'); } } return $this->fetch('auth/wxverify'); } -
登录日志审计:
php复制// 在登录成功后记录 Db::name('admin_log')->insert([ 'admin_id' => $admin['id'], 'username' => $admin['username'], 'url' => 'auth/wxwork/callback', 'title' => '企业微信登录', 'content' => json_encode([ 'ip' => request()->ip(), 'user_agent' => request()->server('HTTP_USER_AGENT'), 'wx_userid' => $userInfo['UserId'] ]), 'createtime' => time() ]);
