[golang]使用mTLS双向加密认证http通信

简介: [golang]使用mTLS双向加密认证http通信

前言

假设一个场景,服务端部署在内网,客户端需要通过暴露在公网的nginx与服务端进行通信。为了避免在公网进行 http 明文通信造成的信息泄露,nginx与客户端之间的通信应当使用 https 协议,并且nginx也要验证客户端的身份,也就是mTLS双向加密认证通信。

这条通信链路有三个角色:服务端、Nginx、客户端。

  • 服务端部署在内网,与nginx使用http通信。
  • 客户端在公网,与nginx使用https通信,且双向加密认证。

服务端

服务端只使用http,所以这里用gin框架写个简单的示例,返回客户端一些基本的http信息,比如客户端IP、请求方法、host等。

package main
import (
  "log"
  "net/http"
  "time"
  "github.com/gin-gonic/gin"
)
/* 中间件: 获取api处理时长 */
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 / 请求 */
func f1(c *gin.Context) {
  // 获取客户端IP
  clientIP := c.ClientIP()
  // 获取请求方法
  method := c.Request.Method
  // 获取协议
  proto := c.Request.Proto
  // 获取host
  host := c.Request.Host
  // 请求Path
  path := c.Request.URL.Path
  log.Printf("客户端IP: %s, 请求方法: %s, 协议: %s, host: %s, path: %s", clientIP, method, proto, host, path)
  // 获取请求头
  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) // 全局引用计算耗时的中间件
  r.GET("/", f1)
  r.Run("0.0.0.0:8080")
}

生成证书

  1. 生成ca根证书。生成过程会要求填写密码、CN、ON、OU等信息,记住密码,填写的信息也要和下一步openssl.cnf文件内容一致。
openssl req -x509 -newkey rsa:4096 -keyout ca.key -out ca.crt -days 3650
  1. 新建并编辑文件openssl.cnf文件。req_distinguished_name中内容按需填写,DNS.1要替换成实际域名。
[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
  1. 生成服务端证书
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
  1. 生成客户端证书
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反向代理服务端的配置示例如下

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;
    
    # 校验客户端证书
    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; # 服务端地址
    }
}

客户端

以下示例使用命令行传参的方式,指定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)
  }
  // 创建根证书池并添加ca证书
  caCertPool := x509.NewCertPool()
  caCertPool.AppendCertsFromPEM(clientCA)
  // 创建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请求头
  req.Header.Set("myheader1", "myheader1value123")
  // 自定义一个cookie对象
  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;
    }
}

测试

这里需要先确保qw.er.com能被正常解析到nginx服务器,比如配置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"}
相关文章
|
2月前
|
Cloud Native 算法 区块链
站在巨人的肩膀上:gRPC通过HTTP/2构建云原生时代的通信标准
gRPC是云原生时代高效通信标准,基于HTTP/2实现,支持四种服务方法。通过.proto文件定义接口,生成多语言Stub,实现跨语言调用。其请求响应结构清晰,结合Headers、Data帧与Trailers,保障高性能与可扩展性,广泛应用于微服务架构中。
161 0
|
2月前
|
安全 算法 网络安全
SSL/TLS协议如何确保HTTP通信的安全
通过这些机制和过程,SSL/TLS对HTTP通信提供了强大的保护,确保数据不被未授权的第三方访问或篡改,这对维护数据隐私和网络安全至关重要。随着互联网技术的不断进步,SSL/TLS协议本身也在不断更新和升级,以对抗新出现的威胁和满足现代网络的要求。
211 10
|
6月前
|
Go
在golang中发起http请求以获取访问域名的ip地址实例(使用net, httptrace库)
这只是追踪我们的行程的简单方法,不过希望你跟着探险家的脚步,即使是在互联网的隧道中,也可以找到你想去的地方。接下来就是你的探险之旅了,祝你好运!
283 26
|
7月前
|
中间件 Go
Golang | Gin:net/http与Gin启动web服务的简单比较
总的来说,`net/http`和 `Gin`都是优秀的库,它们各有优缺点。你应该根据你的需求和经验来选择最适合你的工具。希望这个比较可以帮助你做出决策。
298 35
|
7月前
|
JSON API Go
Golang工程组件:自定义HTTP规则的grpc-gateway选项
总的来说,grpc-gateway提供了一种简单有效的方式来为你的gRPC服务提供RESTful风格的API。通过自定义HTTP规则,你可以灵活地定义你的API的行为,以满足你的应用的需求。
168 27
|
算法 数据库 数据安全/隐私保护
摘要认证,使用HttpClient实现HTTP digest authentication
这篇文章提供了使用HttpClient实现HTTP摘要认证(digest authentication)的详细步骤和示例代码。
947 2
|
网络协议 安全 网络安全
中间人攻击之未加密的通信
【8月更文挑战第12天】
284 2
|
9月前
|
安全 算法 物联网
SSL/TLS:互联网通信的加密基石与安全实践
**简介:** 在数字化时代,互联网每天传输海量敏感数据,网络攻击频发。SSL/TLS协议作为网络安全的基石,通过加密技术确保数据安全传输。本文解析SSL/TLS的技术架构、密码学原理、应用场景及常见误区,探讨其在未来的发展趋势,强调持续演进以应对新型威胁的重要性。 SSL/TLS不仅保障Web安全,还广泛应用于API、邮件、物联网等领域,并遵循合规标准如PCI DSS和GDPR。
HTTP 报文解构:深入剖析 HTTP 通信的核心要素
【10月更文挑战第21天】随着网络技术的不断发展和演进,HTTP 报文的形式和功能也可能会发生变化,但对其基本解构的理解始终是掌握 HTTP 通信的关键所在。无论是在传统的 Web 应用中,还是在新兴的网络技术领域,对 HTTP 报文的深入认识都将为我们带来更多的机遇和挑战。
|
存储 安全 Java
shiro学习二:shiro的加密认证详解,加盐与不加盐两个版本。
这篇文章详细介绍了Apache Shiro安全框架中密码的加密认证机制,包括不加盐和加盐两种加密方式的实现和测试。
769 0

热门文章

最新文章

推荐镜像

更多