frp内网穿透配上TOKEN鉴权这套组合我在三四个不同规模的场景里都用过家里NAS的远程访问、公司测试环境给外部合作方做临时回调、还有给几个小客户做的内网Web服务发布。刚开始玩frp的时候我也是那个把frps裸奔在公网7000端口上的人直到某天早上起来发现日志里塞满了来自世界各地的登录尝试才意识到一个没有TOKEN的内网穿透服务端本质上就是把自家大门钥匙挂在了门把手上。这篇内容就把frp的TOKEN配置从原理到落地完整讲一遍包括新旧版本配置文件的差异、TOKEN怎么生成才算够硬、轮换时怎么不把现有连接打断以及那些日志里看着像TOKEN问题、实际根本不是TOKEN问题的坑。不管你是第一次接触frp内网穿透还是已经跑了一段时间想给服务端补上鉴权下面这些内容都能直接拿去用。1. 先想清楚frp为什么要配TOKEN1.1 一条反向隧道到底是怎么建立的很多人用frp用了很久其实没搞明白它和普通端口转发差在哪。普通端口转发是外面的人主动连到你开放的端口而frp做的是反向连接frpc客户端主动从内网发起连接连到公网的frps服务端然后把这条连接保持住之后外部访问frps某个端口的流量就顺着这条已经建好的隧道回推到内网的frpc再由frpc转发到本地真正的服务上。这个模型最大的好处是内网不需要任何入站端口开放不需要动路由器、不需要公网IP家用宽带的NAT后面照样能跑。但代价也很明显frps成了整条链路的唯一入口谁能连上frps并且通过它的身份校验谁就能用你开放出来的所有隧道。所以frps的鉴权不是锦上添花的功能而是架构层面的必需品。我在第一次部署时就吃过这个亏。当时frps跑在一台小机器上bindPort用了默认7000配置里连token字段都没写。frp在没配置token的时候服务端默认不做任何登录校验——任何人只要知道你的IP和端口写一个指向你的frpc配置就能把自己的服务挂到你的服务端上甚至能通过remotePort占用你想用的端口。这不是危言耸听是真实发生过的默认行为。1.2 不配TOKEN时暴露面究竟有多大我们把不配TOKEN这个状态的攻击面拆开看会清楚很多登录无门槛frps在auth.method未启用或token为空时不会拒绝任何客户端登录任何能访问bindPort的主机都能建立控制连接。端口抢夺先到先得。攻击者抢先申请了remotePort 6000你自己的frpc再想用6000就会被拒绝或者被他改成别的端口服务直接不可用。流量镜像风险如果攻击者用同样的remotePort申请了同名隧道部分配置下会造成转发混乱用户请求可能被引到错误的后端。信息泄露frps的admin面板如果不设密码又对外可达配置、连接状态、隧道列表全都能被读到这等于把内网结构图直接贴出去。我后来做的第一件事就是把bindPort换掉第二件事是加上TOKEN第三件事是把admin面板限制在127.0.0.1。这三步里面TOKEN是性价比最高的那个——改一行配置拦掉99%的自动化扫描。1.3 TOKEN鉴权与其他方案的取舍对比frp本身支持几种身份校验方式选哪种取决于你的规模和运维能力。方案配置复杂度适合场景主要短板token静态密钥极低两端各一行个人、小团队、内网自用密钥泄露即失守轮换稍麻烦oidc对接身份平台中高需要额外服务多人协作、企业统一登录依赖外部身份服务可用性仅靠防火墙白名单低客户端IP固定的场景家用宽带IP会变维护成本高TLS token低全部场景推荐默认开启只解决传输加密不解决身份我的实际建议是TOKEN TLS 防火墙白名单三者叠加。TOKEN负责身份TLS负责传输不被中间设备窥探防火墙负责把可达范围压缩到最小。这三层加起来配置量也就十来行但安全水位完全不是一个量级。2. frp的TOKEN机制拆解它到底在校验什么2.1 配置字段的新旧版本对照frp在v0.52之后逐步把配置文件从INI切换到了TOML这个变化让很多人照着老教程配置时报错因为字段名全变了。先把对照关系理清楚作用旧版INI新版TOML服务端监听端口bind_portbindPort鉴权方式authentication_methodauth.method鉴权密钥tokenauth.token客户端服务端地址server_addrserverAddr客户端服务端端口server_portserverPort开启TLStls_enabletransport.tls.enable日志级别log_levellog.level管理面板地址dashboard_addrwebServer.addr一个很实用的判断方法如果你手上的frp版本执行frps --version输出是0.52及以上优先写TOML旧的INI虽然在一段时间内还兼容但新特性只在TOML下完整支持。我在给客户做部署时统一按版本≥0.52走TOML来写文档省得后面升级时再返工。2.2 登录握手阶段发生了什么TOKEN在frp里的传递逻辑其实很朴素frpc连上frps的bindPort后第一条消息就是登录请求Login里面带着一个privilege_key字段这个字段的值就是你配置的token。frps收到后和自己配置里的token做比对一致就返回登录成功双方进入工作状态不一致就直接断开并在服务端日志里留下一条失败的记录。这里有几个细节值得注意第一TOKEN是明文比对的frp本身对token不做哈希处理。所以token一旦离开你的机器就等同于泄露。这也是为什么必须配TLS——不开TLS的话这条登录消息在链路上是裸奔的。第二TOKEN是全局的。一个frps实例在默认配置下只有一把共享密钥所有frpc用同一把。这带来一个管理上的麻烦想单独吊销某个客户端的访问权限只能换掉全局token然后所有客户端都得跟着改。要真正做到按客户端隔离要么起多个frps实例不同端口、不同token要么上OIDC。第三校验只发生在建立连接的瞬间。连接建好之后你再改服务端的token已经连上的frpc不会立刻掉线它们会继续工作直到某次重连才会失败。这一点在轮换token时非常重要后面第4节会专门讲。2.3 TOKEN和TLS不是同一件事别混着理解我见过不少人以为我配了token流量就是加密的这是个很危险的误解。TOKEN解决的是你是谁的问题是一个身份凭证TLS解决的是路上有没有人偷看的问题是传输层加密。两者完全没有替代关系。不开TLS的时候token本身在登录包里是明文的抓包工具一眼就能看到开了TLS但没配token任何知道地址的人还是能连上来。正确的做法是两者都开。新版TOML下客户端加transport.tls.enable true服务端默认就支持TLS协商。注意transport.tls.enable在客户端是启用TLS连接在服务端有时会配合transport.tls.force true强制所有客户端必须走TLS这样能防住那些忘了开TLS的老客户端。我在给外部合作方开隧道时服务端一律加上force不给自己留侥幸空间。2.4 TOKEN的三种落地方式与选择实际部署中token有三种常见的注入方式各有各的适用场合。方式一写在配置文件里。最简单也最常见。缺点是密钥和配置混在一起配置文件一旦被提交到代码仓库或者被其他用户读到密钥就出去了。用这种方式的话一定要给配置文件设好权限chmod 600是最低要求。方式二通过环境变量注入。较新版本的frp支持用环境变量覆盖配置项命名规则大致是前缀 字段名大写 下划线连接比如服务端的token可以尝试用FRPS_AUTH_TOKEN这类形式传入。这种做法特别适合容器场景因为镜像里可以不带任何密钥密钥在运行时由编排系统注入。方式三tokenSource从文件读取。服务端可以配置auth.method token配合auth.tokenSource.type file让frps在每次校验时从指定文件读取token内容。这个方式的价值在于轮换时不需要重启服务你只要改动那个文件下次有客户端重连就会用新值校验。具体字段名和支持情况跟版本有关配置前建议拿frps --help或者对应版本的文档确认一遍。我的选择习惯是个人服务器用方式一够用且直观容器化部署用方式二需要给多个客户轮换密钥的场景用方式三。3. 动手实操服务端与客户端的TOKEN配置全流程3.1 生成一个扛得住暴力猜测的TOKEN先说结论不要手敲token也不要用123456或者mypassword2024这类东西。这玩意儿是要放在公网端口上被人反复尝试的强度不够等于没配。我一般用系统自带的工具生成两条命令任选# 生成 32 字节的随机十六进制字符串64 个字符 openssl rand -hex 32 # 生成 base64注意可能包含 / 等字符 openssl rand -base64 32为什么我更推荐十六进制因为base64里会出现、/、这些字符写进某些配置文件、shell变量或者URL参数时容易触发转义问题尤其在用脚本自动注入环境变量的时候一个小符号就能让你排查半小时。十六进制只有0-9和a-f放到哪儿都安全。生成出来后先把它存到一个只有自己能访问的地方比如~/.frp-token权限设成600。接下来服务端和客户端都从这里取避免两边手抄抄错。3.2 服务端的完整配置与systemd托管假设你手上是0.52以上的版本新建/etc/frp/frps.tomlbindPort 7000 auth.method token auth.token 把刚才生成的64位十六进制串粘到这里 # 强制客户端走TLS transport.tls.force true # 管理面板只监听本地 webServer.addr 127.0.0.1 webServer.port 7500 webServer.user admin webServer.password 另一个强密码 # 日志 log.to /var/log/frps.log log.level info log.maxDays 7几个参数的选择理由说明一下。bindPort我没有用7000实际部署时换成了一个高位端口。默认端口是被扫描器重点照顾的对象换端口不能替代鉴权但能显著降低噪声日志量。transport.tls.force true的作用是拒绝所有没开TLS的客户端登录。开了这个之后客户端的transport.tls.enable必须为true否则连不上日志里会有明确的TLS相关提示。webServer.addr设成127.0.0.1是关键。管理面板里有配置快照和实时连接列表暴露在公网等于把内网拓扑送人。需要看的时候用SSH端口转发到本地浏览器访问就行。接着用systemd托管新建/etc/systemd/system/frps.service[Unit] Descriptionfrp server Afternetwork.target [Service] Typesimple Userfrp Restartalways RestartSec5 ExecStart/usr/local/bin/frps -c /etc/frp/frps.toml [Install] WantedBymulti-user.target这里有个容易忽略的点Userfrp需要一个真实存在的低权限用户不要用root跑。frps本身不需要任何特权端口7000不是1024以下的端口用普通用户完全够。执行sudo systemctl daemon-reload sudo systemctl enable --now frps sudo systemctl status frps3.3 客户端的配置与开机自启客户端这边/etc/frp/frpc.tomlserverAddr 你的公网地址 serverPort 7000 auth.method token auth.token 和服务端完全一致的token transport.tls.enable true [[proxies]] name nas-web type tcp localIP 127.0.0.1 localPort 5000 remotePort 15000TOKEN部分必须一字不差。我遇到最多的失败原因就是这里服务端的token是a1b2c3客户端复制的时候多带了一个空格或者末尾换行被一起复制进去了。配置文件里字符串结尾的空白字符不会被自动去掉比对就是失败。transport.tls.enable true是配合服务端force的必选项客户端不开TLS服务端会直接拒绝。客户端在Linux上同样建议用systemd托管Windows上则可以用任务计划程序或者nssm包装成服务macOS上可以用launchd的plist。原则都一样要能开机自启要能崩溃自动拉起。frpc断线后虽然是自动重连的但进程本身挂了就没人拉它了。3.4 用日志确认鉴权真的生效了配完不要急着开业务先确认鉴权链路是对的。服务端日志级别临时调成debuglog.level debug重启后客户端连上时你应该能看到类似login to server success的记录。如果TOKEN不匹配会看到明确的失败提示关键词通常包含authorization failed或者token in login doesnt match token from configuration这类描述。看到后面这句基本可以直接定位为两端token不一致。还有一种情况是客户端日志里一直刷重连但服务端日志里连登录请求都看不到。这通常不是TOKEN问题而是网络根本没通——端口没放行、防火墙拦了、或者serverAddr写错了。排查顺序永远是从能不能连上到能不能通过校验别上来就怀疑密钥。我在实际排查时会用这样一条命令快速区分# 从客户端机器测服务端端口可达性 nc -vz 你的公网地址 7000连不上就没必要往下看TOKEN了。3.5 Docker场景下的配置写法容器化部署时服务端镜像一般是frps的官方或社区镜像挂载配置即可docker run -d \ --name frps \ --restart always \ -p 7000:7000 \ -v /opt/frp/frps.toml:/etc/frp/frps.toml \ -e FRPS_AUTH_TOKEN生成的token \ snowdreamtech/frps注意这里有个优先级问题环境变量和配置文件哪个生效取决于版本的实现细节。我的做法是二选一绝不两边都写否则哪天改了一处不生效你会怀疑人生。如果是用环境变量注入密钥配置文件里就不要写auth.token这一行。客户端的Docker部署同理但要注意localIP。容器里的127.0.0.1指的是容器自己不是你宿主机。要转发到宿主机的服务得用宿主机的内网IP或者host.docker.internal部分平台支持也可以让frpc直接走host网络模式。这个坑我在第一次容器化时踩得很实配置看着全对就是访问不到本地服务。4. TOKEN的进阶玩法轮换、多端与热更新4.1 TOKEN轮换的正确姿势密钥用久了就该换尤其是有人离职、有机器报废、或者你怀疑某次日志被看到了。但轮换最怕的是换完所有隧道全断。我的实际做法分两档。如果只有一两个客户端直接粗暴来先在服务端改token重启frps然后挨个改客户端重启frpc。中间会有几分钟不可用适合在维护窗口里做。如果客户端多、不能停机就得利用frp校验的时机特性——校验只发生在建立连接时。这意味着你可以这样操作服务端换token但先不重启改成从文件读取tokenSource方式文件内容先保持旧值。逐个把客户端改成新token此时它们连的还是旧值先不要重启客户端。等所有客户端配置都改完一次性把服务端token文件内容换成新值。重启所有客户端它们会用新token重新登录服务端读到的也是新值。这个流程能把不可用窗口压到秒级。缺点是步骤多容易漏所以我会在改之前先列一张客户端清单改一个勾一个。4.2 tokenSource把密钥从配置文件里挪出去把token写进配置文件有个长期隐患做配置备份、提交到版本库、或者截图分享的经验里密钥很容易跟着一起出去。用tokenSource从独立文件读取就能让配置文件变成可公开的内容密钥单独存在一个600权限的文件里。配置思路大致是这样服务端声明鉴权方式为token并指定一个文件源路径指向一个只包含token字符串的文件。文件里就一行内容不要有空行和多余空格。这样每次校验都从文件实时读取改文件就等于改密钥不用重启进程。这个特性对版本有要求建议配置前用frps --help确认参数存在或者直接查你手上版本对应的文档。不确定的情况下退回配置文件里写token 严格文件权限也是完全可以接受的方案没必要为了优雅引入不确定性。4.3 多客户端隔离该怎么设计frp的token是全局的这是它的设计定位决定的——它把自己定位成一个轻量的反向代理工具不是多租户平台。所以要实现不同客户端用不同密钥的隔离只能绕方案一多实例。起多个frps进程监听不同端口各配各的token。比如给家里的服务用7000端口和tokenA给合作方用7001端口和tokenB。隔离彻底管理成本是每个实例都要单独维护。方案二OIDC。服务端把鉴权方式切到OIDC对接统一的身份平台。适合内部有现成身份体系的团队个人用户没必要。方案三共享token但用STCP/XTCP收敛暴露面。这是很多人忽略的一点不是所有隧道都需要开在公网端口上。用type stcp的隧道服务端不分配公开端口只有持有相同secretKey的访问方客户端才能连上。这样即使token泄露别人也摸不到你的内网服务。我现在的默认打法是全局token负责服务端准入STCP的secretKey负责具体隧道的访问控制两层叠加。真正需要暴露给不完全信任对象的服务一律走STCP不给公网端口。4.4 用admin API做热更新frps的管理接口除了一堆状态查询还支持通过PUT提交配置做局部热更新。大致长这样curl -u admin:面板密码 \ -X PUT \ -H Content-Type: application/json \ -d {auth: {token: 新的token值}} \ http://127.0.0.1:7500/api/config需要注意的是热更新支持哪些字段是跟版本相关的不是所有配置都能这么改。而且改完之后已连接的客户端不会立刻掉线它们仍持有旧的校验结果直到下次重连才会失败——所以热更新不等于立即生效别指望用这个操作来实现紧急封禁。要紧急切断某个客户端更快的做法是直接把对应隧道从服务端配置里摘掉或者干脆重启frps。5. 报错速查TOKEN相关问题的排查路径5.1 三步定位法遇到连不上我固定按这个顺序走能省掉大量瞎猜第一步客户端能不能到达服务端端口。用nc -vz或者telnet测bindPort。不通就查防火墙、安全组、端口是否被占用跟TOKEN无关。第二步服务端日志有没有收到登录请求。日志级别开到debug重启客户端看服务端有没有新的登录记录。如果完全没有说明连接在更底层就被掐断了如果有记录但报鉴权失败那才是TOKEN问题。第三步对比两端token的字节级内容。不是看着一样是真的做对比# 分别取两端配置里的token值直接比对 grep -o auth.token * *[^]* /etc/frp/frps.toml grep -o auth.token * *[^]* /etc/frp/frpc.toml这一步能抓出绝大多数看着一样其实不一样的情况多余空格、全角引号、复制时带上的不可见字符。5.2 常见报错对照表现象/报错关键词大概率原因处理方式token in login doesnt match两端token不一致逐字节比对注意空白字符authorization failed服务端启用了鉴权客户端没提供检查客户端是否漏了auth.token客户端一直重连服务端无日志网络不通或端口被拦先用nc测连通性tls handshake failed一端开TLS一端没开服务端forcetrue时客户端必须开登录成功但业务访问不通隧道配置问题非鉴权问题检查localIP/localPort和remotePort服务端启动就报配置错误配置文件用了错误格式确认版本TOML和INI不要混用改了配置但行为没变进程没重启或没重载systemctl restart别只改文件5.3 那些看起来像TOKEN问题其实不是的情况这一节是我踩坑攒下来的比报错表更值钱。情况一localIP写错。客户端登录成功日志一切正常就是访问不到服务。八成是localIP 127.0.0.1配在了容器里实际服务跑在宿主机上。情况二remotePort被占用。服务端会拒绝分配已经被使用的端口客户端日志里会有端口相关的失败提示。这种报错里也会出现failed很容易被误认为鉴权失败。看关键词别只看failed这个词。情况三服务端时间不对。某些鉴权和加密流程对时间敏感机器时间漂移太大会出各种莫名其妙的失败。部署完顺手timedatectl看一眼成本很低。情况四多个frpc进程抢同一个配置。我见过同一台机器上既有systemd服务又有手动nohup启动的frpc两个进程抢同一个隧道名行为诡异。排查时先ps aux | grep frpc确认只有一个实例。情况五中间设备做了拦截。某些网络环境会对长连接做处理导致连接在建立后不久被切断。表现是反复重连日志里还带着TOKEN相关字样。这种情况要看重连的时间间隔是不是很规律规律就多半是链路问题而非配置问题。6. 实操心得与上线前的检查清单6.1 我在真实环境里踩过的三个坑第一个坑以为改了配置文件就生效了。有次在服务端改完token客户端也跟着改了两边重启之后还是连不上。折腾了二十分钟才发现服务端那次只执行了systemctl reload而frps并不支持真正的配置热重载reload等于没做事进程里跑的还是旧配置。从那以后我形成了条件反射改完frp配置一律restart并在改完后用日志确认启动时读到的是新值。第二个坑token里带了换行。用脚本自动注入密钥的时候如果读取文件时没去掉末尾换行注入进去的字符串末尾就多一个\n。肉眼对比完全看不出区别配置里看着一模一样。后来我在所有取密钥的脚本里统一加了一步去除空白字符的处理问题再没出现过。第三个坑把所有隧道都开成公网端口。早期图省事能开的都开成tcp类型方便随时访问。直到某天发现有个服务被扫到了才回头把大部分隧道改成了stcp。现在我的原则很明确能走stcp的一律走stcp只有明确需要给外部访问的服务才开公网端口并且开出去的服务本身还要有自己的登录鉴权不能只靠frp这一层。6.2 上线前的一张自查表每次新部署或者大改动我都会过一遍这张表花不了几分钟但能挡住绝大多数返工bindPort是否已从默认值改掉auth.method是否为tokenauth.token是否为空空token在有些配置下等同于不校验两端token是否做过字节级比对而不是肉眼比对客户端transport.tls.enable是否与服务端的force设置匹配管理面板是否只监听127.0.0.1并且设了强密码配置文件权限是否收紧到600并且不归属其他用户服务端进程是否用非root用户运行所有frpc是否都有开机自启和崩溃重启机制日志级别是否已从debug调回infodebug长期开着会吃掉不少磁盘隧道清单是否梳理过一遍可收敛到stcp的都已经收敛。TOKEN这一层配好之后frp这套方案基本就能安心跑起来了。我个人最深的体会是内网穿透这类工具功能实现只占三成剩下七成都在暴露面控制上。同样的frp配置方式差几行安全水位差一个数量级。真正省事的做法不是先跑起来再说而是在第一次部署时就把token、TLS、面板限制、隧道类型这四件事一次做对后面基本不需要再回头补。
阅读完成 · 觉得有帮助?