搭一套认人的 Wi-Fi:802.1X / EAP / RADIUS

为什么又瞎折腾
最近在研究 TISAX II 认证,然后看了下 VDA ISA 6.0 的要求,其中里面有几条要求:
- 4.1.1 识别接入网络的每一个用户和设备
- 5.2.4 登录成功 / 失败留日志
- 5.3.4 / 6.1.1 外部 / 第三方访问单独管控
基于这个要求,回看我们公司现有的 Wi-Fi 认证方式:全员共用一个 Wi-Fi 密码。
这种方式最大的问题不在加密强度,而是它压根没有「身份」的概念。
手机、笔记本、IoT、来访客户,在 AP 上看起来都是一样的,根本无法区分。
而 TISAX 要的识别、审计、按身份控制,现有的 Wi-Fi AP 接入方式一样都做不到。
虽然 VDA ISA 没有直接点名 802.1X,写的是 NAC 这种泛要求。
但 Wi-Fi 要想实现身份认证,业界基本只有 802.1X / EAP 这一条路。
考虑到公司本来就有 AD,最快捷的路径就是把 RADIUS(NPS) 挂到 AD 服务器上,从而实现用户密码登录认证。
名词速览
下面是一些相关的缩写,不熟悉的朋友可以先瞄一眼,混个眼熟 🤭:
- 802.1X —— IEEE 标准,定义「在 802 网络上接入认证」的协议框架。客户端、AP/交换机、认证服务器三方协作。
- EAP(Extensible Authentication Protocol)—— 真正承载凭据的协议。EAP 是个壳,里面塞什么由具体的 EAP method 决定。
- PEAP(Protected EAP)—— 微软主推的 EAP method。外层先建一条 TLS 隧道(只验服务端证书),内层再走另一种简单认证。本文用 PEAP-MSCHAPv2,内层就是 MSCHAPv2,对 AD 密码原生友好。
- EAP-TLS —— 另一种主流 EAP method,客户端和服务端都要证书。最安全也最麻烦,得给每台设备签客户端证书。本文不选它。
- TLS(Transport Layer Security)—— 加密传输的事实标准(HTTPS 的「S」就是它)。PEAP 外层就是一条 TLS 隧道,把 802.1X 内层的密码握手包起来防中间人窃听。
- CA(Certificate Authority)—— 证书颁发机构。客户端只信任可信 CA 签发的服务端证书。公网 CA(Google Trust Services、Let’s Encrypt 等)签的证书所有终端自动认;用自建 CA 就得把 root CA 推到每台客户端上才行。
- AP(Access Point)—— Wi-Fi 接入点,家里墙上、办公室天花板那个东西。在 802.1X 流程里担任 NAS 角色,把客户端凭据中转给 RADIUS,自己不做认证。
- SSID(Service Set Identifier)—— Wi-Fi 网络的名字,客户端列表里看到的那个文本。本文测试用的 SSID 是
Office_EAP。 - NAS(Network Access Server)—— RADIUS 语境里对 AP / 交换机这类「替客户端去找 RADIUS」的中转设备的统称。跟家里那个存数据的 NAS 没关系。
- hostapd / wpad —— Linux 上的 802.11 authenticator。OpenWrt 里
wpad是 hostapd + wpa_supplicant 合并的二进制;不同包(basic / full)能力差很多,下面会反复提。 - RADIUS(Remote Authentication Dial-In User Service)—— AP 和后端认证服务器之间的协议,UDP 1812(认证)/ 1813(计费)。
- NPS(Network Policy Server)—— 微软自家的 RADIUS server 实现,Windows Server 一行 PowerShell 装好,跟 AD 是天生一对。同类还有 FreeRADIUS、Cisco ISE。
- LDAP(Lightweight Directory Access Protocol)—— 目录服务的查询协议,TCP 389(明文)/ 636(LDAPS over TLS)。AD 是基于 LDAP 的目录服务实现,Authentik、FreeIPA 等也走 LDAP。
- AD / DC(Active Directory / Domain Controller)—— AD 是微软的 LDAP 目录服务,集中管账号、密码、组、设备。DC 是跑 AD 服务的 Windows Server 机器;本文 NPS 跟 AD 长在同一台 DC 上(
dc.example.com),PEAP 内层的 MSCHAPv2 直接拿密码到本机 AD 验。
整体拓扑
flowchart LR
STA[手机 / 笔记本]
AP["AP
OpenWrt + hostapd
(NAS)"]
RADIUS["NPS
dc.example.com:1812"]
AD[("Active Directory
example.com")]
CERT["wildcard 证书
*.example.com"]
STA -->|EAPOL / WPA2-EAP| AP
AP -->|"RADIUS UDP 1812
shared secret"| RADIUS
RADIUS -->|"MSCHAPv2
against AD"| AD
RADIUS -.uses.-> CERT
客户端走到 AD,中间是三段独立的认证:
- 客户端 ↔ AP:客户端用 EAPOL 协议跟 AP 握手,AP 只是中转,不验任何东西。
- AP ↔ NPS:靠预共享密钥(shared secret),每台 AP 一份。
- NPS ↔ AD:NPS 拿到 PEAP 内层的 MSCHAPv2 凭据,去 AD 上验。NPS 的机器账号必须加入 AD 的
RAS and IAS Servers内置组——这一步关键,没加进去 NPS 是读不到用户密码哈希的。
NPS 复用现有的 wildcard 证书(比如 *.example.com)做 PEAP 外层 TLS 的服务端证书。
好处很明显:不用自建 CA、所有客户端默认信任、不需要每个设备手动确认证书。
但这也同时成为了这套架构里面的一个坑,后面会展开说明。
NPS 装机
先把 NPS 装上。它要跑在 AD DC 上(本文用 dc.example.com,IP 10.0.100.32)——跟 AD 共用一台机器,PEAP 内层验 MSCHAPv2 时直接读本机的密码哈希就行。两行 PowerShell 就装完:
1 | Install-WindowsFeature -Name NPAS -IncludeManagementTools |
第一行装 NPAS 角色(Network Policy and Access Services),包括管理工具。第二行把 DC 的机器账号加进域内置组 RAS and IAS Servers——这是 NPS 能跑 PEAP 的前提,没这一步 NPS 无法从 AD 读用户的 NT 密码哈希,MSCHAPv2 内层永远跑不通。装完防火墙规则会自动开 UDP 1812 / 1813。
第一个坑:「Configure 802.1X」向导没了。 网上一抓一大把的教程(包括微软自己的旧版 docs)都让你右键 NPS (Local) → 选 Configure 802.1X,跟着向导走 8 步。但这个入口在 Windows Server 2019 之后某个版本就被砍了,现在的 NPS MMC 控制台里根本没有这一项。
正确做法:手动建一条 Network Policy(决定谁能进),Connection Request Policy 用安装时自带的默认那条 Use Windows authentication for all users 就行,不用动。
服务端证书:复用通配符 + 续期自动重绑
PEAP 外层 TLS 要求客户端能信任服务端证书。自签证书在这一步特别麻烦——要么所有客户端都装 root CA,要么每次连都点「信任」。能用公网通配符证书就别自签。
公司 DC 上本来就有一张 *.example.com 的通配符证书,由 Google Trust Services 签的,挂在 LocalMachine\My 给 RDP 用,已经有现成的脚本 Update-Windows-Cert.ps1 每天比一下本地和服务器证书的 MD5,不一样才去 getssl 拉新证书替换。我们直接复用这张证书,在 NPS MMC 向导里选它一下,完成绑定。
第二个坑(埋得最深的一个):NPS 是按 SHA-1 thumbprint 绑证书的。 换句话说,证书内容只要变,thumbprint 就变,NPS 还指着旧 thumbprint 找证书——找不到了,下一秒所有 PEAP 握手全跪。getssl 默认 60 天续一次,意味着每 60 天 Wi-Fi 会自己「死」一次。
解法是在原来的 Update-Windows-Cert.ps1 里加一段,每次拉到新证书后,直接二进制 patch 系统文件 C:\Windows\System32\ias\ias.xml。NPS 的 EAP 配置在这个 XML 里以 msEAPConfiguration 节点出现,里面有形如 14000000<old-thumb-hex> 的字段(0x14 = 20,是 SHA-1 的字节长度前缀;后面 40 个 hex 字符是 thumbprint 本体),把这一段替换成 14000000<new-thumb-hex>,存盘,Restart-Service IAS。
1 | msEAPConfiguration blob layout(76 字节,PEAP): |
| Offset (hex chars) | Field |
|---|---|
| 0–31 | EAP type header(19000000…,type 25 = PEAP) |
| 32–71 | header padding + length markers |
| 72–79 | 14000000(长度 20,SHA-1 大小) |
| 80–119 | 40 字符 hex SHA-1 thumbprint ← patch 在这里 |
| 120–151 | flags(fast reconnect、内层 MSCHAPv2 等) |
实现细节有几条要注意:
- 跑前抓旧 thumbprints。从
$ExistingCerts里把所有还在 store 里的相关证书 thumbprint 都记下来,因为 patch 逻辑是「按旧 thumbprint 找位置,换成新 thumbprint」。 - 备份。每次替换都旁存一份
Update-Windows-Cert.ps1.bak-<timestamp>,万一 XML 写坏了能回滚。
跑通之后,60 天一炸的定时炸弹就拆掉了。
Network Policy:谁能进
建一条策略就够,配置长这样:
| 字段 | 值 |
|---|---|
| Name | Wi-Fi-PEAP |
| Type | Unspecified |
| Conditions | NAS-Port-Type ∈ {Wireless-IEEE 802.11, Wireless-Other} |
| Access | Granted |
| Auth methods | 只勾 PEAP,MS-CHAP / CHAP / PAP 全部不勾 |
| Inner method | EAP-MSCHAP v2 |
| User filter | 无 |
注意:这一条要从 NPS MMC 控制台手动建,PowerShell 模块没有暴露 Network Policy 的 CRUD 命令;而且 msEAPConfiguration 那串二进制 blob 也没法手写——必须走 GUI 让 NPS 帮你生成。
「要不要按 AD 安全组过滤」这个问题,三个方案权衡过:
- 完全不过滤:OU=People 全员都能上 Wi-Fi;OU=Services 那些服务账号在 AD 里本来就设了
deny network logon,到不了 NPS 这一步。← 当前选这个——最简单,不建新组、不写同步脚本,访问边界完全跟现有的 OU 划分对齐。 - 建一个
Wi-Fi-Users安全组:每个员工入职时手工加。等于多了一个显式的「总开关」,但流程也多一道,新员工容易漏掉。 - PowerShell 影子组:定时把
OU=People的成员同步到一个安全组。最接近「按 OU 过滤」的语义,但多一个会失败的部件——影子组没同步上,认证就跟着挂。
何时切到 2 / 3:合同工、顾问之类的账号也进了 AD 但不该给 Wi-Fi 的时候。届时直接换条件即可,Network Policy 改一处。
RADIUS 客户端:每台 AP 一条
每台 AP 一份独立的 RADIUS 客户端条目 + 独立的共享密钥——任何一台 AP 被入侵或者丢失了固件,只会影响自身,其它 AP 不受影响,也无需更换密钥。
1 | $secret = -join ((48..57)+(65..90)+(97..122) | Get-Random -Count 32 | ForEach-Object {[char]$_}) |
第三个坑:密钥必须限制成纯字母数字。 第一次试用了 New-Guid + 特殊字符的写法,结果存进文件再用 Get-Content 读回来,PowerShell 的换行/编码处理悄悄改了字符,NPS 收到 RADIUS 包验签对不上号,查了半天才定位到是字符问题。改用 0-9 / a-z / A-Z 这套字符集就再没遇到过这个问题。
AP 侧(OpenWrt 25 + 小米 AX6S)
测试用的 AP 是小米 Redmi 路由器 AX6S(MediaTek MT7622B + MT7915E),刷的 OpenWrt 25.12.2,AP 模式(所有口 bridge 进 br-lan,没 DHCP、没防火墙)。
UCI 配置(在 5GHz 的 wifi-iface 上启用 EAP,2.4GHz 保留 PSK 不动也行,看部署节奏):
1 | uci set wireless.wifi5g.ssid='Office_EAP' |
关键几点:
encryption='wpa2+ccmp'—— EAP 在 OpenWrt 里就是这个值(不是wpa2-eap或wpa2-enterprise,那俩是 LuCI 显示用的别名)。ieee80211w='1'—— 把 PMF(受保护管理帧)设为 optional 模式:支持的新设备走 PMF,老设备退到不启用 PMF。生产环境如果设备都新,可以收紧到'2'(required)。auth_secret/acct_secret用同一个值即可,NPS 那一侧没分。- 删掉
key—— EAP 不用 PSK 字段,留着 hostapd 会犯迷糊。
第四个坑(这一坑搞了最久):官方 OpenWrt 安装的是 wpad-basic-mbedtls。
这玩意只带客户端那一侧的代码,加上 PSK / SAE 这种预共享密钥的 AP 侧实现,根本没有 EAP 用的 AP 侧代码。配下去 auth_server_* 这一类指令,hostapd 当成「不认识的配置项」静默忽略——SSID 永远不广播,LuCI 上还看不出任何报错信息。当时调 LuCI 设置调了快两个小时,从信道、TX power、加密版本一路试到 country code,问题没有任何变化。
怎么验证装的是哪一版?一行 strings:
1 | strings /usr/sbin/wpad | grep auth_server_shared_secret |
wpad-basic-mbedtls 这条命令什么都不输出(二进制里压根没有这个字符串);wpad-mbedtls(full 版)能匹到一行。OpenWrt 25.x 的 apk 体系下解决方式:
1 | apk del wpad-basic-mbedtls && apk add wpad-mbedtls |
替换包会重启 hostapd,所有现有 Wi-Fi 连接断 ~10 秒。如果这台 AP 同时承载 PSK 客户端,建议挑业务低峰时段做。
iOS / macOS:那个挥之不去的「证书不受信任」
配置好后测试时碰到一个让人哭笑不得的现象:iPhone 第一次连 SSID 就弹「Not Verified」让你点 Trust——明明用的是 Google Trust Services 签的公网通配符证书,按道理 iOS 应该自动信任。
真相:802.1X 服务端证书的信任和 HTTPS 信任链是两套独立机制。HTTPS 那一边,iOS 默认就信任所有主流公网 CA 颁发的证书;但 802.1X 这一边,iOS(Android 也一样)没办法自己判断「这个 SSID 应该对应哪张证书」——任何人都能起一个同名 SSID 加一张随便什么 CA 签的证书来钓鱼。所以 iOS 强制让用户首次手动确认,确认过之后再把这张证书和这个 SSID 绑定下来。
目前的做法:让用户首次连这个 SSID 时手动点一次 Trust,iOS 会把证书指纹和 SSID 绑定下来,这台设备之后再连就不会再弹。设备数量不多,这样够用。
排坑速查
把生产里真碰上的几条记一下:
| 现象 | 可能原因 | 排查 / 修复 |
|---|---|---|
AP 日志 RADIUS No response from Authentication server |
NPS 没回 — 多半是 client IP 没注册 | NPS System event 13 会写真实源 IP,加成 client |
STA authenticated → associated → disassociated 几秒后掉,看不到 EAP 交互 |
hostapd 广播 RSN 但 BSS 实际跑 open(hostapd 状态机错位) | 重启 AP;问题持续就换固件版本 |
| 用了一阵突然全断(约 60 天周期一次) | 证书续期了但 NPS 还绑旧 thumbprint | 跑 Update-Windows-Cert.ps1;或核对 Cert:\LocalMachine\My thumbprint 和 ias.xml 里 msEAPConfiguration blob |
| SSID 不广播,hostapd 看不出报错 | wpad 是 basic 版没有 EAP authenticator | `strings /usr/sbin/wpad |
DC 上看 RADIUS 实时认证日志的 PowerShell 一行命令:
1 | Get-WinEvent -FilterHashtable @{LogName='Security'; ID=6272,6273,6274; StartTime=(Get-Date).AddMinutes(-10)} ` |
事件 ID 速查:
- 6272 Access granted
- 6273 Access denied(
ReasonCode字段说原因) - 6274 Discarded(比如未知 RADIUS client)
- 13(System log,不在 Security) RADIUS message from invalid client IP——这一条是 SNAT 问题的招牌
下一步:MAC 白名单 + 动态 VLAN
EAP 跑稳之后想接着做的事:按设备身份分 VLAN。
目标:员工自己的笔记本 / 手机(已注册 MAC)进 VLAN 50(信任网段,可访问内网服务);同样 EAP 认证成功、但 MAC 不在白名单的设备(员工的私人 IoT、临时设备)扔进 VLAN 60(隔离网段,只能上网)。
实现思路:NPS 的 Network Policy 里加 Calling-Station-Id 条件做白名单匹配,命中和未命中分别走两条 policy,policy 上挂三个 RADIUS Tunnel 属性:
| 属性 | 取值(VLAN 50) | 取值(VLAN 60) |
|---|---|---|
Tunnel-Type (64) |
VLAN |
VLAN |
Tunnel-Medium-Type (65) |
802 |
802 |
Tunnel-Private-Group-ID (81) |
50 |
60 |
AP 这一侧 hostapd 会自动读 Tunnel-Private-Group-ID,把这个 STA 推进对应的 VLAN bridge(OpenWrt 上需要预先把 VLAN 50 / 60 的桥配置好)。
为什么不在 hostapd 上用 macaddr_acl=1 + accept_mac_file 做 MAC 白名单?两者对比:
hostapd accept_mac_file |
NPS 侧 Calling-Station-Id | |
|---|---|---|
| 拒绝时机 | EAP 之前,rejected MACs 不走 RADIUS | EAP 之后,所有人都跑完认证 |
| 黑/白名单存放 | 每台 AP 一份 .txt,得分发 |
NPS 一处集中 |
| 改一次生效范围 | 要 scp 到所有 AP 再重启 hostapd | NPS 改完即时全网生效 |
| 日志 | hostapd 调试日志 | Security event 6272/6273,统一审计 |
| 副产物 | — | 同时拿到了动态 VLAN 分流——比黑白名单更进一步 |
实现门槛不高,等 EAP 主路径稳一段时间再做。
收尾
后续考虑可以接着做的几件事情:
- EAP-TLS:给公司发的笔记本签客户端证书,连 Wi-Fi 不用输密码——配合 AD CS 自动续期。
- 访客网络改成门户认证:访客 SSID 走 OIDC(前面挂 Authentik),扫码登录或一次性邀请链接,比 WPA-PSK 共享密码安全得多。
- Authentik 直接出 RADIUS:摆脱对 Windows AD 的硬依赖,让纯 macOS 或纯 Linux 的场景也能跑 802.1X,同时一并支持 OIDC / SAML 协议——身份层彻底收敛到 Authentik 一处。