为WordPress配置SSL(Apache+Linux)

证书的问题已经解决,文章内容更新 ——2016/04/17

Wosign的免费证书2016年9月停止提供了,所以现在StartSSL似乎是唯一选择了——2017/03/10

Wosign和StartSSL的证书已经不再被多个浏览器厂商信任,不建议使用了,推荐Let’s Encrypt的证书——2017/03/11


请注意:由于发生了沃通(Wosign)的证书错误签发事件其与其所有的StartSSL证书已经不被信任,建议参考我新写的文章《通过Certbot配置Let’s Encrypt的SSL(Apache)》配置Let’s Encrypt的SSL


为什么部署ssl?这个不用说了吧,毕竟互联网裸奔太不安全了,不仅要防黑产还要防运营商给你挂码。当然,从来没有实际配置过一个ssl站点,所以也想尝试一下,实际过程困难程度远大于预期,出现了很多没有考虑到的问题,就在下面说一说吧,顺便再带上配置ssl的步骤。

一、SSL的安装步骤

关于ssl的安装步骤其实各个博客上讲的很多,所以我只简略的概括一下。

首先ssl通常分为两种,一种是自行生成的ssl,这类证书是自己颁发给自己的,没有什么意义,一般浏览器都会将这类证书识别为不信任的证书,如此一来反而让一般的网民感到困惑,有些浏览器包括Chrome甚至将忽略风险继续访问的链接隐藏颇深,这样小白可能就不进入站点了。另一种是使用通过受信任的第三方证书发布机构发布的证书,当然不同机构的受信任程度不同,这样就会导致某一证书在某些浏览器上受到信任而其他的没有。如果希望实现非常好的兼容性,则需要去买大厂的企业ssl,显然我这个博客还没有这个必要。因此,我尝试了两种免费的第三方证书,分别是startssl和wosign。一个是国外企业,一个是国内企业。总体感觉之后谈。目前推荐Let’s Encrypt的证书,具体后面谈。

为什么在开头提到两种不同的证书呢?因为两种不同的证书的产生方式不同,当然,大部分是相同的。如果希望自己颁发证书给自己的话,可以参照这个博客,讲的比较易懂,那么下面我简单讲讲通过第三方获得发行的证书。(其实第三方证书大部分步骤和自己生成证书相同)

首先,需要安装一下openssl用来生成key和csr,openssl的最新版本请见http://www.openssl.org/source/,可以通过wget获取后解压并用make和make install命令安装

tar zxvf openssl-1.0.0a.tar.gz
cd openssl-1.0.0a
./config --prefix=/usr/local/openssl
make && make install

安装完成后使用openssl生成key和csr

openssl genrsa -out name.pem 2048
openssl req -new -key name.pem -out name.csr

其中的*.pem文件其实就是私钥,命名为*.key是一样的,具体区别可以看这个博客,csr是指Certificate Signing Request,即证书签名请求,里面已经包含了证书的全部内容以及公钥,所以通过这个就可以直接申请到正式的证书了。

之后选择一家证书提供机构,这些网站通常会采取通过证书进行登录的方式,所以在注册这些网站时会向个人电脑中安装一个该电脑的私有证书用于之后的登录(我在这个地方出现了问题,可能是系统原因,最后用手机完成的)。登录后进行网站的认证,在生成证书的过程中需要填写刚才生成的csr文件的内容。这一部分操作可以参考这个博客,他是使用startssl证书,其他的证书申请过程也相近。

好了,现在同时有公钥和私钥了,可以配置ssl了(ssl安装过程省略了,谷歌一下吧)。修改/etc/httpd/conf/extra/httpd-ssl.conf或者/etc/httpd/conf/vhosts/default.conf

<VirtualHost *:443>
	# Refer to httpd-ssl.conf for further information
	DocumentRoot "/var/www/wordpress/"
	ServerName stringblog.com
	ServerAlias wordpress
	ErrorLog "/var/log/httpd/wordpress-error.log"
	TransferLog "/var/server/httpd/logs/access_wordpress_log"
	CustomLog "/var/log/httpd/wordpress.log" \
		"%t %h %{SSL_PROTOCOL}x %{SSL_CIPHER}x \"%r\" %b"
	SSLEngine on
	SSLProtocol all -SSLv2
	SSLCipherSuite HIGH:MEDIUM:!aNULL:!MD5
	SSLCertificateFile "/etc/pki/tls/certs/2_stringblog.com.crt"
	SSLCertificateKeyFile "/etc/pki/tls/certs/stringblog.key"
	SSLCertificateChainFile "/etc/pki/tls/certs/1_root_bundle.crt"
</VirtualHost>

这其中需要注意的地方是,针对apache服务器同时需要至少两个证书文件才能被浏览器信任,通常证书供应商给apache提供的也是两个文件。上面的范例使用的的是WoSign的证书,其他的应该名称差不多可以参考。我之前一直是用自己生成的证书,所以没有添加ChainFile导致所有手机浏览器都无法正常信任证书。

另外有些供应商,比如Comodo会给四个证书,者涉及到

SSLCACertificateFile “/etc/server/httpd/conf/ssl.crt/ca-bundle.crt”
SSLCARevocationFile “/etc/server/httpd/conf/ssl.crl/ca-bundle.crl”

这两行设置,具体如何设置和效果如何自行尝试吧。

具体参考httpd-ssl.config的说明吧,之后通过命令

service httpd restart

就应该可以看到https站点了。

二、实现强制跳转ssl

考虑到配置ssl的主要目的是让站点更加安全,同时避免国内有些恶心人的运营商直接在html上挂码,如果不强制跳转的话,配置ssl就没有意义了。

在Apache中强制跳转的设置与网站的.htaccess,准确说域名域名重定向都是通过.htaccess文件实现的,要使.htaccess文件生效,需要在httpd/conf/httpd.conf里开启域名重定向

#
# AllowOverride controls what directives may be placed in .htaccess files.
# It can be "All", "None", or any combination of the keywords:
# Options FileInfo AuthConfig Limit
#
AllowOverride All

其实本身wordpress就需要支持域名重定向,所以这里的设置再wordpress安装的时候就应该设置完毕

在打开域名重定向之后需要向网站根目录中的.htaccess加入如下内容

RewriteEngine on
RewriteBase /
RewriteCond %{SERVER_PORT} !^443$
RewriteRule ^.*$ https://%{SERVER_NAME}%{REQUEST_URI} [L,R]

如此以来,所有80端口的请求都换转移到433上,就实现了强制ssl访问的目的。

注意:在强制ssl之后一定要更改wordpress 中站点的设置,否则某个网页同时有非ssl和ssl时会被浏览器认为不安全,如此会向用户发出警告。另外建议再搭建完ssl之后再写文章,否则之前的图片链接都要更改,以避免不安全警告,十分麻烦。

三、在ssl状况下的opensocial插件

考虑到博客的使用便捷性,我在使用由Afly开发的Open Social插件,之前在http的情况下使用没有任何问题,但是到https情况下登陆到控制台总会出现问题,但是评论功能正常。

研究了两个小时之后发现是opensocial在一处代码有问题,如下:

open-social.php(Line 709-808)

function open_action($os){
	if (!isset($_SESSION['access_token'])||strlen($_SESSION['access_token'])<6||!OPEN_TYPE) return;
	$newuser = $os -> open_new_user();
	if(!isset($_SESSION['open_id'])||strlen($_SESSION['open_id'])<6) return; if (is_user_logged_in())
	{ //bind 
		$wpuid = get_current_user_id(); 
		if ( open_isbind(OPEN_TYPE, $_SESSION['open_id']) ) { 
			open_close(__('This account has been bound with other user.','open-social'));
		} 
	} else { //login 
		$wpuid = open_isbind(OPEN_TYPE,$_SESSION['open_id']); 
		if (!$wpuid) { $wpuid = username_exists(strtoupper(OPEN_TYPE).$_SESSION['open_id']); 
		if(!$wpuid){ 
			if(email_exists($newuser['user_email'])) 
				open_close(sprintf(__('This email [%s] has been registered by other user.','open-social'),$newuser['user_email']));//Google,Live 
			$userdata = array( 
					'user_pass' => wp_generate_password(),
					'user_login' => strtoupper(OPEN_TYPE).$_SESSION['open_id']
				);
				if(osop('extend_hide_user_bar',1)) $userdata['show_admin_bar_front'] = 'false';
				$userdata = array_merge($userdata, $newuser);
				if(!function_exists('wp_insert_user')){
					include_once( ABSPATH . WPINC . '/registration.php' );
				} 
				$wpuid = wp_insert_user($userdata);
				if(osop('extend_user_role',1)) wp_update_user(array('ID'=>$wpuid, 'role'=>'subscriber'));
			}
		} 
	} 
	if($wpuid){
		$open_type_list = get_user_meta($wpuid, 'open_type', true);
		if($open_type_list) $open_type_list = trim($open_type_list,',').',';
		if(stripos($open_type_list,OPEN_TYPE)===false) update_user_meta($wpuid, 'open_type', $open_type_list.OPEN_TYPE.',');
		update_user_meta($wpuid, 'open_type_'.OPEN_TYPE, $_SESSION['open_id']);
		if(isset($_SESSION['open_img'])) {
			$_SESSION['open_img'] = preg_replace('{http://}', 'https://', $_SESSION['open_img']); //Change link into ssl link
			update_user_meta($wpuid, 'open_img', $_SESSION['open_img']);
			unset($_SESSION['open_img']); 
		}
		update_user_meta($wpuid, 'open_access_token', $_SESSION["access_token"]);
		wp_set_auth_cookie($wpuid, true);  //Original: wp_set_auth_cookie($wpuid, true, false);
		wp_set_current_user($wpuid);
	}
	unset($_SESSION['open_id']);
	unset($_SESSION["access_token"]);
	$back = isset($_SESSION['back']) ? $_SESSION['back'] : home_url();
	if(isset($_SESSION['back'])) unset($_SESSION['back']);
	header('Location:'.$back);
	exit;
}

请注意在上面代码的中我添加注释的两个地方,其中第一个地方是保证所有的图片地址都是ssl,目的是让浏览器识别为完全安全的ssl链接。而第二地方就是我说的bug内容,wordpress的后台操作,包括发文章等内容,是通过cookie来判断用户是否登陆的。这里函数中调用了wordpressd的函数wp_set_auth_cookie()从而产生cookie,我们看看原函数的定义如何

if ( !function_exists('wp_set_auth_cookie') ) :
	/**
	* Sets the authentication cookies based on user ID.
	*
	* The $remember parameter increases the time that the cookie will be kept. The
	* default the cookie is kept without remembering is two days. When $remember is
	* set, the cookies will be kept for 14 days or two weeks.
	*
	* @since 2.5.0
	* @since 4.3.0 Added the `$token` parameter.
	*
	* @param int $user_id User ID
	* @param bool $remember Whether to remember the user
	* @param mixed $secure Whether the admin cookies should only be sent over HTTPS.
	* Default is_ssl().
	* @param string $token Optional. User's session token to use for this cookie.
	*/
	function wp_set_auth_cookie( $user_id, $remember = false, $secure = '', $token = '' ) {
		/**There are other codes below
		 *
		 *
		 *
		**/
		if ( '' === $secure ) {
		$secure = is_ssl();
		}

		/**There are other codes below
		 *
		 *
		 *
		**/
	}
endif;

我们看到第三个变量$secure是指cookie是否加密,而这个与是否开启ssl有关。作者原来将该参数默认置否导致在ssl状况下不能正常加密,导致后台登陆判断时出现错误,所以应当将第三个参数使用默认值。

四、配置多个ssl站点

配置多个ssl站点的目的很显然,总要需要一个ssl用来操作phpmyadmin吧,再加上之后可能自己配置一个mail服务器,所以能配置多个ssl当然好了。

至于配置方式有两种:直接在httpd-ssl.conf配置两个433端口的站点或者使用两个不同的端口做为https的入口。

如果直接配置两个433端口的网站,则需要让两个网站使用完全相同的证书,及一个证书认证多个子域名,而如果使用不同端口则没有这个限制,具体配置方法建议阅读这个博客

五、ssl证书的选择

这个问题最麻烦,所以我最后谈。

我尝试了三家ssl证书供应商,分别是StartSSL、WoSign和Comodo,其中前两家是免费证书,最后一家是付费证书,证书价格倒是很便宜4.99刀一年如果买三年的话。最近我又尝试了Let’s Encrypted的证书。

Startssl免费一个证书最多支持5个子域名,最长有效期一年。Wosign证书也是支持5个子域名,不填写www的话会免费送www所以实际可以支持6个子域名,最长能申请两年。StratSSL免费的Class 1证书现在最多支持10个子域名,最长有效期三年。Wosign现在已经不提供免费ssl证书了,但不建议使用上述两个厂商的证书(无论免费和付费),因为他们已经不再被谷歌信任,详见谷歌说明Comodo证书相对正式很多,有两个CA证书,但是我购买的这个版本只提供证书给一级域名和www的二级域名。综合上述指标,其实Wosign的证书是最超值的,所以我目前在使用Wosign的证书,我也推荐使用Wosign证书。推荐使用Let’s Encrypt的证书,该证书可以通过脚本直接自动更新,非常方便,直接免去上述自己生成证书的过程,请前往他们的官网了解详情,或者参考我新写的文章配置。

具体使用效果的话,Wosign我尝试了包括ie、edge、chrome、360、opera电脑端和uc uwp应用,还有手机端的浏览器包括chrome、360、uc和safari,这些浏览器都没有任何问题。其他两个证书供应商因为我之前的设置问题,所以我没有完成在正确配置下的完整测试,不过估计问题不大。似乎在 Android 2 和 Windows XP 下有问题,其他绝大部分情况下都能正常信任。[1]


以上

发表评论