HTTPS 双向认证(Mutual TLS authentication)

本文最后更新于:2023年4月25日 晚上

描述

单向认证指浏览器与服务器交互需要对服务器证书进行认证,而双向认证就是服务端对客户端也要进行一次认证,认证的主要条件为:客户端需要有一张客户端证书,而这张客户端证书必须是由服务端指定的 CA 根证书签发的(中间 CA 个人没验证过)。

常规操作

服务端的 Nginx 配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
server {
listen 443 ssl;
server_name example.com;
server_tokens off;

access_log /etc/nginx/access.log;
error_log /etc/nginx/example.log;

ssl_certificate /etc/nginx/certs/example.pem;
ssl_certificate_key /etc/nginx/certs/example.key;
ssl_client_certificate /etc/nginx/certs/example_server_ca.crt;
ssl_verify_client on;
ssl_verify_depth 2;
ssl_prefer_server_ciphers on;
ssl_session_tickets off;
ssl_session_cache off;

location / {
proxy_http_version 1.1;
proxy_ignore_headers Set-Cookie;
proxy_hide_header Vary;

proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-SSL-CERT $ssl_client_escaped_cert;

proxy_pass http://127.0.0.1:serverPort;

add_header Access-Control-Allow-Origin https://server.example.com;
add_header Access-Control-Allow-Credentials true;
}
}

配置说明

基础部分

  • listen:为监听的端口
  • server_name:指定访问该服务的域名
  • server_tokens:在错误页和“服务器”响应头字段中启用或禁用nginx版本(有道翻译)点此查看具体细节
  • access_log:服务日志路径
  • error_log:错误日志路径

证书及验证部分

  • ssl_certificate:服务器证书
  • ssl_certificate_key:服务器证书私钥
  • ssl_client_certificate:服务器验证客户端证书的 CA 证书
  • ssl_verify_client:是否开启客户端验证
  • ssl_verify_depth:验证深度(如果是中间证书颁发的客户端证书深度至少要写2)
  • ssl_prefer_server_ciphers:指定在使用SSLv3和TLS协议时,服务器密码应该优于客户端密码(有道翻译)点此查看具体细节
  • ssl_session_tickets:是否可以通过 ssl_session_tickets 恢复 session 会话(有道翻译)点此查看具体细节
  • ssl_session_cache:设置存储会话参数的缓存的类型和大小(有道翻译)点此查看具体细节

代理部分

  • proxy_http_version:代理 http 协议版本
  • proxy_ignore_headers:禁止从代理服务器处理某些响应标头字段(有道翻译)点此查看具体细节
  • proxy_hide_header:设置不传递的非默认不传递的字段点此查看具体细节
  • proxy_set_header:设置请求头
  • proxy_pass:设置代理服务器的协议和地址

跨域设置

  • add_header:添加头部信息
  • Access-Control-Allow-Origin:访问控制允许来源
  • Access-Control-Allow-Credentials:响应头告诉浏览器是否将响应公开给前端JavaScript代码

扩展

Nginx 反向代理作为客户端请求服务端进行双向认证

服务端配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
server {
listen 443 ssl;
server_name server.example.com;
access_log /etc/nginx/access.log;
error_log /etc/nginx/error.log;

ssl_certificate /etc/nginx/conf.d/ssl/cert.pem;
ssl_certificate_key /etc/nginx/conf.d/ssl/key.pem;

location / {
proxy_http_version 1.1;

proxy_set_header Host example.com;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;

proxy_ssl_certificate /etc/nginx/conf.d/ssl/clientcert.pem;
proxy_ssl_certificate_key /etc/nginx/conf.d/ssl/clientkey.pem;
proxy_ssl_server_name on;

proxy_pass http://127.0.0.1:serverPort;
}
}

坑位总结

  1. 配置问题:代理作为客户端配置客户端证书的字段是 proxy_ssl_certificateproxy_ssl_certificate_key 而不是 ssl_certificatessl_certificate_key ,前者是服务器证书而不是作为代理的客户端证书。
  2. proxy_ssl_server_name 这个字段默认是 off,这里要改为 on ,后端服务需要代理发送 SNI 才能正常工作,如果代理服务器不发送 SNI,会返回 502 错误。即无法正常和后端通信。
  3. 头部信息设置时需要将 Host 改为与 proxy_pass 一致,不然会无法响应,一直 502,个人理解: SNI 中的 server_name 拿取的是 proxy_set_header 中的 Host 所以如果 Host 默认为请求当前服务器的 Host 那么代理到 example.com 的服务器 server_name 就会有问题,找不到对应的路由,当然这只是个人踩坑的解决方案,原理有问题的话,大家可以自动屏蔽。

参考


HTTPS 双向认证(Mutual TLS authentication)
https://agopher.com/2019/12/09/tech/2019_mutual_tls_authentication/
作者
冷宇生(Allen)
发布于
2019年12月9日
更新于
2023年4月25日
许可协议