当前位置:首页 > 文章列表 > Golang > Go教程 > Go URL 怎样编码包含特殊字符的用户信息

Go URL 怎样编码包含特殊字符的用户信息

来源:17golang原创 2026-10-14 19:07:51 0浏览 收藏

Go 里如果要把带有 @、冒号、斜杠、空格或中文的用户名放进 URL,最稳妥的做法不是把字符串直接拼到 https:// 后面,而是交给 net/url 的 User 或 UserPassword 构造 Userinfo,最后调用 URL.String() 统一序列化。这样特殊字符会按用户信息的位置进行百分号编码,不会误被当成 host、端口或路径分隔符。

核心写法是:先用 url.User(username) 或 url.UserPassword(username, password) 设置 URL.User,再调用 u.String()。不要拿 PathEscape 或 QueryEscape 代替 Userinfo 的编码。
  • 只需要用户名时使用 url.User,它不会设置密码。
  • 确实要表达遗留 URL 中的用户名和密码时使用 url.UserPassword。
  • 包含特殊字符的字段不要手工拼接 @、: 和百分号转义。
  • URL 中携带密码是遗留兼容能力,新的系统应优先使用 HTTPS、Header 或专门的认证流程。

一、Userinfo 在 URL 中究竟处于什么位置

一个绝对 URL 可以抽象为 scheme://[userinfo@]host/path?query。其中 Userinfo 位于 host 之前,通常由 username 或 username:password 组成;它结束于分隔 host 的 @。这意味着用户名或密码里的 @、冒号、斜杠、问号和井号都不能靠肉眼直接拼进去,否则解析器很难判断它们究竟是数据还是 URL 结构。

Go URL 组成与 Userinfo 构造关系的静态说明图
图1:Go URL 组成与 Userinfo 构造关系的静态说明图,不是运行截图。

net/url 把这部分封装成不可变的 Userinfo 值,再挂到 url.URL.User 字段上。调用 URL.String 时,标准库知道当前字段处于用户信息位置,会采用对应的转义规则;它和路径段、查询参数的规则并不相同。

二、用 User 或 UserPassword 构造用户信息

先看一个同时包含特殊字符的例子。示例里的值只是演示数据,不代表应该把真实密码写进代码、日志或配置仓库。

package main

import (
    "fmt"
    "net/url"
)

func main() {
    // UserPassword 负责把用户名和密码封装成 URL 的 Userinfo。
    userinfo := url.UserPassword("alice@team", "p:ss/word?中文")

    u := &url.URL{
        Scheme: "https",
        Host:   "example.com:8443",
        Path:   "/v1/profile",
        User:   userinfo,
    }

    // String 会按照 Userinfo 的位置编码特殊字符并生成完整 URL。
    fmt.Println(u.String())
}

这个对象的关键不是把编码结果硬编码出来,而是让字段保持原始语义,直到最后由 String 生成传输形式。输出会把用户名中的 @、密码中的冒号、斜杠、问号和中文转换成不会破坏 Userinfo 边界的编码片段。

如果场景只需要用户名,不应为了凑格式传入一个空密码。使用 url.User 可以明确表达“密码不存在”:

package main

import (
    "fmt"
    "net/url"
)

func main() {
    // User 只设置用户名,生成的 Userinfo 不包含密码分隔符。
    u := &url.URL{
        Scheme: "https",
        Host:   "example.com",
        User:   url.User("reader@example"),
    }

    fmt.Println(u.String())
}

三、为什么不应该手工拼接 URL

直接拼接看似简单,却把多个字段的语义混在了一起。例如把 alice@team 当作用户名直接放在 https:// 后面,未转义的 @ 可能被当成 Userinfo 的结束符;密码里的冒号则可能让读者误以为它是用户名和密码的分隔符。更复杂的字符还会进入路径、查询或片段的解析范围。

package main

import (
    "fmt"
    "net/url"
)

func main() {
    username := "alice@team"
    password := "p:ss/word?中文"

    // 这里仅用于对比:手工拼接无法表达每个字符属于哪个 URL 组件。
    manual := "https://" + username + ":" + password + "@example.com"

    // 结构化构造让标准库按 Userinfo 规则处理边界。
    structured := (&url.URL{
        Scheme: "https",
        Host:   "example.com",
        User:   url.UserPassword(username, password),
    }).String()

    fmt.Println("手工拼接:", manual)
    fmt.Println("结构化构造:", structured)
}

手工字符串在某些简单输入上可能“看起来能用”,但它没有把原始字段与 URL 结构分开。只要输入来自用户、配置文件或外部系统,就不应假设用户名和密码永远只包含字母数字。

四、Parse 后怎样取回用户名和密码

编码只解决了生成 URL 的问题。收到或保存一个 URL 后,可以先调用 url.Parse,再通过 u.User.Username() 和 u.User.Password() 读取语义字段。Password 返回两个值:密码内容,以及密码是否存在。

Go Userinfo 特殊字符编码与 Parse 回读关系的静态说明图
图2:Userinfo 特殊字符编码与解析回读关系的静态说明图,不是运行截图。
package main

import (
    "fmt"
    "log"
    "net/url"
)

func main() {
    raw := "https://alice%40team:p%3Ass%2Fword%3F%E4%B8%AD%E6%96%87@example.com/v1/profile"

    u, err := url.Parse(raw)
    if err != nil {
        // 解析失败时不要继续读取 User,避免把不完整地址当成可信输入。
        log.Fatal(err)
    }
    if u.User == nil {
        // Userinfo 是可选组件,没有它时应区分“没有用户信息”和“空用户名”。
        log.Fatal("URL 不包含 Userinfo")
    }

    username := u.User.Username()
    password, hasPassword := u.User.Password()

    fmt.Println("用户名:", username)
    fmt.Println("密码是否存在:", hasPassword)
    fmt.Println("密码长度:", len(password))
}

读取密码后不要直接打印内容。示例只输出密码长度,是为了说明字段已经恢复,同时避免把敏感值放进日志。对于只用 url.User 构造的地址,Password 的第二个返回值是 false;即使用户名为空,也不能用空字符串代替这个存在标志。

五、Userinfo、路径和查询参数不要混用转义函数

net/url 提供了多种转义入口,但它们服务于不同位置:

数据位置推荐 API不要替代它的 API原因
URL 用户信息User、UserPasswordQueryEscape需要遵守 username/password 与 @ 分隔边界
单个路径段PathEscapeUserPassword路径中的斜杠和分段语义不同
查询参数值url.Values.EncodePathEscape查询使用键值对与 &、= 分隔

例如查询参数应先放进 url.Values,而不是把同一段用户名编码后同时塞到 Userinfo 和 query 中:

package main

import (
    "fmt"
    "net/url"
)

func main() {
    u := &url.URL{
        Scheme: "https",
        Host:   "example.com",
        User:   url.User("reader@example"),
    }

    query := u.Query()
    // Values.Encode 会处理查询键和值,不会改变 Userinfo 的编码规则。
    query.Set("filter", "a/b + 中文")
    u.RawQuery = query.Encode()

    fmt.Println(u.String())
}

六、采用 UserPassword 前要知道的安全边界

Go 官方文档明确提醒,UserPassword 主要是为遗留网站保留的能力;把认证信息直接放进 URI 在很多情况下都存在安全风险。即使使用 HTTPS,完整 URL 仍可能被代理、访问日志、错误信息或监控标签记录,因此“正确编码”不等于“适合承载新系统密码”。

如果协议允许,新的系统应把认证信息放在明确的认证机制中,例如 HTTPS 请求头、短期令牌或专门的客户端认证流程。本文的代码只演示 URL 语义和转义边界,不提供绕过认证或隐藏凭据的做法。

结论

Go URL 中的特殊字符是否安全,取决于它们被放进了哪个组件。Userinfo 使用 url.User 或 url.UserPassword,完整地址使用 URL.String 生成,解析时使用 Username 与 Password 读取;路径和查询参数则分别使用自己的 API。把字段结构交给 net/url,比先拼字符串再补转义更容易维护,也更不容易把数据误当成 URL 分隔符。

相关问题

用户名里有 @,应该手动写成 %40 吗?

不需要。把原始用户名交给 url.User 或 url.UserPassword,最终调用 String 时由标准库按 Userinfo 规则编码。

只想设置用户名,能传一个空密码吗?

更清晰的写法是使用 url.User。它表达的是没有设置密码,解析时 Password 的存在标志也会保持正确。

能不能用 QueryEscape 编码用户名和密码?

不建议。QueryEscape 面向查询字符串,Userinfo 需要另一套组件边界;使用错误 API 可能让空格、加号或分隔符产生不符合预期的结果。

UserPassword 适合新的登录系统吗?

通常不适合。它主要用于遗留 URL 兼容;新系统应使用 HTTPS 配合合适的 Header、令牌或认证协议,避免把密码暴露在可记录的 URL 中。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
Go ServeContent 怎样处理范围请求Go ServeContent 怎样处理范围请求
上一篇
Go ServeContent 怎样处理范围请求
Go HTTP Server 怎样关闭 Keep-Alive 连接复用
下一篇
Go HTTP Server 怎样关闭 Keep-Alive 连接复用
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    472次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    531次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    551次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    503次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    318次使用