golang使用mTLS實(shí)現(xiàn)雙向加密認(rèn)證http通信
前言
假設(shè)一個(gè)場(chǎng)景,服務(wù)端部署在內(nèi)網(wǎng),客戶端需要通過暴露在公網(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根證書。生成過程會(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)文章
Golang反射獲取結(jié)構(gòu)體的值和修改值的代碼示例
這篇文章主要給大家介紹了golang反射獲取結(jié)構(gòu)體的值和修改值的代碼示例及演示效果,對(duì)我們的學(xué)習(xí)或工作有一定的幫助,感興趣的同學(xué)可以參考閱讀本文2023-08-08go責(zé)任鏈行為型設(shè)計(jì)模式Chain?Of?Responsibility
這篇文章主要為大家介紹了go行為型設(shè)計(jì)模式之責(zé)任鏈Chain?Of?Responsibility使用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-12-12淺談Go語言中的結(jié)構(gòu)體struct & 接口Interface & 反射
下面小編就為大家?guī)硪黄獪\談Go語言中的結(jié)構(gòu)體struct & 接口Interface & 反射。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-07-07golang?gorm的Callbacks事務(wù)回滾對(duì)象操作示例
這篇文章主要為大家介紹了golang?gorm的Callbacks事務(wù)回滾對(duì)象操作示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪2022-04-04