nginx是一个web服务器,啥叫web服务器呢?我画个简图:
客户端各种请求,我们会让它们先去nginx(通过域名控制);nginx会根据请求的种类,安排对应的处理,比如请求是访问网站的、nginx就给他对应的页面,是请求接口的、nginx给它对接接口。这种接收各种客户端请求、并根据请求类别给予对应资源的服务器,就叫web服务器。
那这样的安排有什么作用?或者说使用web服务器有什么好处呢?
第一,可以做服务端负载均衡
在分布式系统中,用加机器扩展系统,是提升可用性的最有效方法。但扩展系统时,需要在应用服务前添加1个负载均衡服务,让它能将请求流量分发给后面的应用。
这一场景中,除了对负载均衡服务的性能有极高的要求外,它还必须能够处理应用层协议,而NGINX不仅性能极高、还非常擅长应用层的协议处理,完美胜任这个角色。
第二,指到静态资源
就是把http或者https请求指到对应的vue等前端项目,或具体的图片等。
有朋友就要问了,我直接访问服务器静态资源不行吗?
少了当然可以,但是多了的时候就不好找了;nginx找静态资源,优势在于它处理这种找静态资源的请求效率特别高,没别的、就是又快又准。
第三,反向代理
有的请求,是直接要访问动态资源的,就是想直接找接口要结果数据;nginx可以通过反向代理满足这一需求。
我直接访问接口好好的,为什么非要“多此一举”经nginx这么一手呢?
并不是多此一举,直接访问接口当然可以,但是很多时候,我们的接口不是谁想访问就访问的,需要对请求做有些筛选、增强、跳转等等,或者像后面要说的,限制只能接收https请求;这些额外的要求,写代码就费老鼻子事了,可以通过nginx配置实现上述功能,为了使用nginx的这项能力,有时候就需要配nginx反向代理。
配置外网可以访问的地址
nginx这个功能,就是它反向代理的一个应用实例。
工作中,有时候需要把某些只有内网可以访问的地址,设置成外网可以访问的;比如,我们要给商城测试环境的支付功能配置一个支付宝沙箱支付,支付宝要求回调地址要是外网可以访问的,但无论是我们本地(连的公司网,ip网外不可访问)还是测试服务器,都只有内网可以访问。这时,你就可以找运维申请把测试机改成外网可以访问的地址,你只需要告诉运维,你需要把
123.12:7001(开发电脑,端口为服务端口)
456.89:7002(测试服务器,端口为服务端口)
配置成外网可访问的地址。
运维会通过部署在外网可访问服务器的nginx(假设该服务器叫A、ip是202.103),使用反向代理把你的对服务器A特定端口的访问指向你的测试服务器。
比如,设置用户访问202.103:5001,指向开发机123.12:7001;
设置用户访问202.103:5002,指向测试服务器456.89:7002。
不同项目有所区别,比较简单的如下
# 打包命令不同项目有略微差别,核心命令
npm run build
# 我们项目前端给配了测试、生产环境,测试环境打包命令是
npm run build:stage
# 建议先看一下项目的README文件
打包之后,得到一个文件夹,一般叫dist、也有其他名字的
提前再linux上、合适的位置建好放代码的文件夹,将dist中的内容传过去
我以自己遇到的情况为例说明,如果你的linux服务器还没有装nginx,那就先装一下。
# 先用命令找nginx进程pid
ps -aux | grep nginx
# 再用命令找位置
ll /proc/[写pid]/cwd
在nginx下的conf中,配置文件nginx.conf
在文件中编辑 http{} 块 中的 server{} 块
新增(或者编辑已经不用的)server,主要是以下几个地方:
# server{}中的listen
listen :代理端口
# server{}中的server_name
server_name:服务名,比较简单的就写服务器ip;
# 注意,还可以写域名,但是需要额外的配置,后面示例会介绍,这个实例是简单的测试服务器的nginx配置、不过多说明
# location{}中的root和index
root :资源根目录
index :默认索引页面
server {
listen 9001;
server_name 192.168.191.52;
location / {
root /webserver/nuohua2023/ui/admin;
try_files $uri $uri/ /index.html $uri/ =404;
index index.html index.htm;
add_header X-Content-Type-Options 'nosniff';#禁止嗅探文件类型
add_header Content-Security-Policy "script-src 'self' 'unsafe-eval' 'unsafe-inline' oyxdwx.com:9443 *.aliyun.com unpkg.com g.alicdn.com *.alibaba.com *.aliapp.org *.csslcloud.net *.qq.com *.bokecc.com";#只允许同源下的js
add_header X-XSS-Protection "1; mode=block";#开启XSS过滤器
}
}
# 重启:进入nginx的sbin中,执行
./nginx -s reload
# 或者在nginx下执行
./sbin/nginx -s reload
# 或者在任意目录执行(注意不同系统nginx安装位置可能不同,path指nginx安装位置)
path/sbin/nginx -s reload
# 例如
/usr/local/nginx/sbin/nginx -s reload
# 上面三个命令效果相同
# 补充:停止nginx服务
./sbin/nginx -s stop
4)访问
以我上面对admin的配置为例,访问首页地址应该是
http://192.168.191.52:9001
一台nginx服务器,往往不只为我们一个项目使用;尤其在测试环境,多个项目公用一个nginx的情况很多。
这个时候,如果多个项目的nginx配置都放在一个conf配置文件中,会特别混乱;有的同事习惯不好、加配置还不写备注,更有甚者配错了、导致所有项目都用不了…
这个种情况,怎么处理会更好呢?
在nginx.conf这个主配置中,我们保留所有公共配置,但是所有server不要写在主配置中。
在nginx/conf文件夹下新建一个文件夹,我这里叫www,在此文件夹下建多个.conf配置文件,具体建多少个看需要,一般是一个项目一个;这些子配置文件,最好以项目名简称.conf
格式命名、做到见名知义,如下图:
每个子配置文件中,做各自的server{}模块配置,例如:
# xxx项目-后台
server {
listen 4101;
server_name 192.168.191.52;
location / {
root /webserver/nuohua/ui/admin;
try_files $uri $uri/ /index.html $uri/ =404;
index index.html index.htm;
add_header X-Content-Type-Options 'nosniff';#禁止嗅探文件类型
add_header Content-Security-Policy "script-src 'self' 'unsafe-eval' 'unsafe-inline' oyxdwx.com:9443 *.aliyun.com unpkg.com g.alicdn.com *.alibaba.com *.aliapp.org *.csslcloud.net *.qq.com *.bokecc.com";#只允许同源下的js
add_header X-XSS-Protection "1; mode=block";#开启XSS过滤器
}
}
# xxx项目-用户端pc
server {
listen 4102;
server_name 192.168.191.52;
location / {
root /webserver/nuohua/ui/web;
try_files $uri $uri/ /index.html $uri/ =404;
index index.html index.htm;
add_header X-Content-Type-Options 'nosniff';#禁止嗅探文件类型
add_header Content-Security-Policy "script-src 'self' 'unsafe-eval' 'unsafe-inline' *.bokecc.com class.csslcloud.net *.amap.com *.haoyisheng.com 192.168.191.52";#只允许同源下的js
add_header X-XSS-Protection "1; mode=block";#开启XSS过滤器
}
}
# xxx项目-用户端h5
server {
listen 4103;
server_name 192.168.191.52;
location / {
root /webserver/nuohua/ui/web_h5;
try_files $uri $uri/ /index.html $uri/ =404;
index index.html index.htm;
add_header X-Content-Type-Options 'nosniff';#禁止嗅探文件类型
add_header Content-Security-Policy "script-src 'self' 'unsafe-eval' 'unsafe-inline' 192.168.191.51:7000 p.bokecc.com class.csslcloud.net";#只允许同源下的js
add_header X-XSS-Protection "1; mode=block";#开启XSS过滤器
}
}
# 在nginx.conf的http{}模块中,加入include配置
http {
......
include path/www/*.conf
}
# 其中path指子配置目录所在位置,可以用相对路径、也可以用绝对路径
# 相对路径,就是指相对于主配置文件的路径,比如我这里子配置文件都在主配置文件同级的www文件夹中
http {
......
include www/*.conf
}
# 绝对路径,就是从根目录开始的路径,示例如下
http {
......
include /usr/local/nginx/conf/www/*.conf
}
# *.conf就是说,把目录下所有conf文件引入到主配置的http{}模块中
配置完成后重启nginx即可生效。
ok,通过以上主从配置后,各个项目的nginx分开配置,开发们各改各的,清晰明了;而且出了问题,也好定位。
甚至可以为每个子配置单独配置访问日志,用的不多就不多说了;但是这个主从配置真的很好用,强烈建议采用。
Nginx的https功能是基于ngx_http_ssl_module模块来实现的,这个模块默认不安装,需要手动装以下。
第一步:查看nginx是否安装了ssl模块,在nginx目录执行(注意,如果报“未找到命令”,说明nginx没有配环境变量)
nginx -v
我的执行结果如下
configure arguments后面如果有值,如上图,就说明安好了;没有的话接着往下看。
依次执行以下命令安装ssl模块
./configure --with-http_ssl_module
make
make install
https协议是在http协议的基础上,出于数据安全的考虑、对传输数据进行加密传输的一种协议。
https是通过网景公司设计的SSL(Secure Sockets Layer)协议对数据进行加密的;换句话说,https协议由ssl+http协议构建而成。
所以,要发送https请求,需要通过ssl对数据进行加密;
要接收https请求,也需要通过ssl协议对数据进行解密。
另一个概念,CA(Certificate Authority,证书授权/授权证书),可以把它理解为电子证书;相当于生活中的“门禁卡”、“工卡”、“会员卡”。
在ssl协议下,只有使用相同的ca证书,才能对ssl加密数据进行解密。
通用的ca证书需要向GoDaddy或VeriSign等大机构申请,需要一定费用;如果是不知名或本土机构颁发的ca证书,需要专门安装这些ca证书才能和通用的证书一样解析对应的ssl加密数据。
先说比较顺利的情况,先联系公司的运维,看看能不能提供可用的ca证书。
比如,运维提供,abc.com的ca证书(包括公钥abc.com.pem,私钥abc.com.key两个文件),把ca证书放到服务器专门的文件夹下,比如/usr/local/nginx/ssl_key,接着做如下配置
专门开几个子配置文件(有几个端开几个,比如有客户端、管理端,因为两个端域名不同,就新建两个子配置)。
举例,我这个系统有客户端,测试域名设置为buyertest.abc.com
(二级域名、顶级域名要与ca证书匹配,三级域名自定义,尽量见名知义),那么对应的子配置文件需要这样配置
server {
listen 80;
listen 443 ssl;
server_name buyertest.abc.com;
ssl_certificate /usr/local/nginx/ssl_key/abc.com.pem;
ssl_certificate_key /usr/local/nginx/ssl_key/abc.com.key;
ssl_session_cache shared:SSL:20m;
ssl_session_timeout 10m;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
# 用户端
location / {
root /webserver/javashop/ui/buyer;
try_files $uri $uri/ /index.html $uri/ =404;
index index.html index.htm;
add_header X-Content-Type-Options 'nosniff';#禁止嗅探文件类型
add_header Content-Security-Policy "script-src 'self' 'unsafe-eval' 'unsafe-inline' 192.168.191.51:7000";#只允许同源下的js
add_header X-XSS-Protection "1; mode=block";#开启XSS过滤器
}
}
配置说明:
https请求的默认端口是443,所以同时监听80和443端口;
如果是http请求(80端口),nginx会作正常的处理(不用解密),然后指到对应的静态文件;
如果是https请求(443端口),nginx会通过ssl、ca证书对数据先进行解密,然后指到静态文件;
listen 80;
listen 443 ssl;
监听域名,当然是我们设置的客户端域名
server_name buyertest.abc.com;
给nginx的ssl模块配置ca证书,即配置公钥、私钥
ssl_certificate /usr/local/nginx/ssl_key/abc.com.pem;
ssl_certificate_key /usr/local/nginx/ssl_key/abc.com.key;
再往下ssl的配置,我都是采用查到的同一配置,大家也可以参照写一样
ssl_session_cache shared:SSL:20m;
ssl_session_timeout 10m;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
最后的localhost配置,也和普通的前端项目nginx配置一样
配置完成后重启nginx,
至此,nginx接收https请求就以配置完毕!
注意,因为我们的域名是自己编的、公网解析不了,所以我们需要在测试的电脑上,配置一下host文件
buyer.abc.com 服务器ip
大功告成!
一般来说,后端使用同一个域名,所以配一个nginx子配置文件即可。
与前端不同的地方在于,前端请求nginx最终将给它指到静态文件(页面),而后端请求nginx最终通过反向代理指到对应接口。
和上边一样,先给后端编一个测试域名apitest.abc.com
(二级域名、顶级域名要与ca证书匹配,三级域名自定义,尽量见名知义),nginx子配置如下:
server {
listen 80;
server_name apitest.abc.com;
rewrite ^(.*)$ https://$host$1 permanent;
access_log /backup/logs/nginx/kmapitest.haoyisheng.com-access.log main;
}
server {
listen 443 ssl;
server_name apitest.abc.com;
ssl_certificate /usr/local/nginx/sslkey/abc.com.pem;
ssl_certificate_key /usr/local/nginx/sslkey/abc.com.key;
ssl_session_timeout 5m;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers AESGCM:ALL:!DH:!EXPORT:!RC4:+HIGH:!MEDIUM:!LOW:!aNULL:!eNULL;
ssl_prefer_server_ciphers on;
client_max_body_size 10M;
location /buyer {
proxy_pass http://xxx:7002;
proxy_set_header Host $host:$server_port;
proxy_pass_header User-Agent;
}
location /seller {
proxy_pass http://xxx:7003;
proxy_set_header Host $host:$server_port;
proxy_pass_header User-Agent;
}
location /admin {
proxy_pass http://xxx:7004;
proxy_set_header Host $host:$server_port;
proxy_pass_header User-Agent;
}
access_log /backup/logs/nginx/apitest.abc.com-access.log main;
}
配置说明:
我们这里要求,后端只能接受https请求,对于http请求就需要对其进行强转、也就是重定向:
server {
listen 80;
server_name apitest.abc.com;
rewrite ^(.*)$ https://$host$1 permanent;
access_log /backup/logs/nginx/kmapitest.haoyisheng.com-access.log main;
}
对于nginx的ssl模块配置,与前端相同,这里就不赘述了。
最后,和前端不同,要把请求通过反向代理,指到对应接口(接口可以是装nginx的本服务器的、也可以是其他可访问服务器的)
location /buyer {
proxy_pass http://xxx:7002;
proxy_set_header Host $host:$server_port;
proxy_pass_header User-Agent;
}
location /seller {
proxy_pass http://xxx:7003;
proxy_set_header Host $host:$server_port;
proxy_pass_header User-Agent;
}
location后面要配置/模块名,要求后端必须配置springbootserver:servlet:context-path,location后模块名要与之对应,以客户端配置为例,yml配置如下:
nginx的location后就配置/buyer
。
再说一下不顺利的情况,网上说、可用通过以下步骤,申请自签ca证书,再服务器依次执行以下命令(有的命令需要填配置,公司名啊、要申请的域名啊,等等),执行完成后,最终会获得两个文件:
xxx.crt文件
xxx.key文件
通过自签证书,也能实现接收https请求(测试环境);其中crt文件,和上面pem公钥文件一样。
我没有实操过,把命令记录在下边,大家需要可用去试试
# 使用openssl生产服务端、客户端私钥
openssl genrsa -out server.key 1024
openssl genrsa -out client.key 1024
# 使用openssl生产服务端、客户端公钥
openssl rsa -in client.key -pubout -out client.pem
openssl rsa -in server.key -pubout -out server.pem
# 生成ca私钥
openssl genrsa -out ca.key 1024
# 生成ca-csr请求文件
openssl req -new -key ca.key -out ca.csr
# 生成ca-crt证书
openssl x509 -req -in ca.csr -signkey ca.key -out ca.crt
# 生成客户端、服务器csr请求文件
openssl req -new -key server.key -out server.csr
openssl req -new -key client.key -out client.csr
# 生成客户端、服务端crt证书
openssl x509 -req -CA ca.crt -CAkey ca.key -CAcreateserial -in server.csr -out server.crt
openssl x509 -req -CA ca.crt -CAkey ca.key -CAcreateserial -in client.csr -out client.crt
# 生成nginx需要的私钥和crt证书
openssl rsa -in server.key -out server_nginx.key
openssl x509 -req -days 3650 -in server.csr -signkey server_nginx.key -out server_nginx.crt
最后使用server_nginx.crt 和 server_nginx.key 这两个文件。
之后配置如下:
server {
listen 8061 ssl;
server_name hlhk.com;
ssl_certificate /root/new_cert/server_nginx.crt;
ssl_certificate_key /root/new_cert/server_nginx.key;
# 后面配置与前面2)一样,省略
}