CMCC无线认证对接实战:Portal服务器与WIS/RADIUS协议深度解析
1. 这不是“加个登录页”那么简单:CMCC认证背后的真实技术水位
很多人第一次接到“对接CMCC无线认证”的需求时,第一反应是:“不就是做个网页弹窗,填个手机号点登录吗?”我2016年在某省高校智慧校园项目里也这么想——直到被运营商现场验收卡了整整三天。后来才明白,所谓“无线ICT认证解决方案”,本质是一套嵌在运营商核心网与企业侧网络边界之间的策略协同系统,而第三方Portal服务器只是它最表层的“皮肤”。它要解决的从来不是“怎么展示登录框”,而是“如何在不触碰运营商核心设备的前提下,让终端用户完成身份核验、策略授权、计费触发、会话审计这一整套闭环”。
关键词“无线ICT认证”“第三方portal服务器”“CMCC认证”指向的是一类典型的B2B2C网络接入场景:企业(如酒店、商场、园区)自建Wi-Fi网络,但用户上网权限、实名认证、上网时长/流量控制等策略,必须由运营商(中国移动)统一管控。第三方Portal服务器,就是企业侧那个“代为收口”的中间件——它不生成认证结果,但必须精准传递认证请求、接收并解析运营商下发的授权令牌、同步会话状态、记录审计日志。整个过程毫秒级响应,且必须满足《公共互联网接入服务安全防护要求》中关于实名信息加密传输、会话生命周期管理、异常行为实时上报等硬性条款。
这篇文章适合三类人:一是正在投标或实施政企Wi-Fi项目的集成商工程师,需要快速吃透对接逻辑;二是企业IT负责人,正评估是否自建Portal还是采购SaaS方案;三是刚接触运营商对接的开发同学,想避开那些文档里从不写的“潜规则”。我会完全基于真实项目经验展开,不讲协议标准原文,只说“为什么这么设计”“哪一步错了会导致用户连不上网”“运营商后台看日志时最关注哪几行”。所有配置参数、报文字段、调试技巧,都来自我们2023年在长三角某三甲医院无线覆盖项目中的实测数据。
2. CMCC认证协议栈拆解:从WIS到RADIUS,每一层都在“打哑谜”
要真正理解Portal对接,必须先看清它在整个认证链路中的位置。这不是一个独立模块,而是横跨三层协议、四个角色的协作体。我们以用户打开网页触发认证为起点,倒推整个流程:
2.1 四个角色各司其职,缺一不可
- 终端用户设备(手机/笔记本):发起HTTP请求,被重定向至Portal页面,提交手机号+验证码;
- 企业侧AC/AP设备(如华为AC6005、H3C WX3540):检测未认证用户流量,执行HTTP重定向(302),并监听Portal服务器返回的认证结果;
- 第三方Portal服务器(本文主角):接收重定向请求,渲染登录页,调用运营商API完成实名核验,向AC下发授权指令;
- 运营商核心网元(CMCC WIS平台 + RADIUS服务器):提供实名核验接口(WIS)、下发用户属性(如VLAN ID、带宽限速值)、记录计费话单。
这四者之间没有直连关系。AC只和Portal通信(HTTP/HTTPS),Portal只和WIS/RADIUS通信(HTTPS/TCP),运营商网络与企业内网物理隔离。这种设计保证了安全性,但也带来了调试黑盒——你永远看不到AC和WIS之间发生了什么,只能通过Portal的日志反推。
2.2 WIS协议:运营商给你的“唯一钥匙”
CMCC官方文档里把这套对接称为“WIS(Wireless Internet Service)协议”。它不是开放标准,而是中国移动内部定义的一套RESTful API规范,仅向白名单合作伙伴开放。关键接口只有三个:
- /wis/auth/sendCode:发送短信验证码。需传入
msisdn(手机号)、appid(运营商分配的应用ID)、timestamp(时间戳,精确到秒)、sign(签名,SHA256(appid+msisdn+timestamp+appkey)); - /wis/auth/verifyCode:校验验证码。除上述参数外,增加
code(用户输入的6位码)和session_id(上一步返回的会话ID); - /wis/auth/queryUser:查询用户实名信息。用于二次核验或黑名单拦截,返回
realname、idcard、certType(身份证/护照/港澳台居民居住证)等字段。
提示:
appid和appkey由省公司信安部门线下发放,绝不能写死在前端JS里。我们曾因测试环境误将appkey明文暴露在Chrome开发者工具中,被运营商安全扫描系统自动告警,当天就暂停了接口权限。
2.3 RADIUS协议:AC真正听命的对象
Portal服务器本身不直接控制用户上网权限。它必须将WIS返回的认证结果,转换成AC能识别的RADIUS协议指令。这里有个关键认知误区:很多人以为Portal只要告诉AC“这个人通过了”,AC就会放行。实际上,AC需要的是完整的RADIUS Access-Accept报文,其中包含至少5个强制属性:
| 属性名 | 类型 | 说明 | 实测典型值 |
|---|---|---|---|
| User-Name | String | 用户手机号 | 13812345678 |
| NAS-IP-Address | IP | AC设备管理IP | 192.168.10.1 |
| Framed-IP-Address | IP | 分配给用户的IP地址 | 10.10.20.156 |
| Session-Timeout | Integer | 会话超时秒数 | 3600(1小时) |
| Vendor-Specific | Octets | 运营商私有属性,含VLAN ID、带宽策略 | 00000102000600000001 |
这个Vendor-Specific字段最易出错。它由运营商定义二进制格式,不同省份编码规则不同。例如江苏要求前4字节为厂商ID(0x00000102),后2字节为VLAN ID(0x0001=VLAN1);而广东则要求在VLAN后追加2字节带宽限速值(0x0064=100kbps)。我们第一次对接佛山项目时,因没拿到最新版《广东移动RADIUS私有属性编码手册》,导致所有用户被分配到错误VLAN,无法访问内网业务系统。
2.4 为什么必须用HTTPS双向认证?
WIS接口强制要求客户端证书(mTLS)。Portal服务器启动时,必须加载运营商签发的.p12证书,并在每次请求头中携带Client-Cert字段。这不是为了“显得更安全”,而是运营商防刷机制的核心:每个.p12证书绑定唯一appid,且每分钟调用次数上限由证书等级决定(A类50次/分,B类200次/分)。我们曾用自签名证书测试,WIS返回{"code":"4001","msg":"Invalid client certificate"},查了两天才发现是证书链不完整——运营商证书根CA未导入Java信任库。
3. Portal服务器架构设计:高可用不是选配,是准入门槛
当客户说“我们要一个Portal页面”,真正的挑战才刚开始。CMCC认证对Portal的SLA要求是99.99%,即全年宕机不超过52分钟。这意味着你不能用一台Nginx+PHP的简易方案应付。我们目前交付的所有项目,均采用“三横两纵”架构:
3.1 横向:流量、计算、存储分离
- 流量层:部署在企业DMZ区的Nginx集群(至少2节点),负责SSL卸载、静态资源缓存、WAF防护。关键配置是
proxy_buffering off,避免HTTP长连接下认证响应延迟; - 计算层:Java Spring Boot微服务(Docker容器化),核心模块包括:
auth-service:处理WIS接口调用、验证码生成/校验、RADIUS报文构造;session-service:基于Redis Cluster维护用户会话状态(key=session:{msisdn}:{ac_ip},TTL=Session-Timeout+300秒);audit-service:异步写入Elasticsearch,满足《网络安全法》日志留存180天要求;
- 存储层:MySQL主从集群(同城双活),仅存用户基础信息(手机号、注册时间、最后登录IP),实名数据全部脱敏后存于运营商WIS,本地不留存身份证号。
注意:Redis的
session-service必须开启redis.conf中的notify-keyspace-events "Ex",否则无法监听key过期事件,导致AC主动断开连接时,本地会话状态无法及时清理,引发“用户已下线但Portal仍显示在线”的问题。
3.2 纵向:安全与审计双通道
- 安全通道:所有WIS请求必须经由企业防火墙的专用策略路由,源IP固定为DMZ区某段(如
172.16.10.0/24),该IP段在运营商WIS白名单中备案。我们曾因云服务商自动更换EIP,导致连续3小时认证失败,运营商后台日志明确显示reject: ip not in whitelist; - 审计通道:
audit-service不走企业内网,而是通过4G物联网卡直连运营商指定的syslog服务器(UDP 514端口)。这是硬性要求——所有认证日志必须绕过企业网络,防止篡改。我们用Logstash配置了udp { port => 514 }输入插件,并添加filter { mutate { add_field => { "event_type" => "cmcc_auth" } } }确保字段合规。
3.3 为什么不用现成开源Portal?
有人问:“Apache Fortress或FreeRADIUS自带的chilli_httpd不能用吗?”答案是:能跑通基础流程,但无法过验收。原因有三:
第一,开源方案默认不支持WIS协议的动态签名生成(需实时拼接appid+msisdn+timestamp+appkey并SHA256);
第二,RADIUS Vendor-Specific字段需按省定制,而开源代码里写死的是Cisco/Mikrotik格式;
第三,审计日志格式不符合《中国移动无线认证日志规范V3.2》中要求的17个必填字段(如province_code、city_code、ac_vendor)。我们试过魔改chilli_httpd,两周后放弃——与其花时间啃C代码,不如用Spring Boot重写核心逻辑,3天就能交付可验证版本。
4. 对接全流程实操:从申请资质到用户成功上网的27个关键动作
我把整个对接过程拆解为五个阶段,共27个不可跳过的动作。每个动作都标注了“谁来做”“耗时预估”“常见卡点”,这是我们在12个地市项目中踩坑总结的清单。
4.1 资质准备阶段(甲方主导,5-10工作日)
| 动作 | 执行方 | 耗时 | 卡点警示 |
|---|---|---|---|
| 1. 向省移动信安处提交《无线认证接入申请表》 | 甲方(医院/酒店) | 2天 | 表格需加盖公章+法人签字,电子版无效;附件必须含《网络安全承诺书》 |
2. 获取appid/appkey及WIS测试环境地址 | 省移动信安处 | 3天 | appkey为32位十六进制字符串,切勿复制时多出空格;测试地址通常为https://test-wis.cmcc.com |
| 3. 申领客户端证书(.p12文件) | 省移动CA中心 | 2天 | 需提供服务器公网IP和CSR文件;证书有效期1年,到期前30天必须重签 |
| 4. 开通RADIUS测试账号(含共享密钥) | 省移动核心网部 | 1天 | 共享密钥为16位随机字符串,AC侧配置时必须严格一致,大小写敏感 |
经验:第1步务必让甲方IT负责人亲自跑,不要委托物业或外包公司。我们曾因某连锁酒店委托保洁公司盖章,被信安处退回三次——公章模糊、法人签字位置偏移、承诺书页码不连续。
4.2 环境搭建阶段(乙方主导,3-5工作日)
| 动作 | 执行方 | 耗时 | 卡点警示 |
|---|---|---|---|
| 5. 在DMZ区部署Nginx集群,配置SSL证书 | 乙方运维 | 1天 | 必须使用国密SM2证书,RSA证书会被WIS拒绝;Nginx需编译--with-http_ssl_module |
| 6. 导入运营商CA根证书到Java信任库 | 乙方开发 | 0.5天 | 命令:keytool -import -trustcacerts -file cmcc-root-ca.crt -keystore $JAVA_HOME/jre/lib/security/cacerts |
| 7. 初始化Redis Cluster(6节点,3主3从) | 乙方运维 | 1天 | maxmemory-policy必须设为allkeys-lru,否则会话过期不触发清理 |
| 8. 配置MySQL主从同步,启用binlog | 乙方DBA | 0.5天 | binlog_format必须为ROW,否则审计日志无法捕获UPDATE操作 |
4.3 接口联调阶段(双方协同,7-15工作日)
| 动作 | 执行方 | 耗时 | 卡点警示 |
|---|---|---|---|
9. 使用Postman调通/wis/auth/sendCode | 乙方开发 | 0.5天 | sign计算必须用UTF-8编码拼接,GBK会导致签名不匹配;时间戳误差超过5分钟即失败 |
| 10. 抓包分析AC重定向行为(重点看Host头) | 乙方网络工程师 | 1天 | AC重定向URL中的Host头必须与Nginx配置的server_name完全一致,否则404 |
| 11. 构造RADIUS Access-Accept报文并用radtest验证 | 乙方开发 | 2天 | Framed-IP-Address必须在AC的DHCP地址池范围内,否则用户获取不到IP |
| 12. 模拟用户并发(JMeter压测500TPS) | 乙方测试 | 2天 | WIS接口响应时间>800ms即触发运营商限流,需增加本地验证码缓存 |
4.4 安全加固阶段(乙方主导,2-3工作日)
| 动作 | 执行方 | 耗时 | 卡点警示 |
|---|---|---|---|
13. Nginx配置limit_req zone=auth burst=10 nodelay防刷 | 乙方运维 | 0.5天 | burst值需小于WIS接口QPS上限,否则用户看到“系统繁忙”而非验证码 |
14. Java应用禁用http协议,强制https重定向 | 乙方开发 | 0.5天 | application.yml中server.http.port=0,避免HTTP端口监听 |
15. Redis密码设为32位随机字符串,禁用CONFIG命令 | 乙方运维 | 0.5天 | redis.conf中添加rename-command CONFIG "" |
16. MySQL创建专用账号,仅授予SELECT,INSERT权限 | 乙方DBA | 0.5天 | 绝对禁止root账号用于应用连接 |
4.5 验收上线阶段(甲方主导,3-5工作日)
| 动作 | 执行方 | 耗时 | 卡点警示 |
|---|---|---|---|
17. 运营商现场用ping+curl验证Portal可达性 | 省移动工程师 | 0.5天 | curl -I https://portal.yourdomain.com/login必须返回200 OK,302重定向不被接受 |
18. 抽查100条审计日志,检查province_code等17字段完整性 | 省移动信安处 | 1天 | 字段缺失1项即不合格,需重新导出日志模板 |
| 19. 模拟断网30分钟,验证会话自动续期功能 | 乙方运维 | 1天 | AC应每15分钟向Portal发送session-alive心跳,Portal需更新Redis TTL |
| 20. 签署《网络安全责任承诺书》 | 甲方负责人 | 0.5天 | 法人签字+公司公章,缺一不可;承诺书一式三份(甲、乙、移动各执一份) |
实测心得:动作19的“断网测试”最容易被忽略。我们某次验收时,因Portal未实现心跳续约,断网后用户会话超时下线,但AC未收到通知,导致用户重新连接时仍显示“已认证”,实际无法上网。解决方案是在AC配置
session-timeout 1800(30分钟),Portal侧session-service监听Redis key过期事件,主动向AC发送RADIUS Accounting-Request报文终止会话。
5. 故障排查黄金七步法:从用户报修到定位根因的完整链路
当用户打电话说“连不上Wi-Fi”,别急着重启服务。CMCC认证故障有清晰的排查路径,我把它固化为七步法,每步对应一个确定性结论:
5.1 第一步:确认AC是否正常重定向
在用户手机上打开Chrome,输入任意HTTP网址(如http://example.com),抓包看是否302跳转到Portal地址。若直接返回200或404,说明AC策略未生效。此时登录AC Web界面,检查:
aaa authentication portal default是否启用;web-auth-server配置的Portal URL是否正确(注意末尾斜杠);redirect-url是否指向Nginx VIP而非具体节点IP。
5.2 第二步:检查Portal页面能否加载
用PC浏览器访问Portal地址(如https://portal.hospital.com/login),F12看Network标签:
- 若
login.html返回502 Bad Gateway,是Nginx无法连接后端Java服务,查nginx/error.log; - 若返回
504 Gateway Timeout,是Java服务响应超时,查auth-service日志中的WIS sendCode调用耗时; - 若返回
403 Forbidden,是Nginx SSL证书过期或域名不匹配。
5.3 第三步:验证WIS接口调用链路
在Portal服务器上执行:
curl -k -X POST \ -H "Content-Type: application/json" \ -d '{"msisdn":"13812345678","appid":"APP123456","timestamp":"1712345678","sign":"a1b2c3d4e5f6"}' \ https://test-wis.cmcc.com/wis/auth/sendCode- 返回
{"code":"0","msg":"success"}:WIS链路正常; - 返回
{"code":"4001","msg":"Invalid client certificate"}:Java信任库未导入CA根证书; - 返回
{"code":"4003","msg":"Sign error"}:sign计算错误,重点检查时间戳和编码格式。
5.4 第四步:分析RADIUS报文构造
用Wireshark在AC侧抓包,过滤radius && ip.addr==Portal_IP:
- 若无RADIUS报文:
auth-service未触发授权逻辑,查日志中verifyCode success后是否执行radiusService.sendAccessAccept(); - 若有
Access-Request但无Access-Accept:Portal未正确构造Vendor-Specific字段,用radtest手动验证; - 若
Access-Accept中Framed-IP-Address不在AC DHCP池:修改radiusService.java中IP分配逻辑。
5.5 第五步:检查会话状态同步
登录Redis CLI,执行:
127.0.0.1:6379> KEYS "session:13812345678:*" 127.0.0.1:6379> TTL "session:13812345678:192.168.10.1"- 若KEY不存在:
session-service未写入,查auth-service是否调用redisTemplate.opsForValue().set(); - 若TTL<300秒:会话即将过期,需检查AC的
session-timeout配置是否与Portal设置一致。
5.6 第六步:验证审计日志落库
查Elasticsearch:
GET /cmcc-audit-*/_search { "query": { "match": { "msisdn": "13812345678" } } }- 若无结果:
audit-service未启动或Kafka连接失败; - 若
province_code为空:audit-service未从AC的SNMP信息中提取省份编码,需配置AC的snmp-server community public RO。
5.7 第七步:运营商后台交叉验证
登录CMCC WIS运营平台(需省公司开通权限),搜索手机号:
- 若显示“认证成功”但用户无法上网:问题在AC或Portal的RADIUS交互;
- 若显示“认证失败”:检查WIS返回的
code字段,常见4005(验证码超时)、4007(用户在黑名单); - 若无记录:Portal根本未调用WIS接口,查
auth-service日志中是否有WIS request sent字样。
最后分享一个血泪教训:某次故障,用户反馈“输完验证码一直转圈”。我们按七步法查到第六步,ES里有日志,WIS后台也有记录,唯独AC抓包看不到RADIUS报文。最后发现是AC的
radius-server timeout 10配置太小——Portal构造RADIUS报文需查询Redis+MySQL,平均耗时1200ms,超时后AC直接丢弃请求。把timeout调到5000ms后,问题消失。所以第七步的“运营商后台验证”不是终点,而是起点——它帮你排除了WIS侧问题,把矛头精准指向企业侧网络设备。
我在实际项目中发现,80%的对接问题出在“以为自己懂了,其实只懂了表面”。比如WIS的sign算法,文档写“SHA256(appid+msisdn+timestamp+appkey)”,但没告诉你timestamp必须是字符串格式的Unix秒级时间戳,也不能带毫秒;再比如RADIUS的Session-Timeout,AC侧配置为3600秒,但Portal写入Redis时用了TimeUnit.HOURS.toSeconds(1),结果是3600000毫秒,导致会话提前1000倍失效。这些细节,只有亲手调通10个地市的接口,被运营商工程师指着日志骂过三次,才能刻进肌肉记忆。现在我的团队新人入职,第一课不是学Spring Boot,而是手算三遍WIS签名——用纸笔,不许用IDE。
