HTTPS 已经是现代 Web 服务的标配了。没有 HTTPS 的网站,浏览器会显示"不安全",搜索引擎排名也会受影响。Gin 启用 HTTPS 其实很简单,但背后的一些细节值得了解。
HTTPS = HTTP + TLS。TLS(Transport Layer Security)是传输层安全协议,它的作用是:
要启用 HTTPS,你需要一个 SSL/TLS 证书。证书由证书颁发机构(CA)签发,证明你是域名的所有者。
本地开发可以用 OpenSSL 生成自签名证书:
# 生成私钥和证书(有效期365天)
openssl req -x509 -newkey rsa:2048 -nodes -keyout key.pem -out cert.pem -days 365
执行过程中会问你一些信息,随便填就行。生成的 key.pem 是私钥,cert.pem 是证书。
Let's Encrypt 提供免费的 SSL 证书,可以用 certbot 工具申请:
# 安装 certbot
sudo apt install certbot
# 申请证书
sudo certbot certonly --standalone -d yourdomain.com
证书会保存在 /etc/letsencrypt/live/yourdomain.com/ 目录下。
最简单的方式是使用 RunTLS 方法:
package main
import (
"github.com/gin-gonic/gin"
)
func main() {
router := gin.Default()
router.GET("/", func(c *gin.Context) {
c.String(200, "Hello HTTPS")
})
// 传入证书和私钥路径
router.RunTLS(":443", "cert.pem", "key.pem")
}
RunTLS 内部其实就是调用 http.Server 的 ListenAndServeTLS 方法。
如果需要更多控制,可以自己创建 http.Server:
package main
import (
"crypto/tls"
"log"
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
router := gin.Default()
router.GET("/", func(c *gin.Context) {
c.String(200, "Hello HTTPS")
})
// 自定义 TLS 配置
cfg := &tls.Config{
MinVersion: tls.VersionTLS12,
CurvePreferences: []tls.CurveID{tls.X25519, tls.CurveP256},
PreferServerCipherSuites: true,
CipherSuites: []uint16{
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
},
}
srv := &http.Server{
Addr: ":443",
Handler: router,
TLSConfig: cfg,
}
log.Fatal(srv.ListenAndServeTLS("cert.pem", "key.pem"))
}
这个配置指定了最低 TLS 版本为 1.2,并设置了一组安全的加密套件。
通常我们希望用户访问 HTTP 时自动跳转到 HTTPS:
package main
import (
"log"
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
router := gin.Default()
router.GET("/", func(c *gin.Context) {
c.String(200, "Hello HTTPS")
})
// 启动 HTTP 服务器,重定向到 HTTPS
go func() {
http.ListenAndServe(":80", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "https://"+r.Host+r.RequestURI, http.StatusMovedPermanently)
}))
}()
// 启动 HTTPS 服务器
log.Fatal(router.RunTLS(":443", "cert.pem", "key.pem"))
}
这样,访问 http://example.com 会自动跳转到 https://example.com。
使用 golang.org/x/crypto/acme/autocert 包可以自动申请和续期 Let's Encrypt 证书:
package main
import (
"crypto/tls"
"log"
"net/http"
"github.com/gin-gonic/gin"
"golang.org/x/crypto/acme/autocert"
)
func main() {
router := gin.Default()
router.GET("/", func(c *gin.Context) {
c.String(200, "Hello HTTPS with auto cert")
})
// 配置自动证书
manager := &autocert.Manager{
Prompt: autocert.AcceptTOS,
HostPolicy: autocert.HostWhitelist("example.com", "www.example.com"),
Cache: autocert.DirCache("certs"),
}
// TLS 配置
cfg := &tls.Config{
GetCertificate: manager.GetCertificate,
}
srv := &http.Server{
Addr: ":443",
Handler: router,
TLSConfig: cfg,
}
// HTTP 服务器用于 ACME 验证和重定向
go func() {
http.ListenAndServe(":80", manager.HTTPHandler(nil))
}()
log.Fatal(srv.ListenAndServeTLS("", ""))
}
这个方案的好处:
需要注意的是,服务器必须能被 Let's Encrypt 服务器访问到(80 和 443 端口),否则证书申请会失败。
普通 HTTPS 只验证服务器身份,双向 TLS 还要求客户端提供证书:
package main
import (
"crypto/tls"
"crypto/x509"
"io/ioutil"
"log"
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
router := gin.Default()
router.GET("/", func(c *gin.Context) {
c.String(200, "Hello mTLS")
})
// 读取 CA 证书
caCert, err := ioutil.ReadFile("ca.crt")
if err != nil {
log.Fatal(err)
}
caCertPool := x509.NewCertPool()
caCertPool.AppendCertsFromPEM(caCert)
// TLS 配置
cfg := &tls.Config{
ClientCAs: caCertPool,
ClientAuth: tls.RequireAndVerifyClientCert,
}
srv := &http.Server{
Addr: ":443",
Handler: router,
TLSConfig: cfg,
}
log.Fatal(srv.ListenAndServeTLS("server.crt", "server.key"))
}
客户端访问时需要提供证书:
curl --cert client.crt --key client.key https://example.com
mTLS 常用于内部服务之间的认证,比用户名密码更安全。
cfg := &tls.Config{
MinVersion: tls.VersionTLS12, // 最低 TLS 1.2
}
TLS 1.0 和 1.1 已经被认为不安全,应该禁用。
cfg := &tls.Config{
CipherSuites: []uint16{
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
},
}
避免使用 RC4、DES、CBC 模式等不安全的加密算法。
通过响应头告诉浏览器始终使用 HTTPS:
router.Use(func(c *gin.Context) {
c.Header("Strict-Transport-Security", "max-age=31536000; includeSubDomains")
c.Next()
})
如果使用 DH 密钥交换,应该使用 2048 位以上的参数:
openssl dhparam -out dhparam.pem 2048
// Go 1.8+ 默认使用安全的 DH 参数,通常不需要额外配置
可以用 openssl 测试服务器的 TLS 配置:
# 测试连接
openssl s_client -connect example.com:443
# 测试证书
openssl s_client -connect example.com:443 -showcerts
# 测试支持的协议和加密套件
nmap --script ssl-enum-ciphers -p 443 example.com
也可以用在线工具如 SSL Labs(https://www.ssllabs.com/ssltest/)测试。
Gin 启用 HTTPS 很简单,关键是:
HTTPS 不仅能保护用户数据,还能提升网站可信度和搜索排名,是现代 Web 服务的必备配置。