为了提高网站的安全性,一般会在比较敏感的部分页面采用https
传输,比如注册、登录、控制台等。像Gmail
、网银等全部采用https
传输。
https/ssl
主要起到两个作用:网站认证、内容加密传输和数据一致性。经CA
签发的证书才起到认证可信的作用,所有有效证书均可以起到加密传输的作用。
浏览器与SSL
证书
上图是IE和Chrome
上对https
的不同表现。
Chrome
网站安全性指示器说明:http://support.google.com/chrome/bin/answer.py?hl=zh&answer=95617
SSL
最主要应用是在浏览器和Web
服务器之间,尽管不限于此。当然,安全本身是重要的内在属性。但在表面上看,部署SSL
就是为了让用户浏览器里看起来更安全一些,以增加用户的信任感。所以很多企业更把它当作门面,而签发机构也为此卖高价,尤其是国内的价格明显高于国外的。
实际上SSL
证书也可以做客户端认证,用户拥有自己特有的证书,用它可以证明自己的身份,当然也就用不着用户名和密码了。但这种用的很少,一般web
服务器也不支持。
内容加密传输更安全,如果只是为了加密,使用自签发的证书也可以,但浏览器无法验证证书,所以会给出一个非常吓人的警告,所以自签发证书不适合给外人使用,只适合内部使用,把这个证书 加入到自己的信任列表或忽略证书验证即可,以后就不会继续拦截了。
证书需要被少数一级或二级 CA
认证才有效。计算机安全中的信任就是一个信任链的关系,信任链最顶端的被称为根证书。
自签发的证书在技术上是完全一样的,仅用于加密传输是没问题的。但是不能被外人信任,所以一般仅用于内部使用。除了自签发不被信任,如果证书过期、已被吊销或者非证书所代表的域名也都是不被信任的,导致证书验证出错。
用于网站的证书需要被大众信任,所以不能自签发的证书,那就申请(购买)一个吧。
申请证书
1.证书类别
按证书包含域名数量分为:
- 单域名:只针对这个域名有效,不能用在其它域名下。
- 多域名:只针对列出的多个域名有效。
- 通配符域名(
wildcard
):对任意子域名有小,显示的是*.example.com
。
注意:
SSL
所说的单个域名是一个完整的域名,一个子域名就算一个,而非一个顶级域名。如果网站有很多子域名,只需要申请真正需要的域名证书。
按验证的类别分:
- 域名认证(
Domain Validation
):认证你的域名所有权和网站,申请验证简单,几分钟即可。 - 组织机构认证(
Organization Validation
):认证的域名和公司信息,需要提交公司资料认证。 - 扩展认证(
Extended Validation,
简称EV
):这种证书会在浏览器中出现“很明显”的绿色地址栏,给用户的可信度最高。有安全评估保证。
个人或小站点可用一类或二类,企业一般用二类认证,少数企业会用到
EV
认证。
SSL
证书需要向国际公认的证书证书认证机构(简称CA
,Certificate Authority
)申请。
CA
机构颁发的证书有3种类型:
域名型SSL证书(DV SSL
):信任等级普通,只需验证网站的真实性便可颁发证书保护网站;
企业型SSL证书(OV SSL
):信任等级强,须要验证企业的身份,审核严格,安全性更高;
增强型SSL证书(EV SSL
):信任等级最高,一般用于银行证券等金融机构,审核严格,安全性最高,同时可以激活绿色网址栏。
2. 证书价格
看了看网上SSL
证书的价格,便宜的一般都是10美元左右一个子域名/每年,按不同类别、不同品牌等价格在几十美元到几百美元一年。比如能显示绿色地址栏的EV
证书和通配符证书贵一些。国内自己的或代理的,比国外贵不少,动辄几千元。其实就是由可信源认证了一下,类似于办证,用起来没什么差别,并非越贵越好。
3. 签发机构(“卖家”)
国外常见的SSL提供商有:Thawte,Go Daddy,VeriSign,RapidSSL,GeoTrust(QuickSSL),StartSSL,Comodo
。
StartSSL、Go Daddy
的比较便宜,GeoTrust、Comodo
的价格适中,Thawte
和VeriSign
的价格较贵。
VeriSign
现在归属赛门铁克,在国内是由天威诚信代理的。世界真小,天威诚信就在我很多年以前的东家(启明星辰)大楼里,地下一层是他们的机房,我还进去过一次。
4. 免费的StartSSL
唯一免费的是StartSSL
,其它的一般只提供30免费试用。
但 StartSSL
提供的免费证书是一类的、仅对域名和email
进行验证,不对组织做验证(也就是面向自然人的,非面向组织机构的),不过
仅作为域名验证和数据加密也够了,并且浏览器也认它,一般人也不会去看你的证书级别。适合个人和初创网站使用,以后有钱了再申请个收费的替换即可。
我很顺利地申请到了免费的StartSSL
证书,分别用在两个子域上。
最后,证书签发给你后,最主要是保护好私钥证书,这个丢失或泄漏就完了。因为如果被别人利用也就毫无安全性了,需要向证书签发机构申请撤销证书并申请新的证书,这当然也是要收费的。
应用规划、配置和调整
并不是说有了SSL证书就没事了,还要考虑应用中的使用问题,需要规划、服务器配置、应用调整等多个环节。
SSL
比 http
要消耗更多cpu
资源(主要是在建立连接的阶段,之后还要对内容加密),所以对一般网站,只需要对部分地方采用https
,大部分开放内容是没必要的,具体取决于你的业务要求。比如对于很多安全要求较低的网站,完全不用https
也是可接受的。
某些页面是同时支持 http
和 https
,还是只支持 https
、强制 https
?
同时支持就是用户用什么协议访问都可以,那么用户的请求主要就是由页面本身的链接引导来的,因为一般用户不会自己特意去修改地址栏的。
一般我们的网站可以做成同时支持http
和https
,都可以访问。但是这就容易有后面说的混合内容或混合脚本的问题。
还可以规划为部分页面支持 https
,一般公开页面不用https
,只是将部分地方的链接改为 https
就可以了。专门期望以 https
访问的页面中,引用的绝对URL
可以明确的使用 https
链接。
是否强制 https
?对于安全性高的网站或网站中的部分页面,可以强制使用https
访问, 即使用户在地址栏里手工把 https
改为 http
, 也会被自动重定向回 https
上。比如可以通过配置web服务器 rewrite
规则将这些 http url
自动重定向到对应的 https url
上(这样维护比较简单),而不用改应用。
解决混合内容问题(http
和https
)
混合内容是指:在https
的页面中混合了非https
的资源请求,比如图片、css
、js
等等。如果是混合了非 https
的 js
代码,则被称为混合脚本。
混合内容的危害:如果只是混合了不安全的图片和css
,那么受中间人攻击篡改,一般只会影响页面的显示,危害相对小一点。如果是混合了不安全的 js
代码,则这个不安全的 js
可以完全访问和修改页面中的任何内容,这是非常危险的。
另请参看,Chrome
对混合脚本危害的说明与提示:Trying to end mixed scripting vulnerabilities
所以,只有页面本身和所有引用的资源都是 https
的浏览器才认为是安全的,只要其中引用了非安全资源(即使图片),浏览器都会给出不安全的提示,特别是有 js
的情况。如果浏览器提示不安全,那样我们就达不到原来目的了。我们费了半天功夫去申请 SSL
证书,配置Web
服务器,最后如果因为混合内容而前功尽弃就太糟了。咱继续努力吧,想办法让所有引用资源都是安全的。
理论上,混合了第三方的内容,即使是SSL
的第三方内容也不是很好。因为用户信任的是你,而不是第三方,即使第三方也支持https
,但你能保证第三方就绝对安全吗。不引用任何第三方才是绝对安全的,但这样太严格了,安全其实也是一个 tradeoff
的问题,需要考虑很多方面的平衡。还好,起码现在浏览器认为已经是安全的了。
引用第三方文件的问题(如 CDN 分发的文件)
简单地说,这个问题要么有第三方提供 https
支持,要么不用它(用自己本地的)。
一般我们会引用由 CDN 分发的文件,比如某个 js 库文件,而不用访问自己网站上的,这样借助 CDN 网络可以加快速度,这当然很好。
但是,如果我们在页面中使用绝对 URL 直接引用这个文件就无法自动使用 https
了!出现了混合协议内容,浏览器又该“变脸”了。
当SSL
遇上CDN
或 其它第三方文件就有点麻烦,因为很多CDN
还不支持SSL
。如果支持 https
的话就可以直接用 https
的绝对URL了,即使是同时支持http
和 https
的页面,这样做也不算太浪费,起码解决了问题。
因为CDN的云文件提供者,一般为每个cdn
用户创建一个单独的子域名来使用,这样的话,CDN提供者要想支持 https
就必须支持所有可能的子域名,因此要求CDN提供方使用那种通配符子域名的证书。
相对 URL
、绝对 URL
与 只缺协议的URL(Protocol Relative URL)
相对路径比较简单,自动匹配用户请求的 http
或 https
协议。
但是绝对 url
则不成,因为绝对 url
已经明确地写上了协议: http://www.example.com/jquery.js 。
这个问题还有一个办法解决,你一定没见过这种形式: //www.example.com/jquery.js
哈哈,一个缺少协议的URL
(实际上还算是相对URL),这种形式可以在浏览器中被正确补充上合适的协议!很多人都用这种方法。
但是,这里有点小问题,IE7 和 IE8 处理这种缺少协议的URL
的css
文件时,同一个css
文件会下载两次,详见Steve的文章 。
JS 自动判断当前协议
现在我们经常用 js 来加载其它 js 文件或 其它别的文件,如果是请求是相对URL则没问题,如果是绝对URL
怎么办?
其实 js 脚本可以这样:document.location.protocol
等于 ‘http:
’ 还是 ‘https:
’ 来判断。例如在 Google Analytics
的嵌入代码中:
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
应用程序中如何判断访问协议
对于动态页面,如 jsp、php等,也是可以动态判断当前是否使用了 https
协议的。所以应用可以根据动态判断,来生成不同的引用 URL。这样虽然有点麻烦,但也算是解决了自动识别协议的问题,当然相对路径总是不需要处理的。
比如在 jsp 中:
request.isSecure()
为true
表示当前为https
,false
表示http
访问request.getScheme()
返回字符串https
或http
注意,如果 tomcat
部署在其它web
服务器代理的后面,需要正确配置好才能返回正确结果,见本文最后一部分。
同源策略的问题
最后提醒一点:http
和 https
是不同源的!即使后面的内容都一样。所以 ajax
发请求的时候要使用正确协议的绝对URL
才行。
相对URL
的 ajax
请求没关系。
Nginx
配置
小结一下 Nginx
配置SSL
注意的问题,详细安装配置内容请参考其它资料,如官方 SSL
模块 和 https
配置文档。
- 首先检查一下是否已安装了
SSL
模块,因为默认是不包含的。
用 nginx -V
命令检查一下。如果没有ssl模块则需要重新安装(建议升级到最新版本),注意安装时加上ssl 选项:
./configure --with-http_ssl_module
另外,nginx
需要依赖 openssl
提供ssl
支持,这个也要有。
nginx.conf
中的典型配置示例
listen 80;
listen 443 ssl;
ssl_certificate cert.pem; #修改具体文件
ssl_certificate_key ssl.key; #修改具体文件
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
ssl_protocols SSLv2 SSLv3 TLSv1;
ssl_ciphers ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP;
ssl_prefer_server_ciphers on;
上面第2-4项是关键。这些配置放在 server
块就可以对其中的所有 location
生效了,并且同时支持 http
和 https
。或者把 http
和 https
分开配置也很常见。
- 合并证书配置文件
和Apache
配置不同,Nginx
需要将服务器证书和ca
证书链合并到一个文件中,作为ssl_certificate
配置的内容。
例如,按照证书链从下向上的顺序,我有三个证书:ssl.crt
(自己域名的服务器证书)sub.class1.server.ca.pem
(startssl 的一类证书)ca.pem
(startssl 的根证书)
把它们的内容按顺序连接到的一个文件中,每个内容另起一行,中间没有空行或空格。
- 避免启动时输入密码
配好之后,启动nginx
要你输入密钥的密码。这是因为ssl_certificate_key
配置对应的文件(也就是startssl
给你的私钥文件)内容是加密的,需要输入你创建这个时设置的密码才能解密。这样私钥虽然很安全,但是每次重启服务都要输入一次密码也太麻烦了。其实,只要证书改为解密了的内容,就可以避免每次输入密码。用如下命令即可:
openssl rsa -in ssl.key -out newssl.key
输入密码,就生成了解密后的私钥内容,使用这个就OK了。
但是就像前面说的,一定要在服务器上保护好它,例如:
chmod 400 ssl.key
(仅root可读)
- 优化
SSL
配置
SSL 很消耗 CPU 资源,尤其是在建立连接的握手阶段。一是通过开启keepalive
可以重用连接。二是可以重用和共享ssl session
,见上面ssl_session
相关配置。
独立Tomcat+SSL
Tomcat
是很常见的 Java
应用服务器,当然也可以作为独立的 Web
服务器,所有用户请求直接访问 tomcat
。
如果 Tomcat
作为独立的Web
服务器,那么就需要配置Tomcat
就可以了,文档参考这里 和 这个。主要是配置存放证书的 Keystore
和 连接器Connector
。
Java的keystore
keystore
是 Java 中专用并内置的一个类似于 openssl
的工具,一个 keystore
文件就是一个“保险箱”(database
),专门存放证书和密钥,和相关的管理功能:生成自签发的证书、密钥、导入导出等。可以通过 keytool
命令或 Java api
交互。
利用keytool
命令将你的证书导入进去。
Tomcat中Connector
tomcat中有三种 Connector
实现:block
、nio
和 APR
。前两者使用Java SSL(这需要 keystore
的配置 ),APR
使用OpenSSL
(不需要用keystore,直接指定证书),配置略有不同。
Nginx+Tomcat+SSL
实际上,大规模的网站都有很多台Web
服务器和应用服务器组成,用户的请求可能是经由 Varnish、HAProxy、Nginx
之后才到应用服务器,中间有好几层。而中小规模的典型部署常见的是 Nginx+Tomcat
这种两层配置,而Tomcat
会多于一台,Nginx
作为静态文件处理和负载均衡。
如果Nginx
作为前端代理的话,则Tomcat
根本不需要自己处理 https
,全是Nginx
处理的。用户首先和Nginx
建立连接,完成SSL
握手,而后Nginx
作为代理以 http
协议将请求转给 tomcat
处理,Nginx
再把 tomcat
的输出通过SSL
加密发回给用户,这中间是透明的,Tomcat
只是在处理 http
请求而已。因此,这种情况下不需要配置 Tomcat
的SSL
,只需要配置 Nginx
的SSL
和 Proxy
。
在代理模式下,Tomcat
如何识别用户的直接请求(URL、IP、https还是http )?
在透明代理下,如果不做任何配置Tomcat
认为所有的请求都是 Nginx
发出来的,这样会导致如下的错误结果:
request.getScheme() //总是 http,而不是实际的http或https
request.isSecure()
//总是false(因为总是http)request.getRemoteAddr()
//总是 nginx 请求的 IP,而不是用户的IPrequest.getRequestURL()
//总是 nginx 请求的URL 而不是用户实际请求的 URLresponse.sendRedirect( 相对url )
//总是重定向到http
上 (因为认为当前是http
请求)
如果程序中把这些当实际用户请求做处理就有问题了。解决方法很简单,只需要分别配置一下 Nginx
和 Tomcat
就好了,而不用改程序。
配置 Nginx
的转发选项:
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_set_header X-Forwarded-Proto $scheme;
配置Tomcat
server.xml
的 Engine
模块下配置一个 Value
:
<Valve className="org.apache.catalina.valves.RemoteIpValve" remoteIpHeader="X-Forwarded-For" protocolHeader="X-Forwarded-Proto" protocolHeaderHttpsValue="https"/>
配置双方的 X-Forwarded-Proto
就是为了正确地识别实际用户发出的协议是 http
还是 https
。X-Forwarded-For
是为了获得实际用户的 IP
。
这样以上5项测试就都变为正确的结果了,就像用户在直接访问 Tomcat
一样。