描述
单向认证指浏览器与服务器交互需要对服务器证书进行认证,而双向认证就是服务端对客户端也要进行一次认证,认证的主要条件为:客户端需要有一张客户端证书,而这张客户端证书必须是由服务端指定的 CA 根证书签发的(中间 CA 个人没验证过)。
常规操作
服务端的 Nginx 配置:
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 反向代理作为客户端请求服务端进行双向认证
服务端配置:
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;
    }
}
坑位总结
- 配置问题:代理作为客户端配置客户端证书的字段是 proxy_ssl_certificate和proxy_ssl_certificate_key而不是ssl_certificate和ssl_certificate_key,前者是服务器证书而不是作为代理的客户端证书。
- proxy_ssl_server_name这个字段默认是- off,这里要改为- on,后端服务需要代理发送- SNI才能正常工作,如果代理服务器不发送- SNI,会返回 502 错误。即无法正常和后端通信。
- 头部信息设置时需要将 Host改为与proxy_pass一致,不然会无法响应,一直502,个人理解:SNI中的server_name拿取的是proxy_set_header中的Host所以如果Host默认为请求当前服务器的Host那么代理到example.com的服务器server_name就会有问题,找不到对应的路由,当然这只是个人踩坑的解决方案,原理有问题的话,大家可以自动屏蔽。
