[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")
}
生成证书
- 生成ca根证书。生成过程会要求填写密码、CN、ON、OU等信息,记住密码,填写的信息也要和下一步
openssl.cnf文件内容一致。
openssl req -x509 -newkey rsa:4096 -keyout ca.key -out ca.crt -days 3650
- 新建并编辑文件
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
- 生成服务端证书
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
- 生成客户端证书
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"}
[golang]使用mTLS双向加密认证http通信的更多相关文章
- 【HTTPS双向加密认证】
HTTPS单向认证和双向认证 nearzk-osc 发布时间: 2015/07/30 15:27 阅读: 4177 收藏: 178 点赞: 6 评论: 3 一.背景&概念 HTTPS:在htt ...
- SSL构建单双向https认证
1. SSL基本介绍 我们常常在使用网上银行时看到的连接都是以“https”开始的,那么这个https是什么呢?这其实是表示目前连接使用了SSL进加密,能保证客户端到服务器端的通信都在被保护起来,那 ...
- 搭建私有CA并基于OpenSSL实现双向身份认证
0x00 前言 互联网上的Web应用由于用户数目广泛,都是采用单向身份认证的,只需要客户端验证服务端的身份.但如果是企业内部的应用对接,客户端数量有限,可能就会要求对客户端也做身份验证,这时就需要一个 ...
- Linux的加密认证功能以及openssl详解
一.详细介绍加密.解密技术 现在的加密/解密技术主要有三种:对称加密,非对称加密,和单向加密 这三种加密解密技术的组合就是现在电子商务的基础,它们三个有各自最适合的领域,而且所要完成的功能也是不同的, ...
- ATSHA204加密认证IC
The Atmel® ATSHA204 is a full turnkey security device. It includes a 4.5Kb EEPROM divided into 16 sl ...
- 【WP开发】加密篇:双向加密
说起双向加密,如果以前在.NET开发中弄过加/解密的朋友都不会陌生,常用的算法有DES.AES等.在RT应用程序中,也提供了加密相关的API,算法自然是一样的,只是API的封装方式不同罢了,因为RT不 ...
- 即时通信系统中如何实现:聊天消息加密,让通信更安全? 【低调赠送:QQ高仿版GG 4.5 最新源码】
加密重要的通信消息,是一个常见的需求.在一些政府部门的即时通信软件中(如税务系统),对聊天消息进行加密是非常重要的一个功能,因为谈话中可能会涉及到机密的数据.我在最新的GG 4.5中,增加了对聊天消息 ...
- golang AES/ECB/PKCS5 加密解密 url-safe-base64
因为项目的需要用到golang的一种特殊的加密解密算法AES/ECB/PKCS5,但是算法并没有包含在标准库中,经过多次失败的尝试,终于解码成功,特此分享: /* 描述 : golang AES/EC ...
- 《LDAP服务器和客户端的加密认证》RHEL6——第二篇 运维工程师必考
服务端的配置: (基于原先配好的ldap服务器)打开加密认证: Iptables –F setenforce 0 1.编辑ldap的配置文件:slapd.conf 2.启动ldap服务器: 3.切换 ...
- MySQL数据库的双向加密方式
如果你正在运行使用MySQL的Web应用程序,那么你把密码或者其他敏感信息保存在应用程序里的机会就很大.保护这些数据免受或者窥探者的获取 是一个令人关注的重要问题,因为您既不能让未经授权的人员使用或者 ...
随机推荐
- 记一次某CMS代码审计(转载)
作者:ddwGeGe 本文转自先知社区:https://xz.aliyun.com/t/11774 前言 无意中浏览到某小众OA官网且可以下载到源码,随机审计一波,最后成功Getshell,大佬勿喷 ...
- pytest—添加日志
开头 保存工作中常用的日志. pytest有自己的日志管理的开关,不需要用到python的日志库 开始 在文件根目录下新建一个pytest.ini文件 基本设置 [pytest] log_cli=tr ...
- 2023-03-04:定义一个二维数组N*M,比如5*5数组下所示: 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0,
2023-03-04:定义一个二维数组NM,比如55数组下所示: 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0 ...
- 2020-10-10:OOM都有哪些,说出几种?
福哥答案2020-10-10:#福大大架构师每日一题# [答案参考了此链接:](https://cloud.tencent.com/developer/article/1480668) 本地方法栈:1 ...
- 2022-07-23:给定N件物品,每个物品有重量(w[i])、有价值(v[i]), 只能最多选两件商品,重量不超过bag,返回价值最大能是多少? N <= 10^5, w[i] <= 10^5, v
2022-07-23:给定N件物品,每个物品有重量(w[i]).有价值(v[i]), 只能最多选两件商品,重量不超过bag,返回价值最大能是多少? N <= 10^5, w[i] <= 1 ...
- 2021-01-30:redis中,Pipeline有什么好处?
福哥答案2021-01-30:可以将多次 IO 往返的时间缩减为一次,减少多次IO延迟的开销.前提是 pipeline 执行的指令之间没有因果相关性. 多个指令之间没有依赖关系,可以使用 pipeli ...
- 2022-02-03:有一队人(两人或以上)想要在一个地方碰面,他们希望能够最小化他们的总行走距离。 给你一个 2D 网格,其中各个格子内的值要么是 0,要么是
2022-02-03:最佳的碰头地点. 有一队人(两人或以上)想要在一个地方碰面,他们希望能够最小化他们的总行走距离. 给你一个 2D 网格,其中各个格子内的值要么是 0,要么是 1. 1 表示某个人 ...
- 【熊子q的代码乐园】用python写个健康报备记录小系统
目录 一.前言 二.技术实现 1.概述 2. 环境 3. 技术核心 4. 数据库 5. 源代码 三.最后 一.前言 coding的一路上,遇到过许多问题,也写过一些代码去解决,回头看看还是有点意思的, ...
- WPF入门教程系列二十六——DataGrid使用示例(3)
WPF入门教程系列目录 WPF入门教程系列二--Application介绍 WPF入门教程系列三--Application介绍(续) WPF入门教程系列四--Dispatcher介绍 WPF入门教程系 ...
- Odoo-----计算字段、depnds,onchange 机制、模型约束
1 计算字段和默认值问题 字段通过调用模型的方法的实时计算获得,一般都是 compute 属性为主的方法,这个计算方法通过计算self每条记录设置的的值,self 是一个有记录的有序集合,支持py ...