Linux下编译支持SQLite3加密扩展

自PHP 5.3.0起默认启用SQLite3扩展,但是由于SQLite 3 开源版不带加密功能,如果想使用加密功能需要用他的商业版本。这导致PHP默认的SQLite扩展本身是不支持加密功能,只预留了相关的接口,详见官方文档介绍:

An optional encryption key used when encrypting and decrypting an SQLite database. If the SQLite encryption module is not installed, this parameter will have no effect.

但是如果项目需要用到SQLite,如果不加密,对安全多多少少有一点影响。还好SQLite本身有预留加密的接口,许多爱好者就基于这个接口,实现了加密功能。

目前比较有知名度的分别是:wxsqlite3sqlcipher,其中sqlcipher官方有文档介绍如何实现php的加密SQLite3扩展,而wxsqlite3没有,不过目前知名的数据库管理工具Navicat使用的是wxsqlite3的库实现SQLite3的加密连接,所以为了后续维护的方便,故打算使用wxsqlite3。

扩展编译教程

首先你需要确保你没有将SQLite3扩展加入php主程序(编译时使用 –without-sqlite3 禁用 SQLite3 扩展),万一不幸加入了,我目前也不知道有什么好办法,感觉重新编译一下就好了。

到php官网下载相对应的php版本源码(本文以php-7.0.17为例)。

将下载的源码包放到root目录,并解压

1
tar -zxf php-7.0.17.tar.gz

进入SQLite3扩展源码目录

1
cd php-7.0.17/ext/sqlite3

将wxsqlite3源码里的 sqlite3secure/src 的所有文件拷贝进 /root/php-7.0.17/ext/sqlite3/libsqlite
生成并编辑预编译配置文件

1
2
cp config0.m4 config.m4
vi config.m4

将config.m4里的 sqlite3_extra_sources=”libsqlite/sqlite3.c” 替换成 sqlite3_extra_sources=”libsqlite/sqlite3secure.c”
生成预编译文件

1
phpize

预编译

1
./configure CFLAGS="-DSQLITE_HAS_CODEC"

编译并安装

1
make && make install

最后在php.ini里加载编译好的so文件即可,这时php就支持加密版本的SQLite3啦,么么哒。

开启SELinux下启动Mysql(转)

本文转至:http://blog.51cto.com/morrowind/1854427

前言:

今日在部署mysql应用时,遇到mysql无法启动错误

环境:

系统:centos 6.8 x64

mysql:mysql 5.7

问题:

采用mysql 官方yum 源安装mysql5.7,安装后修改了my.cnf 默认的datadir 路径为自定义目录。

启动mysql 报datadir 目录无权限,错误信息如下。

1
2
3
4
5
Initializing MySQL database: mysqld: Can't create directory '/data/mysql/' (Errcode: 17 - File exists)
2016-09-20T01:51:47.062108Z 0 [Warning] TIMESTAMP with implicit DEFAULT value is deprecated.
Please use --explicit_defaults_for_timestamp server option (see documentation for more details).
2016-09-20T01:51:47.064457Z 0 [ERROR] Aborting
[FAILED]

解决方案:

确认datadir 目录属主 属组均为mysql 用户,应该不是用户权限的问题,这时想起SELinux 默认是开启状态,会不会是SELinux 的问题,为了确认问题,临时关闭SELINUX

1
# setenforce 0

也可边查看SELINUX 日志边启动mysql服务

1
# tail -f /var/log/audit/audit.log

确认确实是SELinux 的原因,那就来修改 datadir 自定义目录的SELinux 标签。

首先查看默认目录的SElinux 标签

1
2
# ls -ldZ /var/lib/mysql
drwxr-x--x. mysql mysql system_u:object_r:mysqld_db_t:s0 /var/lib/mysql

设置自定义目录的SELINUX 标签为以上值,并确认

1
2
3
# chcon -Rv -u system_u -t mysqld_db_t /data/mysql
# ls -ldZ /data/mysql
drwxr-x--x. mysql mysql system_u:object_r:mysqld_db_t:s0 /data/mysql/

这样问题应该就解决了,启动mysql

1
# service mysqld start

晕,居然还是同样的问题,再次查看/data/mysql目录的安全上下文

1
2
# ls -lZd /data/mysql/
drwxr-x--x. mysql mysql system_u:object_r:default_t:s0 /data/mysql/

汗,目录的安全上下文居然变回默认值了,猜测可能是mysql启动时重置了目录的selinux安全上下文。

那有没有办法修改默认安全上下文的方法呢,只要你能想到的,就肯定是有的。

使用semanage 修改安全上下文的默认值,具体使用可见 http://man.linuxde.net/semanage

1
# semanage fcontext -a -t mysqld_db_t "/data/mysql(/.*)?"

接下来在将目录的安全上下文恢复为刚刚设置的默认值即可

1
# restorecon -Rv /data/mysql

如果使用的是非默认端口,还需修改端口的安全上下文

1
# semanage port -a -t mysqld_port_t -p tcp port_number

需要修改的安全上下文标签值,是什么这里就用什么,此处mysql 用的是mysqld_db_t 如果是http类的,用的就是http_port

启动还是报同样的错误

最后要修正下安全上下文

1
2
3
# fixfiles restore
********************************************filespec_add: conflicting specifications for /var/log/boot.log and /var/spool/plymouth/boot.log, using system_u:object_r:plymouthd_spool_t:s0.
******************

再次启动mysql就可以了

亦可通过查看selinux 文档确认修改

1
2
3
# grep "/data/mysql" /etc/selinux/targeted/contexts/files/file_contexts.local
/data/mysql(/.*)? system_u:object_r:mysqld_db_t:s0
/data/mysql system_u:object_r:mysqld_db_t:s0

按照这种方式推理,直接在file_contexts.local 文件增加自定义目录及对应安全上下文貌似也可以,

经验证可行,但同样需要执行

1
# fixfiles restore

总结:

遇到系统权限问题时,首先确认用户权限设置是否正确,其次就要想到SELINUX 这个linux系统独有的安全控制

selinux 太复杂,但在生产环境是一种不错的安全机制

解决OpenSSL在C++/PHP下AES加密结果不一致问题

最近公司需要用到AES算法对数据进行加密传输,其中客户端用的是C/C++开发,web端用的是PHP开发。

C/C++这边使用OpenSSL库实现AES加密,PHP端则使用自带的 openssl_encrypt 实现AES加密,一开始都挺顺利的,到后面对接时才发现两边加密出来的结果不一致,但两边的密钥与初始向量都是一样的。

经过反复测试后,发现当明文刚好为16个字节的倍数时,则两边加密出来的密文是一致的。最后上网搜了一下资料,发现OpenSSL进行AES加密时,每次只能加密16个字节,所以明文长度必须是16的整数倍,或者至少大于in长度的最小16倍数,这样才能真正完成加密解密。而如果明文长度不足不是16的倍数,那么最后的几个字节,其实相当于填充 \0 。

既然发现了问题原因,那就好办了,只需要PHP这边在加密前先判断一下明文长度,长度不是16的倍数的,就自动填充 \0 到16的倍数即可。比如长度为15,则填充到16,30则填充到32,以此类推。下面是我的自动填充代码:

1
2
3
4
5
6
7
8
9
10
<?php
$text = "12345678";
$text_len = strlen($text);
$mod = $text_len%16;
if($mod){
$max = $text_len + (16 - $mod);
for($i=$text_len;$i<$max;$i++){
$text[$i] = "\0";
}
}

Window下编译openssl

在Window下编译OpenSSL需要安装perl、vs等软件,本教程将以VS2010+Activeperl为示例。

安装ActivePerl

官网下载对应的运行版本安装即可。

下载并解压OpenSSL源码

官网下载自己需要的版本,并解压到任意目录,比如 c:\openssl-src

设置VS2010编译环境

打开命令行,执行如下命令:

32位:

1
2
cd C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\bin
vcvars32.bat

64位:

1
2
cd C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\bin\amd64
vcvars64.bat

其中 C:\Program Files (x86)\Microsoft Visual Studio 10.0 是我VS2010安装目录,读者需要把路径修改为本机实际路径。

进入OpenSSL源码目录

1
cd c:\openssl-src

配置编译文件

1
perl Configure debug-VC-WIN32 no-asm --prefix="c:\openssl"

其中:

debug-VC-WIN32 表示windows 32位系统,64位系统请换成 debug-VC-WIN64A

Relase 版本去掉debug,改为VC-WIN32或者VC-WIN64A

no-asm 表示不用汇编,不设置此属性会导致编译不过

prefix 表示需要安装的目录

生成编译文件

32位:

1
ms\do_ms.bat

64位:

1
ms\do_win64a.bat

执行这一步之后,在ms目录下会生成 nt.makntdll.mak 两个编译配置文件

nt.mak 用于生成静态lib库

ntdll.mak 用于生成动态dll库

编译

静态库

1
2
nmake -f ms\nt.mak
nmake -f ms\nt.mak install

动态库

1
2
nmake -f ms\ntdll.mak
nmake -f ms\ntdll.mak install

常见错误

1
2
3
4
5
Assembling: tmp32\sha1-586.asm
tmp32\sha1-586.asm(1427) : error A2070: invalid instruction operands
tmp32\sha1-586.asm(1571) : error A2070: invalid instruction operands
NMAKE : fatal error U1077: 'ml' : return code '0x1'
Stop.

出现这个问题一般就是因为在编译配置文件的时候没有加上 no-asm 属性导致的

1
2
3
4
.\ssl\bad_dtls_test.c : error C2220: 警告被视为错误 - 没有生成“object”文件
.\ssl\bad_dtls_test.c : warning C4819: 该文件包含不能在当前代码页(936)中表示的字符。请将该文件保存为 Unicode 格式以防止 数据丢失
NMAKE : fatal error U1077: “"H:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\BIN\cl.EXE"”: 返回代码“0x2”
Stop.

出现这个问题是只需要降低警告级别就可以了,打开 ntdll.mak 文件,大概在第19行附近,将 CFLAG 参数中的 /W3 改成 /W0 就可以了

1
2
cryptlib.obj : error LNK2001: 无法解析的外部符号 _OPENSSL_ia32cap_P
cryptlib.obj : error LNK2019: 无法解析的外部符号 _OPENSSL_ia32_cpuid,该符号在函数 _OPENSSL_cpuid_setup 中被引用

出现这个问题是因为之前编译的临时文件没有清理导致的,因为我之前编译的时候没有添加no-asm选项,后面加上时没有清理就直接编译了,那肯定有问题。所以重新清理下再编译即可(清理方式就是重新解压一份原来的压缩包,重新来就是了)

开发一个简易的PHP扩展

本教程将实现一个阶乘扩展,编译环境为:CentOS7/PHP5.6.3,其PHP函数实现如下:

1
2
3
4
5
6
7
8
9
10
function factorial($number){
if($number < 1) return 0;
$i = 1;
$result = 1;
while($i <= $number){
$result *= $i;
$i++;
}
return $result;
}

注意:由于这个函数只用于演示,未考虑大数的阶乘,在使用大数进行阶乘时,有可能会发生整形溢出。

定义函数原型

我们首先在php源码的扩展目录定义一个文件名为”factorial.def”的文件,并在里面写上如下内容:

1
long factorial(int number)

生成扩展基本代码

这时候我们就可以使用php官方为我们提供的工具ext_skel,来生成扩展的基本代码,命令如下:

1
./ext_skel --extname=factorial --proto=factorial.def

其中extname表示要生成的扩展名称,proto表示函数原型的文件路径

扩展配置修改

经过上一步的代码生成,现在当前目录下应该出现了一个factorial的目录,我们进入这个目录,并编辑目录下的“config.m4”文件,将以下两行代码前的dnl删除,如下:

1
2
3
PHP_ARG_ENABLE(factorial, whether to enable factorial support,
Make sure that the comment is aligned:
[ --enable-factorial Enable factorial support])

功能实现

接下来就是我们今天的主菜了,我们修改一下factorial.c,找到函数主体,修改为如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
PHP_FUNCTION(factorial)
{
int argc = ZEND_NUM_ARGS();
long number;
if (zend_parse_parameters(argc TSRMLS_CC, "l", &number) == FAILURE)
return;
if(number < 1) RETURN_LONG(0);
long i = 1;
long result = 1;
while(i <= number){
result *= i;
i++;
}
RETURN_LONG(result);
}

代码中的zend_parse_parameters是用来检验输入参数的,它的第三个参数表示的是输入参数的类型,觉的类型表示如下表所示:

类型指定符 对应的C类型 描述
l long 长整数
d double 浮点数
s char *, int 二进制字符串,长度
b zend_bool 逻辑型(1或0)
r zval * 资源(文件指针,数据库连接等)
a zval * 联合数组
o zval * 任何类型的对象
O zval * 指定类型的对象。需要提供目标对象的类类型
z zval * 无任何操作的zval
foo foo foo
bar bar bar
baz baz baz

扩展函数的返回值,需要使用php预设的宏定义来返回,常见的宏定义如下表所示:

宏定义 设置返回值 宏返回类型和参数
RETURN_LONG(l) RETVAL_LONG(l) 整数
RETURN_BOOL(b) RETVAL_BOOL(b) 布尔数(1或0)
RETURN_NULL() RETVAL_NULL() NULL
RETURN_DOUBLE(d) RETVAL_DOUBLE(d) 浮点数
RETURN_STRING(s, dup) RETVAL_STRING(s, dup) 字符串。如果dup为1,引擎会调用estrdup()重复s,使用拷贝。如果dup为0,就使用s
RETURN_STRINGL(s, l, dup) RETVAL_STRINGL(s, l, dup) 长度为l的字符串值。与上一个宏一样,但因为s的长度被指定,所以速度更快。
RETURN_TRUE RETVAL_TRUE 返回布尔值true。注意到这个宏没有括号。
RETURN_FALSE RETVAL_FALSE 返回布尔值false。注意到这个宏没有括号。
RETURN_RESOURCE(r) RETVAL_RESOURCE(r) 资源句柄。

编译扩展

执行以下命令,执行扩展编译:

1
2
3
/usr/local/php/bin/phpzie
./configure
make && make install

添加扩展

编辑php.ini,加入如下代码:

1
extension=factorial.so

代码添加后,我们需要重启一下php-fpm.
至此,我们就将扩展添加进PHP了,这时我们可以编写一个php文件来做测试,如下:

1
2
echo factorial(10);
// returns 3628800

Windows下C语言连接Oracle数据库

最近公司有个项目需要用到Oracle数据库,我负责前期的调研。由于项目要用到C和PHP两种语言,所以先收集这两种语言连接Oracle的方法。PHP使用的是Laravel框架,直接使用了Laravel-OCI8进行数据库连接,毫无压力的连接成功了。C使用的是Ocilib进行数据库连接,在这里踩了几个坑,卡了几天,今天有空整理下,防止下次继续掉坑。

提示:本文使用的是win7 64位去连接远程的 Oracle 11g数据库

OCILIB介绍

OCILIB是一个跨平台的Oracle驱动程序,可提供非常快速和可靠地访问Oracle数据库。它提供了一个丰富,功能齐全,并易于使用的API 。OCILIB 支持运行的所有Oracle平台。

OCI环境搭建

由于OCILIB是对OCI进行二次封装,所以在使用OCILIB前,需要安装OCI环境,我们只要安装好Instant Client就能完成OCI环境的搭建。

Instant Client下载

首先我们从官方下载自己系统所对应的Instant Client,由于我的系统是win7 64位的,所以选择了 Instant Client for Microsoft Windows (x64)下的 instantclient-basic-windows.x64-12.1.0.2.0.zip

提示:页面下需要点击 Accept License Agreement 后才能开始下载。

Instant Client安装

我们将下载的文件解压到电脑任意位置(本文将以解压到 D:\instantclient_12_1 为例),然后添加以下环境变量:

  • path => D:\instantclient_12_1
  • TNS_ADMIN => D:\instantclient_12_1
  • NLS_LANG => SIMPLIFIED CHINESE_CHINA.ZHS16GBK

提示:path如果已经存在值的话,直接在尾部追加。

下载OCILIB

直接从Github上下载最新版即可,下载完成后将文件解压到任意位置(本文将以解压到 C:\ocilib 为例)。

VS2010配置

  1. 点击 项目 - 属性 - 配置管理器,新建一个X64平台(因为我下载的Instant Client是64位的,所以要用x64平台)
  2. 点击 项目 - 属性 - 配置属性 - VC++ 目录,在 包含目录 添加 C:\ocilib\include
  3. 点击 项目 - 属性 - 配置属性 - 链接器 - 常规,在 附加库目录 添加 C:\ocilib\lib64

示例代码

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#include "stdafx.h"
#include "ocilib.h"
#if defined(OCI_CHARSET_WIDE)
#pragma comment(lib, "ocilibw.lib")
#elif defined(OCI_CHARSET_ANSI)
#pragma comment(lib, "ociliba.lib")
#endif
void err_handler(OCI_Error *err)
{
printf(
"code : ORA-%05i\n"
"msg : %s\n"
"sql : %s\n",
OCI_ErrorGetOCICode(err),
OCI_ErrorGetString(err),
OCI_GetSql(OCI_ErrorGetStatement(err))
);
}
int main(int argc, _TCHAR* argv[])
{
OCI_Connection* cn;
OCI_Statement* st;
OCI_Resultset* rs;
OCI_Initialize(NULL, NULL, OCI_ENV_DEFAULT);
cn = OCI_ConnectionCreate("192.168.1.152:1521/TEST", "system", "123456", OCI_SESSION_DEFAULT);
if(cn == NULL){
err_handler(OCI_GetLastError());
printf("%i",OCI_GetVersionServer(cn));
printf("连接失败!\n");
}
st = OCI_StatementCreate(cn);
OCI_ExecuteStmt(st, "select id,age from web_user");
rs = OCI_GetResultset(st);
while (OCI_FetchNext(rs)){
printf("code: %i, name %s\n", OCI_GetInt(rs, 1) , OCI_GetString(rs, 2));
}
printf("\n%d row(s) fetched\n", OCI_GetRowCount(rs));
OCI_Cleanup();
char a[20];
gets(a);
return EXIT_SUCCESS;
}

提示:程序运行时如果提示 计算机中丢失 ocilib.dll,这里由于程序找不到ocilib.dll造成的,只要将
C:\ocilib\lib64 加入path或者将 C:\ocilib\lib64 下的 ocilib*.dll
复制到程序目录就可以解决了。

Window下使用GMP库

GMP介绍

GMP是The GNU MP Bignum Library,是一个开源的数学运算库,它可以用于任意精度的数学运算,包括有符号整数、有理数和浮点数。它本身并没有精度限制,只取决于机器的硬件情况。
更多详情可能访问官方网站 https://gmplib.org/

环境所需软件

点击上面两个软件名就能下载对应的安装包,如果下载不了,可以到我的网盘上下载,地址如下:
链接: http://pan.baidu.com/s/1slw4YOP 密码: 9a2h

—— 这里是赶时间分割线 ——
如果你很赶时间,我网盘上也有已经编译好的.h .lib .dll文件,可以直接下载,当然在这里下载以后,下面的内容也可以不用看了,地址如下:
链接: http://pan.baidu.com/s/1bpaCP0z 密码: kaxw

安装过程

1.安装MinGW
安装后组件选择”mingw32-base”,”mingw32-gcc-g++”,”mingw32-gmp”
安装组件的过程中需要保持网络连接正常。
本文假设MinGW的安装路径为默认的路径 C:\MinGW

2.安装pexports
将下载回来的pexports.exe复制进 C:\MinGW\bin

3.生成lib文件
到了这一步,GMP就已经在Windows里安装好了,我们只要将gmp.h和libgmp.lib文件载入我们的VS工程就可以使用GMP库了。
gmp.h 位于 C:\MinGW\include 目录下
libgmp.lib 则需要我们手动生成,我们可以根据 C:\MinGW\bin\libgmp-10.dll 来生成相应的lib文件,步骤如下:

1
2
3
cd C:\MinGW\bin
pexports.exe libgmp-10.dll > libgmp-10.def
lib /def:libgmp-10.def

这时目录下就会出现 libgmp.lib 文件了。

如果提示 lib 命令不存在,请将VS的相关路径写入环境变量,比如我的是 C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\bin

使用示例

这里我以VS2010做示例,首先我们要将gmp.h和libgmp.lib文件载入工程。
代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
#include "gmp.h"
#pragma comment(lib,"libgmp-10.lib")
int main()
{
mpz_t t; //mpz_t 为GMP内置大数类型
mpz_init(t); //大数t使用前要进行初始化,以便动态分配空间
mpz_ui_pow_ui(t, 2, 100); //GMP所有函数基本都是以mpz打头
gmp_printf("2^100=%Zd\n", t); //输出大数,大数的格式化标志为%Zd
mpz_clear(t);
scanf_s("%s");
return 0;
}

结语

写完以后才发现好像也不难,挺简单的,然而我竟然搞了一下午(一不小心就暴露智商了),那就这样了。

使用x-sendfile提升PHP文件下载效率

最近做的项目有个工具库模块,需要实现工具的上传与下载。上传与下载都使用的是Laravel内置的方法,本来都没什么问题,今天无意上传了一个两百多M的文件,上传很顺利,但当点击下载时,发现会出现下载回来的数据不全的情况。暂时没找到是什么原因,估计是内存方面的问题。

修改了几次代码后都无法解决,那就只能换一种方式了。多次搜索后,发现了一种直接返回 x-sendfile 头实现文件下载方式,完美的解决了我的问题。

Nginx

Nginx默认支持x-sendfile模式,所以使用起来十分方便。

配置虚拟域名的配置文件,添加如下内容
1
2
3
4
5
#伪造下载路径
location /down/{
internal; #只允许内部访问,防止盗链
alias /home/www/myweb/public/uploads/utool/;
}
PHP返回header头
1
2
3
4
5
$filename = 'test.zip';
$file_path = '/down/test.zip';//物理真实路径是 /home/www/myweb/public/uploads/utool/test.zip ,至于为什么请自行脑补
header('Content-type: application/octet-stream');
header('Content-Disposition: attachment; filename="'.$filename.'"');
header('X-Accel-Redirect:'.$file_path);

就这么轻松的两步就实现了大文件的下载,是不是很简单。

Apache

Apache需要加载 module mod_xsendfile 模块来实现相对应的功能,所以稍微复杂一点

加载 module mod_xsendfile

项目地址上下载对应的 mod_xsendfile.so,放到apache的modules目录下.然后修改 http.conf ,添加如下内容

1
LoadModule xsendfile_module modules/mod_xsendfile.so
开启 xsendfile

修改虚拟域名的配置文件,添加如下内容

1
2
3
4
5
6
7
8
# 开启XSendFile
XSendFile On
<Directory "D:/wamp/www/myweb/public/uploads/utool/">
# 只允许本地链接,防止盗链
<IfDefine APACHE24>
Require local
</IfDefine>
</Directory>
PHP返回header头
1
2
3
4
5
$filename = 'test.zip';
$file_path = 'D:/wamp/www/myweb/public/uploads/utool/test.zip';
header('Content-type: application/octet-stream');
header('Content-Disposition: attachment; filename="'.$filename.'"');
header('X-Sendfile:'.$file_path);

使用 x-sendfile 模式后,PHP只需返回header头,后续就不需要处理,所以效率有了明显提升。

如果在下载过程中,出现中文文件名乱码的情况,可以使用函数 rawurlencode 对文件名做转码,这样就能解决乱码问题。

Apache/Nginx开启SSL双向认证

OpenSSL生成密钥证书

如果是付费的一般都会有现成的密钥证书,可以略过此步。

1.1 进入openssl目录

1
# cd <openssl目录路径>

在要生成证书的目录下建立几个文件和文件夹,目录结构如下

1
2
3
4
|-- demoCA
|-- demoCA/newcerts
|-- demoCA/index.txt
|-- demoCA/serial

在serial文件中写入第一个序列号“01”

1.2 设置openssl配置文件地址

1
# set OPENSSL_CONF= <配置文件地址>

1.3 生成CA根证书

1
# openssl req -new -x509 -keyout ca.key -out ca.crt -days 8000

输入以上命令后,按提示依次国家缩写、省名、城市名、公司名、部门名、主机名、邮箱
Ps:以上各选项只能输入英文,不然会乱码

1.4 生成客户端密钥

1
# openssl genrsa -out client.key 1024

1.5 生成证书请求

1
# openssl req -new -out client.csr -key client.key

输入以上命令后,按提示依次国家缩写、省名、城市名、公司名、部门名、主机名、邮箱、密码、公司别名
Ps:以上各选项只能输入英文,不然会乱码

1.6 用CA证书为server.csr做签名

1
# openssl ca -in client.csr -out client.crt -cert ca.crt -keyfile ca.key -days 8000

1.7 导出成浏览器能导入的证书

1
# openssl pkcs12 -export -inkey client.key -in client.crt -out client.p12

这里需要输入密码(这里的密码就是导入证书时要用到的密码)

这一步主要是生成一个可导入的证书,如果只是想开启单向认证可以略过此步。

1.8 移动证书与密钥

我们将当前目录下的 ca.crt、ca.key、client.csr、client.crt、client.key、client.p12 都拷贝到 conf/ssl-client 目录下,没有就创建。

Apache开启方法

2.1 修改 http.conf 文件

2.1.1 添加443端口的监听
1
Listen 443

2.1.2 去掉下面行首的 # 号(加入ssl模块)

1
#LoadModule ssl_module modules/mod_ssl.so

2.2 修改需要开启ssl的域名配置文件(示例)

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
<VirtualHost *:443>
ServerAdmin www.test.com
DocumentRoot "D:/wamp/www/test"
ServerName test.com
ErrorLog "logs/test.com-error.log"
CustomLog "logs/test.com-access.log" common
# 开启HTTPS
SSLEngine on
SSLCertificateFile "<server.crt本地路径>"
SSLCertificateKeyFile "<server.key本地路径>"
# 开启双向认证
SSLCACertificateFile "<ca.crt路径>"
SSLCertificateChainFile "<client.crt路径>"
SSLVerifyClient require
SSLVerifyDepth 10
<Directory "D:/wamp/www/test">
AllowOverride None
Options None
Order allow,deny
Allow from all
</Directory>
</VirtualHost>

以上步骤完成后,重启Apache。这时再访问我们的网址就会提示 SSL 连接出错 ,我们只要双击刚刚生成的 client.p12 证书进行导入后,就可以正常访问了。

如果在Windows下遇到Apache启动不了的情况,可以进入 我的电脑->管理->事件检查器->应用程序日志 ,打开apache的错误报告,会有提示哪里出错了,一般都可以找到原因。

Nginx 开启方法

Nginx开启相对简单,只要修改配置文件就可以了。

配置文件基本内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
server {
# 开启HTTPS
ssl on;
ssl_certificate <client.crt路径>
ssl_certificate_key <server.key路径>
ssl_protocols SSLv3 TLSv1;
ssl_ciphers HIGH:!ADH:!EXPORT56:RC4+RSA:+MEDIUM;
ssl_prefer_server_ciphers on;
# 开启双向认证
ssl_verify_client on;
ssl_client_certificate <ca.crt路径>
location ~ \.php{
# PHP开启HTTPS
fastcgi_param HTTPS on;
}
}

以上的目录路径和文件名都是可以自定义的,不一定要按照我的来。

Laravel导入自定义扩展类

创建自定义类

app/Extensions 目录下创建一个文件 MyClass.php

1
2
3
4
5
6
namespace App\Extensions;
class MyClass{
public function run(){
return 'Hello world';
}
}

将自定义类添加到Ioc容器

app/Providers 目录下创建一个文件 MyClassServiceProvider.php

1
2
3
4
5
6
7
8
9
10
namespace app\Providers;
use Illuminate\Support\ServiceProvider;
class MyClassServiceProvider extends ServiceProvider{
public function register() {
$this->app['MyClass'] = $this->app->share(
function ($app) {
return new \App\Extensions\MyClass();
});
}
}

实现伪静态方法调用

app/Facades 目录下创建一个文件 MyClassFacades.php

1
2
3
4
5
6
7
namespace app\Facades;
use Illuminate\Support\Facades\Facade;
class MyClassFacades extends Facade{
protected static function getFacadeAccessor(){
return 'MyClass';
}
}

自动加载并声明一个调用别名

app/config/app.php 中的 providers 中添加如下配置:

1
2
3
'providers' => array(
'app\Providers\MyClassServiceProvider'
),

app/config/app.php 中的 aliases 中添加别名:

1
2
3
'aliases' => array(
'MyClass' => 'app\Facades\MyClassFacades',
)

最后我们就可以在自己的项目中用 MyClass::run() 的方式调用自定义类里的函数了。

上面所有的目录路径和文件名都可以根据自己项目的实际结构进行规划,不一定要按照我上面的路径和文件名来,只要你的目录结构不影响你项目的开发即可。

如果想详细了解Facades,可以查看官方文档的介绍。