golang使用mTLS實(shí)現(xiàn)雙向加密認(rèn)證http通信
前言
假設(shè)一個(gè)場(chǎng)景,服務(wù)端部署在內(nèi)網(wǎng),客戶端需要通過(guò)暴露在公網(wǎng)的nginx與服務(wù)端進(jìn)行通信。為了避免在公網(wǎng)進(jìn)行 http 明文通信造成的信息泄露,nginx與客戶端之間的通信應(yīng)當(dāng)使用 https 協(xié)議,并且nginx也要驗(yàn)證客戶端的身份,也就是mTLS雙向加密認(rèn)證通信。
這條通信鏈路有三個(gè)角色:服務(wù)端、Nginx、客戶端。
- 服務(wù)端部署在內(nèi)網(wǎng),與nginx使用http通信。
- 客戶端在公網(wǎng),與nginx使用https通信,且雙向加密認(rèn)證。

服務(wù)端
服務(wù)端只使用http,所以這里用gin框架寫個(gè)簡(jiǎn)單的示例,返回客戶端一些基本的http信息,比如客戶端IP、請(qǐng)求方法、host等。
package main
import (
"log"
"net/http"
"time"
"github.com/gin-gonic/gin"
)
/* 中間件: 獲取api處理時(shí)長(zhǎng) */
func midElapsed(c *gin.Context) {
start := time.Now()
c.Next()
elapsed := time.Since(start)
log.Printf("API: %s, elapsed: %s", c.Request.URL.Path, elapsed)
}
/* 處理 GET / 請(qǐng)求 */
func f1(c *gin.Context) {
// 獲取客戶端IP
clientIP := c.ClientIP()
// 獲取請(qǐng)求方法
method := c.Request.Method
// 獲取協(xié)議
proto := c.Request.Proto
// 獲取host
host := c.Request.Host
// 請(qǐng)求Path
path := c.Request.URL.Path
log.Printf("客戶端IP: %s, 請(qǐng)求方法: %s, 協(xié)議: %s, host: %s, path: %s", clientIP, method, proto, host, path)
// 獲取請(qǐng)求頭
headers := c.Request.Header
for hk, hv := range headers {
log.Printf("header key: %s, value: %s", hk, hv)
}
// 獲取名為"mycookie"的cookie
var cookies []string
cookie, err := c.Cookie("mycookie")
if err != nil {
log.Printf("get cookie [mycookie] error: %s", err)
} else {
log.Printf("get cookie [mycookie]: %s", cookie)
cookies = append(cookies, cookie)
}
c.JSON(http.StatusOK, gin.H{
"clientIP": clientIP,
"method": method,
"proto": proto,
"host": host,
"headers": headers,
"cookies": cookies,
"path": path,
})
}
func main() {
r := gin.Default()
r.Use(midElapsed) // 全局引用計(jì)算耗時(shí)的中間件
r.GET("/", f1)
r.Run("0.0.0.0:8080")
}生成證書
1.生成ca根證書。生成過(guò)程會(huì)要求填寫密碼、CN、ON、OU等信息,記住密碼,填寫的信息也要和下一步openssl.cnf文件內(nèi)容一致。
openssl req -x509 -newkey rsa:4096 -keyout ca.key -out ca.crt -days 3650
2.新建并編輯文件openssl.cnf文件。req_distinguished_name中內(nèi)容按需填寫,DNS.1要替換成實(shí)際域名。
[req] req_extensions = v3_req distinguished_name = req_distinguished_name prompt = no [req_distinguished_name] countryName = CN stateOrProvinceName = Anhui localityName = Hefei organizationName = zhangsan commonName = qw.er.com [v3_req] subjectAltName = @alt_names [alt_names] DNS.1 = qw.er.com
3.生成服務(wù)端證書
openssl req -newkey rsa:2048 -nodes -keyout server.key -out server.csr -subj "/CN=qw.er.com" -config openssl.cnf
# 提示輸入ca私鑰的密碼
openssl x509 -req -in server.csr -out server.crt -CA ca.crt -CAkey ca.key -CAcreateserial -days 365 -extensions v3_req -extfile openssl.cnf
4.生成客戶端證書
openssl req -newkey rsa:2048 -nodes -keyout client.key -out client.csr -subj "/CN=qw.er.com" -config openssl.cnf
# 提示輸入ca私鑰的密碼
openssl x509 -req -in client.csr -out client.crt -CA ca.crt -CAkey ca.key -CAcreateserial -days 365 -extensions v3_req -extfile openssl.cnf
Nginx配置
nginx反向代理服務(wù)端的配置示例如下
server {
listen 80 ssl;
server_name qw.er.com;
ssl_certificate /home/atlas/apps/nginx/certs/qwer/server.crt;
ssl_certificate_key /home/atlas/apps/nginx/certs/qwer/server.key;
# 校驗(yàn)客戶端證書
ssl_verify_client on;
ssl_client_certificate /home/atlas/apps/nginx/certs/qwer/ca.crt;
location / {
proxy_set_header Host $host;
proxy_set_header X-real-ip $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://192.168.0.10:8080; # 服務(wù)端地址
}
}客戶端
以下示例使用命令行傳參的方式,指定tls證書文件和是否使用tls通信。
package main
import (
"crypto/tls"
"crypto/x509"
"flag"
"io"
"log"
"net/http"
"os"
"time"
)
var (
cafile = flag.String("cafile", "ca.crt", "ca 證書文件")
crtfile = flag.String("crtfile", "client.crt", "客戶端tls證書")
keyfile = flag.String("keyfile", "client.key", "客戶端tls私鑰")
url = flag.String("url", "http://127.0.0.1:8080", "url")
isTls = flag.Bool("tls", false, "是否使用tls")
)
func tlsClient(cafile, crtfile, keyfile string) *http.Transport {
// 加載證書和私鑰
clientCert, err := tls.LoadX509KeyPair(crtfile, keyfile)
if err != nil {
log.Fatalf("load key pair error: %s", err)
}
// 加載ca證書
clientCA, err := os.ReadFile(cafile)
if err != nil {
log.Fatalf("load ca cert error: %s", err)
}
// 創(chuàng)建根證書池并添加ca證書
caCertPool := x509.NewCertPool()
caCertPool.AppendCertsFromPEM(clientCA)
// 創(chuàng)建transport
tr := &http.Transport{
TLSClientConfig: &tls.Config{
Certificates: []tls.Certificate{clientCert},
RootCAs: caCertPool,
},
}
return tr
}
func main() {
flag.Parse()
req, err := http.NewRequest("GET", *url, nil)
if err != nil {
log.Fatalf("new request error: %s", err)
}
// 自定義HTTP請(qǐng)求頭
req.Header.Set("myheader1", "myheader1value123")
// 自定義一個(gè)cookie對(duì)象
cookie := &http.Cookie{
Name: "mycookie",
Value: "mycookievalue",
}
req.AddCookie(cookie)
client := &http.Client{
Timeout: time.Second * 5,
}
if *isTls {
client.Transport = tlsClient(*cafile, *crtfile, *keyfile)
}
resp, err := client.Do(req)
if err != nil {
log.Fatalf("get error: %s", err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
log.Fatalf("read error: %s", err)
}
log.Printf("body: %+v", string(body))
}Nginx配置
server {
listen 80 ssl;
server_name qw.er.com;
ssl_certificate /home/elifen/apps/nginx/certs/qwer/server.crt;
ssl_certificate_key /home/elifen/apps/nginx/certs/qwer/server.key;
ssl_verify_client on;
ssl_client_certificate /home/elifen/apps/nginx/qwer/ca.crt;
location / {
proxy_set_header Host $host;
proxy_set_header X-real-ip $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://192.168.0.10:8080;
}
}測(cè)試
這里需要先確保qw.er.com能被正常解析到nginx服務(wù)器,比如配置hosts文件或dns解析記錄。
go run main.go -cafile ./ca.crt -crtfile ./client.crt -keyfile ./client.key -url 'https://qw.er.com:80/' -tls
輸出示例
2023/08/07 17:34:51 body: {"clientIP":"192.168.0.11","cookies":["mycookievalue"],"headers":{"Accept-Encoding":["gzip"],"Connection":["close"],"Cookie":["mycookie=mycookievalue"],"Myheader1":["myheader1value123"],"User-Agent":["Go-http-client/1.1"],"X-Forwarded-For":["192.168.0.11"],"X-Real-Ip":["192.168.0.11"]},"host":"qw.er.com","method":"GET","path":"/","proto":"HTTP/1.0"}
到此這篇關(guān)于golang使用mTLS實(shí)現(xiàn)雙向加密認(rèn)證http通信的文章就介紹到這了,更多相關(guān)golang mTLS雙向加密認(rèn)證通信內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
淺析Go使用定時(shí)器時(shí)如何避免潛在的內(nèi)存泄漏陷阱
這篇文章來(lái)和大家一起探討一下Go?中如何高效使用?timer,特別是與select?一起使用時(shí),如何防止?jié)撛诘膬?nèi)存泄漏問(wèn)題,感興趣的可以了解下2024-01-01
GoLang中生成UUID唯一標(biāo)識(shí)的實(shí)現(xiàn)方法
UUID是讓分散式系統(tǒng)中的所有元素,都能有唯一的辨識(shí)信息,本文主要介紹了GoLang中生成UUID唯一標(biāo)識(shí)的實(shí)現(xiàn)方法,具有一定的參考價(jià)值,感興趣的可以了解一下2024-08-08
GO語(yǔ)言基礎(chǔ)入門第一個(gè)go程序解讀
這篇文章主要為大家介紹了GO語(yǔ)言基礎(chǔ)入門的第一個(gè)go程序解讀,下面來(lái)帶大家進(jìn)入Go語(yǔ)言世界helloworld的大門吧,有需要的朋友可以借鑒參考下,希望能夠有所幫助2021-11-11
go語(yǔ)言環(huán)境變量設(shè)置全過(guò)程
這篇文章主要介紹了go語(yǔ)言環(huán)境變量設(shè)置全過(guò)程,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-05-05
Go語(yǔ)言JSON編解碼神器之marshal的運(yùn)用
這篇文章主要為大家詳細(xì)介紹了Go語(yǔ)言中JSON編解碼神器——marshal的運(yùn)用,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2023-09-09
基于Go語(yǔ)言實(shí)現(xiàn)Base62編碼的三種方式以及對(duì)比分析
Base62 編碼是一種在字符編碼中使用62個(gè)字符的編碼方式,在計(jì)算機(jī)科學(xué)中,,Go語(yǔ)言是一種靜態(tài)類型、編譯型語(yǔ)言,它由Google開(kāi)發(fā)并開(kāi)源,本文給大家介紹了Go語(yǔ)言實(shí)現(xiàn)Base62編碼的三種方式以及對(duì)比分析,需要的朋友可以參考下2025-05-05
Golang基礎(chǔ)之函數(shù)使用(參數(shù)傳值)實(shí)例詳解
這篇文章主要為大家介紹了Golang基礎(chǔ)之函數(shù)使用(參數(shù)傳值)實(shí)例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-10-10

