nginx配置https并强制http自动跳转到https
关于使用HTTPS/SSL的必要性,可以自行baidu,援引的说法,EFF(Electronic Frontier Foundation),全球过半流量采用https。
https://www.oschina.net/news/82222/https-web
关于SSL的握手过程,简单的来说,如下,线上报文流:
(1).client_hello
客户端发起请求,以明文传输请求信息,包含版本信息,加密套件候选列表,压缩算法候选列表,随机数,扩展字段等信息,相关信息如下:
• 支持的最高TSL协议版本version,从低到高依次 SSLv2 SSLv3 TLSv1 TLSv1.1 TLSv1.2,当前基本不再使用低于 TLSv1 的版本;
• 客户端支持的加密套件 cipher suites 列表, 每个加密套件对应前面 TLS 原理中的四个功能的组合:认证算法 Au (身份验证)、密钥交换算法 KeyExchange(密钥协商)、对称加密算法 Enc (信息加密)和信息摘要 Mac(完整性校验);
• 支持的压缩算法 compression methods 列表,用于后续的信息压缩传输;
• 随机数 random_C,用于后续的密钥的生成;
• 扩展字段 extensions,支持协议与算法的相关参数以及其它辅助信息等,常见的 SNI 就属于扩展字段,后续单独讨论该字段作用。
(2).server_hello+server_certificate+sever_hello_done
• server_hello, 服务端返回协商的信息结果,包括选择使用的协议版本 version,选择的加密套件 cipher suite,选择的压缩算法 compression method、随机数 random_S 等,其中随机数用于后续的密钥协商;
• server_certificates, 服务器端配置对应的证书链,用于身份验证与密钥交换;
• server_hello_done,通知客户端 server_hello 信息发送结束;
(3).证书校验
客户端验证证书的合法性,如果验证通过才会进行后续通信,否则根据错误情况不同做出提示和操作,合法性验证包括如下:
• [证书链]的可信性 trusted certificate path,方法如前文所述;
• 证书是否吊销 revocation,有两类方式离线 CRL 与在线 OCSP,不同的客户端行为会不同;
• 有效期 expiry date,证书是否在有效时间范围;
• 域名 domain,核查证书域名是否与当前的访问域名匹配,匹配规则后续分析;
(4).client_key_exchange+change_cipher_spec+encrypted_handshake_message
(a) client_key_exchange,合法性验证通过之后,客户端计算产生随机数字 Pre-master,并用证书公钥加密,发送给服务器;
(b) 此时客户端已经获取全部的计算协商密钥需要的信息:两个明文随机数 random_C 和 random_S 与自己计算产生的 Pre-master,计算得到协商密钥;
enc_key=Fuc(random_C, random_S, Pre-Master)
(c) change_cipher_spec,客户端通知服务器后续的通信都采用协商的通信密钥和加密算法进行加密通信;
(d) encrypted_handshake_message,结合之前所有通信参数的 hash 值与其它相关信息生成一段数据,采用协商密钥 session secret 与算法进行加密,然后发送给服务器用于数据与握手验证;
(5).change_cipher_spec+encrypted_handshake_message
(a) 服务器用私钥解密加密的 Pre-master 数据,基于之前交换的两个明文随机数 random_C 和 random_S,计算得到协商密钥:enc_key=Fuc(random_C, random_S, Pre-Master);
(b) 计算之前所有接收信息的 hash 值,然后解密客户端发送的 encrypted_handshake_message,验证数据和密钥正确性;
(c) change_cipher_spec, 验证通过之后,服务器同样发送 change_cipher_spec 以告知客户端后续的通信都采用协商的密钥与算法进行加密通信;
(d) encrypted_handshake_message, 服务器也结合所有当前的通信参数信息生成一段数据并采用协商密钥 session secret 与算法加密并发送到客户端;
(6).握手结束
客户端计算所有接收信息的 hash 值,并采用协商密钥解密 encrypted_handshake_message,验证服务器发送的数据和密钥,验证通过则握手完成;
(7).加密通信
开始使用协商密钥与算法进行加密通信。
可知:
1、生成对话密钥一共需要三个随机数。
2、握手之后的对话使用"对话密钥"加密(对称加密),服务器的公钥和私钥只用于加密和解密"对话密钥"(非对称加密),无其他作用。
3、服务器公钥放在服务器的数字证书之中。
找了张交互图(不包含后面的应用交互,图中实际上少了最后一步服务器结合所有当前的通信参数信息生成一段数据并采用协商密钥 session secret 与算法加密、最后发送给客户端)如下:
注意:
(a) 服务器也可以要求验证客户端,即双向认证,可以在过程2要发送 client_certificate_request 信息,客户端在过程4中先发送 client_certificate与certificate_verify_message 信息,证书的验证方式基本相同,certificate_verify_message 是采用client的私钥加密的一段基于已经协商的通信信息得到数据,服务器可以采用对应的公钥解密并验证;
(b) 根据使用的密钥交换算法的不同,如 ECC 等,协商细节略有不同,总体相似;
(c) sever key exchange 的作用是 server certificate 没有携带足够的信息时,发送给客户端以计算 pre-master,如基于 DH 的证书,公钥不被证书中包含,需要单独发送;
(d) change cipher spec 实际可用于通知对端改版当前使用的加密通信方式,当前没有深入解析;
(e) alter message 用于指明在握手或通信过程中的状态改变或错误信息,一般告警信息触发条件是连接关闭,收到不合法的信息,信息解密失败,用户取消操作等,收到告警信息之后,通信会被断开或者由接收方决定是否断开连接。
不过在大部分的场景下,服务器并不要求验证客户端,除了在一些情况下比如我们在银企直连、银证转账、清算中心的一些系统中就采用的是双向认证。
==================配置实战=======================
首先,查看安装了哪些模块,如下:
[root@iZ23i5mx5vxZ sbin]# ./nginx -V
nginx version: nginx/1.10.0
built by gcc 4.4.7 20120313 (Red Hat 4.4.7-17) (GCC)
built with OpenSSL 1.0.1e-fips 11 Feb 2013
TLS SNI support enabled
configure arguments: --user=www --group=www --prefix=/usr/local/nginx --with-http_stub_status_module --with-http_ssl_module --with-http_v2_module --with-http_gzip_static_module --with-ipv6 --with-http_sub_module
建议把相关证书和key放在$NGINX_HOME/security或cert下,便于统一管理。
建议命名规范为:
ca-key.pem 一般来说是CA证书的rsa私钥文件
ca.pem 可信Certificate Authority (CA)证书,通常内部通信的话,可以自签名,通过openssl req -new -x509生成。
client-cert.pem 客户端公钥证书(一般HTTPS不用,浏览器会自动管理,但是自行开发的客户端就需要了,比如java rpc,jdbc客户端,nginx和upstream server的通信)
client-key.pem 客户端私钥(一般HTTPS不用,浏览器会自动管理,但是自行开发的客户端就需要了,比如java rpc,jdbc客户端,nginx和upstream server的通信)
server-cert.pem 服务器公钥证书,必须由拥有它的CA认证,会发送给每个连接到服务器的客户端。
server-key.pem 服务器私钥,技术上来说公钥证书和私钥存储在一起也是可以的,发送的时候会仅仅把公钥部分发给客户端。
[root@iZ23i5mx5vxZ sbin]# cd ../conf/
[root@iZ23i5mx5vxZ conf]# openssl genrsa -des3 -out server.key 1024 #证书现在有很多三方可以免费生成,如阿里云,不一定要人工生成,从别的机器拷贝一个也是可以的。
Generating RSA private key, 1024 bit long modulus
.................................++++++
...................++++++
e is 65537 (0x10001)
Enter pass phrase for server.key:
Verifying - Enter pass phrase for server.key:
[root@iZ23i5mx5vxZ conf]# openssl req -new -key server.key -out server.csr
Enter pass phrase for server.key:
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [XX]:CN
State or Province Name (full name) []:
Locality Name (eg, city) [Default City]:
Organization Name (eg, company) [Default Company Ltd]:ldtrader.com
Organizational Unit Name (eg, section) []:
Common Name (eg, your name or your server's hostname) []:ldtrader.com # 从这里可以看出,openssl的定义要比keytool更加清晰
Email Address []:
Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:tomcat
An optional company name []:
[root@iZ23i5mx5vxZ conf]# openssl rsa -in server.key -out server_nopwd.key
Enter pass phrase for server.key:
writing RSA key
[root@iZ23i5mx5vxZ conf]# openssl x509 -req -days 3650 -in server.csr -signkey server_nopwd.key -out server.crt
Signature ok
subject=/C=CN/L=Default City/O=ldtrader.com/CN=ldtrader.com
Getting Private key
vim nginx.conf如下:
worker_processes 1; #error_log logs/error.log; #error_log logs/error.log notice; #error_log logs/error.log info; #pid logs/nginx.pid; events { use epoll; worker_connections 1024; } http { include mime.types; default_type application/octet-stream; log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; access_log logs/access.log main; sendfile on; tcp_nopush on; tcp_nodelay on; #keepalive_timeout 0; keepalive_timeout 65; #gzip on; # upstream web-admin { server localhost:8080 fail_timeout=15s; server localhost:8080 fail_timeout=15s; server localhost:8080 fail_timeout=15s; } server { # 也可以采用单独一个server块,通过重定向到443,效果一样的 listen 80; listen 443 ssl; server_name localhost; # ssl on; 注意:400 Bad Request: The plain HTTP request was sent to HTTPS port,这里一定要注释,否则就只能接收https,http请求会报错 ssl_certificate /tmp/ssl/server.crt; ssl_certificate_key /tmp/ssl/server_nopwd.key; ssl_session_cache shared:SSL:1m; ssl_session_timeout 5m; ssl_ciphers HIGH:!aNULL:!MD5; ssl_prefer_server_ciphers on; error_page 497 https://$host$uri?$args; #charset koi8-r; #access_log logs/host.access.log main; location / { root html; index index.html index.htm; proxy_pass http://web-admin;
proxy_set_header Host $host:80; #注意, 原host必须配置, 否则传递给后台的值是web-admin,端口如果没有输入的话会是80, 这会导致连接失败
proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "Upgrade";
}
#error_page 404 /404.html; # redirect server error pages to the static page /50x.html
#error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
}
启动或者reload配置。
使用http://ip访问,如下:
被自动重定向到https,可见在nginx层做https相比tomcat要简单得多。
值得注意的是,给现有的网站加上nginx代理可能会导致一些资源引用的URL失效,如果有第三方也访问本站比如websocket,切记一定要提前规划好资源走相对路径,否则可能会导致后面的变更难以推进。
在spring boot上,有一些特殊注意点,可以参考:spring boot 1.x nginx前置https配置及注意点。
参考:
https://blog.cloudflare.com/announcing-keyless-ssl-all-the-benefits-of-cloudflare-without-having-to-turn-over-your-private-ssl-keys/
https://blog.cloudflare.com/keyless-ssl-the-nitty-gritty-technical-details/
https://www.nginx.com/resources/admin-guide/nginx-ssl-termination/
https://www.nginx.com/resources/admin-guide/nginx-https-upstreams/
http://blog.csdn.net/hherima/article/details/52469674
http://www.ruanyifeng.com/blog/2014/09/illustration-ssl.html