搭一套认人的 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,中间是三段独立的认证:

  1. 客户端 ↔ AP:客户端用 EAPOL 协议跟 AP 握手,AP 只是中转,不验任何东西。
  2. AP ↔ NPS:靠预共享密钥(shared secret),每台 AP 一份。
  3. 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
2
Install-WindowsFeature -Name NPAS -IncludeManagementTools
netsh ras add registeredserver

第一行装 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 安全组过滤」这个问题,三个方案权衡过:

  1. 完全不过滤:OU=People 全员都能上 Wi-Fi;OU=Services 那些服务账号在 AD 里本来就设了 deny network logon,到不了 NPS 这一步。← 当前选这个——最简单,不建新组、不写同步脚本,访问边界完全跟现有的 OU 划分对齐。
  2. 建一个 Wi-Fi-Users 安全组:每个员工入职时手工加。等于多了一个显式的「总开关」,但流程也多一道,新员工容易漏掉。
  3. PowerShell 影子组:定时把 OU=People 的成员同步到一个安全组。最接近「按 OU 过滤」的语义,但多一个会失败的部件——影子组没同步上,认证就跟着挂。

何时切到 2 / 3:合同工、顾问之类的账号也进了 AD 但不该给 Wi-Fi 的时候。届时直接换条件即可,Network Policy 改一处。

RADIUS 客户端:每台 AP 一条

每台 AP 一份独立的 RADIUS 客户端条目 + 独立的共享密钥——任何一台 AP 被入侵或者丢失了固件,只会影响自身,其它 AP 不受影响,也无需更换密钥。

1
2
3
4
$secret = -join ((48..57)+(65..90)+(97..122) | Get-Random -Count 32 | ForEach-Object {[char]$_})
$secret | Set-Content "C:\Users\Administrator\nps-radius-secret-<name>.txt" -NoNewline
New-NpsRadiusClient -Name "<name>" -Address "<ip>" -SharedSecret $secret `
-AuthAttributeRequired $false -VendorName "RADIUS Standard"

第三个坑:密钥必须限制成纯字母数字。 第一次试用了 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
2
3
4
5
6
7
8
9
10
11
12
uci set wireless.wifi5g.ssid='Office_EAP'
uci set wireless.wifi5g.encryption='wpa2+ccmp'
uci set wireless.wifi5g.auth_server='10.0.100.32'
uci set wireless.wifi5g.auth_port='1812'
uci set wireless.wifi5g.auth_secret='<secret>'
uci set wireless.wifi5g.nasid='<ap-hostname>'
uci set wireless.wifi5g.acct_server='10.0.100.32'
uci set wireless.wifi5g.acct_port='1813'
uci set wireless.wifi5g.acct_secret='<secret>'
uci set wireless.wifi5g.ieee80211w='1'
uci -q delete wireless.wifi5g.key
uci commit wireless && wifi down && wifi up

关键几点:

  • encryption='wpa2+ccmp' —— EAP 在 OpenWrt 里就是这个值(不是 wpa2-eapwpa2-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
2
apk del wpad-basic-mbedtls && apk add wpad-mbedtls
wifi down && wifi up

替换包会重启 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.xmlmsEAPConfiguration blob
SSID 不广播,hostapd 看不出报错 wpad 是 basic 版没有 EAP authenticator `strings /usr/sbin/wpad

DC 上看 RADIUS 实时认证日志的 PowerShell 一行命令:

1
2
3
4
5
6
7
8
Get-WinEvent -FilterHashtable @{LogName='Security'; ID=6272,6273,6274; StartTime=(Get-Date).AddMinutes(-10)} `
| Sort-Object TimeCreated `
| ForEach-Object {
$kv = (([xml]$_.ToXml()).Event.EventData.Data
| Where-Object { $_.Name -in 'SubjectUserName','CallingStationID','ClientName','ReasonCode' }
| ForEach-Object { $_.Name + '=' + $_."#text" }) -join ' '
"$($_.TimeCreated.ToString('HH:mm:ss')) ID=$($_.Id) $kv"
}

事件 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 一处。