07 . Nginx常用模块及案例

访问控制

用户访问控制

ngx_http_auth_basic_module

有时我们会有这么一种需求,就是你的网站并不想提供一个公共的访问或者某些页面不希望公开,我们希望的是某些特定的客户端可以访问。那么我们可以在访问时要求进行身份认证,就如给你自己的家门加一把锁,以拒绝那些不速之客。我们在服务课程中学习过apache的访问控制,对于Nginx来说同样可以实现,并且整个过程和Apache 非常的相似。

#1.建立口令文件
htpasswd -c /etc/nginx/passwd.db user1
New password:
Re-type new password:
Adding password for user user1
#2.实现认证
vim default.conf
    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
        auth_basic "hello";                                # 这一行是你登录窗口写的提示文字
        auth_basic_user_file /etc/nginx/passwd.db;         # 密码存放的目录
    }
    
# htpasswd -c选项只有第一次没有密码文件的时候需要添加,第二再添加账户的时候,不需要跟-c
systemctl reload nginx

# 用户认证局限性
# 1. 用户信息依赖文件方式
# 2. 用户管理文件过多,无法联动
# 3. 操作管理机械,效率低下

# 解决办法
# 1. Nginx结合LUA实现高效验证
# 2. Nginx结合LDAP利用nginx-auth-ldap模块

访问测试
重新加载配置文件后如果用elinks,curl访问会报错401,需要授权,需要用浏览器访问.

主机访问控制

ngx_http_access_module

    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
        # auth_basic "hello";
        # auth_basic_user_file /etc/nginx/passwd.db;
        allow 39.108.140.0;  #只允许此IP访问
        deny all;	     	 #拒绝所有
    }
elinks --dump 49.233.69.195
                                 403 Forbidden

状态访问

ngx_http_stub_status_module

使用第三方的软件监控nginx时,需要保证其本身能显示状态信息,然后利用这些数据显示一些监控数据和生成图表,使用该模块需要在编译nginx时启用该模块.

# 在server中加入以下几行
    location ~ /status {
        stub_status on;
        access_log off;            # 状态模块的访问不记录日志
        allow 39.108.140.0;        # 只允许管理员访问
        deny all;                 
    }
curl 49.233.69.195/status
Active connections: 1             # Nginx当前活跃连接数
server accepts handled requests
14 14 14      
Reading: 0 Writing: 1 Waiting: 0
            
#第一个14:	为一共处理的多少个连接请求
#第二个14:	为创建多少次握手
#第三个14:	为总处理了多少请求    

# Active connections        对后端发起的当前活动连接数
# server accepts handled requests:
# server:    Nginx总共处理了14个连接, 表示Nginx处理接收握手总次数

#    成功创建了14次握手,也就是成功的连接数connection     失败连接=(总连接数-成功连接数) (相等表示中间没有失败的)
#    总共处理了14个请求数requests (server和client建立一条连接之后开始请求数据,每有一个请求此数字会+1)

# request:                   http请求,GET/POST/DELETE/UPLOAD(指的是请求资源),默认情况下下一个请求需要一个连接,但是如果开启了长连接,那么一个连接可以对应多个请求.

# Reading:                   nginx读取到客户端的Header信息数,请求头,Nginx读取数据
# Waiting:                    nginx返回给客户端的Header信息数,响应头,如果这个值飙升,说明后面的节点挂掉了,例如数据库等. Nginx写的情况
# Waiting:                   开启keep-alive的情况下,这个值等于active - (reading + writing),意思就是Nginx说已经处理完正在等候下一次请求指令的驻留连接.处理完等待下次请求的数量, 通俗点就是即没有读也没有写,建立连接情况.
# 一般监控都是通过此模块监控状态比如蓝鲸,zabbix;如果需要监控进程和端口需要

网站过滤

替换网站响应内容: ngx_http_sub_module

server里面加入下面两行代码.
    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
        sub_filter nginx 'flying';        # 默认只替换第一个nginx为flying,如果有多的nginx是不会替换的.
    }

    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
        sub_filter nginx 'flying';
        sub_filter_once off;             # 替换全部nginx为flying
    }

实用用法

如果我们用模板生成网站的时候,因为疏漏或者别的原因造成代码不如意,但是此时因为文件数量巨大,不方便全部重新生成,那么这个时候我们就可以用此模块来暂时实现纠错,另一方面,我们也可以利用这个实现服务器端文字过滤的效果.

Nginx流量控制(连接和请求)

http协议的连接与请求

HTTP是建立在TCP, 在完成HTTP请求需要先建立TCP三次握手(称为TCP连接),在连接的基础上再HTTP请求

HTTP请求建立在一次TCP连接基础上,一次TCP请求至少产生一次HTTP请求

连接频率限制限制模块

ngx_http_limit_conn_module
(nginx的连接频率限制模块可以根据定义的key来限制每个键值的连接数.)

# Syntax: limit_req_zone key zone=name:size rate=rate;(设置速率每秒多少个请求)
# Default: —
# Context: http

# Syntax: limit_req zone=name [burst=number] [nodelay];(burst可以设置前多少个请求不限制,也可以设置是否延迟访问)
# Default: —
# Context: http, server, location

# Example:
yum -y install httpd-tools

vim /etc/nginx/nginx.conf
http {
limit_req_zone $binary_remote_addr zone=req_zone:10m rate=1r/s;

vim /etc/nginx/conf.d/default.conf
    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
        limit_req zone=req_zone;
       #limit_req zone=req_zone burst=5;            #5个请求时不限制的,但有延迟
       #limit_req zone=req_zone burst=5 nodelay;    #无延迟,只要拿到令牌就可以处理
    }

ab  -n 100 -c 10 http://39.108.140.0/
Time taken for tests:   0.940 seconds                     # 请求花费的总时间
Complete requests:      100
Failed requests:        99                                # 请求了100,失败了99
   (Connect: 0, Receive: 0, Length: 99, Exceptions: 0)    
Write errors:           0
Non-2xx responses:      99                                # 非200请求99个

连接限制没有请求限制有效?

我们前面说过, 多个请求可以建立在一次的TCP连接之上, 那么我们对请求的精度限制,当然比对一个连接的限制会更加的有效。
因为同一时刻只允许一个连接请求进入。
但是同一时刻多个请求可以通过一个连接进入。
所以请求限制才是比较优的解决方案。

防盗链

图片防盗链

防止网站资源被盗用 ngx_http_referer_module

# 盗链是本网站的url显示其他网站图片,而直接用它的URL是转发,宣传的也是人家的URL.
# 主机proxy准备一张图片放到根目录
mv 1.png  /usr/share/nginx/html/

# 主机rearend修改index.html
cat /usr/share/nginx/html/index.html
<html>
<head>
    <meta charset="utf-8">
    <title>hello everyone</title>
</head>
<body style="background-color:red;">
    <img src="http://39.108.140.0/1.png"/>
</body>
</html>

此时用windows10谷歌浏览器访问rearend主机,但是日志在proxy主机上,消耗资源也是proxy主机

tail -1 /var/log/nginx/access.log
114.242.249.222 - - [03/Nov/2019:17:00:31 +0800] "GET /1.png HTTP/1.1" 304 0 "http://49.233.69.195/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.87 Safari/537.36" "-"

修改proxy主机配置文件

vim /etc/nginx/conf.d/default.conf
    location ~ \.(img|png|gif)$ {
        root /usr/share/nginx/html/images;
        index index.html;
    valid_referers none blocked *.youmen.cn server_names ~youmen  ~\.google
\. ~baidu;
    if ($invalid_referer) {
        return 403;
    }
}
systemctl reload nginx

此时再用浏览器访问rearend主机就会显示一张破碎的图片标志,图片资源也加载不出来

如何区分哪些是不正常的用户?

http referer是header的一部分,当浏览器向web服务器发送请求的时候,一般会带上referer,告诉服务器我是从那个页面链接过来的,服务器借此可以获得一些信息用于处理,例如防止未经允许的网站盗链图片、文件等,因此Http Referer头信息是可以通过程序伪装生成的,所以通过Referer信息防盗链并非100%可靠,但是他能限制大部分盗链情况。

屏蔽.git等文件
location ~ (.git|.gitattributes|.gitignore|.svn) {
    deny all;
}

正则拦截指定url请求

location / {
  if ($request_uri ~* ^/\?http(.*)$) {
         return 404;
     }
  }
// or
        location /actuator/health {
          return 200;
        }

Nginx下载站点

Nginx默认不允许整个目录浏览下载

如果需要开启,修改一下配置文件即可

autoindex常用参数

autoindex_exact_size off;
# 默认为on, 显示出文件的确切大小,单位是bytes。
# 修改为off,显示出文件的大概大小,单位是kB或者MB或者GB。
autoindex_localtime on;
# 默认为off,显示的文件时间为GMT时间。
# 修改为on, 显示的文件时间为文件的服务器时间。
charset utf-8,gbk;
# 默认中文目录乱码,添加上解决乱码。
开启全站目录浏览器功能(http下面添加如下内容)
autoindex on;  # 开启目录文件列表
autoindex_exact_size on;  # 显示出文件的确切大小,单位是bytes
autoindex_localtime on;  # 显示的文件时间为文件的服务器时间
charset utf-8,gbk,gb2312;  # 避免中文乱码

add_header Content-Disposition attachment;  # 文件下载(可以不开启该功能)
开启网站部分目录浏览器功能
# 编辑nginx.conf,在Server{}模块或者localtion模块加入以下内容:
autoindex on;  # 开启目录文件列表
autoindex_exact_size on;  # 显示出文件的确切大小,单位是bytes
autoindex_localtime on;  # 显示的文件时间为文件的服务器时间
charset utf-8,gbk,gb2312;  # 避免中文乱码

ngxtop监控

使用ngxtop实时解析nginx访问日志,并且将处理结果输出到终端,功能类似于系统命令top。所有示例都读取nginx配置文件的访问日志位置和格式。如果要指定访问日志文件和/或日志格式,请使用-f和-a选项。

注意:在nginx配置中/usr/local/nginx/conf/nginx.conf日志文件必须是绝对路径。

或者使用GoAccess分析日志

https://www.cnblogs.com/you-men/p/13171065.html

# 安装 ngxtop
pip install ngxtop

# 实时状态
ngxtop
# 状态为404的前10个请求的路径:
ngxtop top request_path --filter 'status == 404'

# 发送总字节数最多的前10个请求
ngxtop --order-by 'avg(bytes_sent) * count'

# 排名前十位的IP,例如,谁攻击你最多
ngxtop --group-by remote_addr

# 打印具有4xx或5xx状态的请求,以及status和http referer
ngxtop -i 'status >= 400' print request status http_referer

# 由200个请求路径响应发送的平均正文字节以'foo'开始:
ngxtop avg bytes_sent --filter 'status == 200 and request_path.startswith("foo")'

# 使用“common”日志格式从远程机器分析apache访问日志
ssh remote tail -f /var/log/apache2/access.log | ngxtop -f common

跨域

在工作中,有时候会遇到一些接口不支持跨域, 这时候可以简单的添加add_headers来支持

server {
  listen 80;
  server_name api.xxx.com;
    
  add_header 'Access-Control-Allow-Origin' '*';
  add_header 'Access-Control-Allow-Credentials' 'true';
  add_header 'Access-Control-Allow-Methods' 'GET,POST,HEAD';

  location / {
    proxy_pass http://127.0.0.1:3000;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host  $http_host;    
  } 
}

上面通过更改头信息,还有一种,使用rewrite指令重定向URL来解决跨域问题

upstream test {
  server 127.0.0.1:8080;
  server localhost:8081;
}
server {
  listen 80;
  server_name api.xxx.com;
  location / { 
    root  html;                   #去请求../html文件夹里的文件
    index  index.html index.htm;  #首页响应地址
  }
  # 用于拦截请求,匹配任何以 /api/开头的地址,
  # 匹配符合以后,停止往下搜索正则。
  location ^~/api/{ 
    # 代表重写拦截进来的请求,并且只能对域名后边的除去传递的参数外的字符串起作用,
    # 例如www.a.com/proxy/api/msg?meth=1&par=2重写,只对/proxy/api/msg重写。
    # rewrite后面的参数是一个简单的正则 ^/api/(.*)$,
    # $1代表正则中的第一个(),$2代表第二个()的值,以此类推。
    rewrite ^/api/(.*)$ /$1 break;
    
    # 把请求代理到其他主机 
    # 其中 http://www.b.com/ 写法和 http://www.b.com写法的区别如下
    # 如果你的请求地址是他 http://server/html/test.jsp
    # 配置一: http://www.b.com/ 后面有“/” 
    #         将反向代理成 http://www.b.com/html/test.jsp 访问
    # 配置一: http://www.b.com 后面没有有“/” 
    #         将反向代理成 http://www.b.com/test.jsp 访问
    proxy_pass http://test;

    # 如果 proxy_pass  URL 是 http://a.xx.com/platform/ 这种情况
    # proxy_cookie_path应该设置成 /platform/ / (注意两个斜杠之间有空格)。
    proxy_cookie_path /platfrom/ /;

    # http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_pass_header
    # 设置 Cookie 头通过
    proxy_pass_header Set-Cookie;
  } 
}

跳转到带www的域上面

server {
    listen 80;
    # 配置正常的带www的域名
    server_name www.wangchujiang.com;
    root /home/www/wabg/download;
    location / {
        try_files $uri $uri/ /index.html =404;
    }
}
server {
    # 这个要放到下面,
    # 将不带www的 wangchujiang.com 永久性重定向到  https://www.wangchujiang.com
    server_name wangchujiang.com;
    rewrite ^(.*) https://www.wangchujiang.com$1 permanent;
}

代理转发

upstream server-api{
    # api 代理服务地址
    server 127.0.0.1:3110;    
}
upstream server-resource{
    # 静态资源 代理服务地址
    server 127.0.0.1:3120;
}
server {
    listen       3111;
    server_name  localhost;      # 这里指定域名
    root /home/www/server-statics;
    # 匹配 api 路由的反向代理到API服务
    location ^~/api/ {
        rewrite ^/(.*)$ /$1 break;
        proxy_pass http://server-api;
    }
    # 假设这里验证码也在API服务中
    location ^~/captcha {
        rewrite ^/(.*)$ /$1 break;
        proxy_pass http://server-api;
    }
    # 假设你的图片资源全部在另外一个服务上面
    location ^~/img/ {
        rewrite ^/(.*)$ /$1 break;
        proxy_pass http://server-resource;
    }
    # 路由在前端,后端没有真实路由,在路由不存在的 404状态的页面返回 /index.html
    # 这个方式使用场景,你在写React或者Vue项目的时候,没有真实路由
    location / {
        try_files $uri $uri/ /index.html =404;
        #                               ^ 空格很重要
    }
}
域名路径加不加都能正常访问
http://wangchujiang.com/api/index.php?a=1&name=wcj
                                  ^ 有后缀

http://wangchujiang.com/api/index?a=1&name=wcj
                                 ^ 没有后缀
    
# nginx rewrite规则如下
rewrite ^/(.*)/$ /index.php?/$1 permanent;
if (!-d $request_filename){
        set $rule_1 1$rule_1;
}
if (!-f $request_filename){
        set $rule_1 2$rule_1;
}
if ($rule_1 = "21"){
        rewrite ^/ /index.php last;
}

Nginx配置HTTPS

在之前步骤我们下载完证书后会得到一个文件,解压后会有两个文件

# 以.pem为后缀或文件类型的是证书文件
# 以.key位后缀或文件类型的是密钥文件

# 将这两个文件上传到我们nginx服务器
# 此处我的Nginx是yum装的,目录是/etc/nginx/

# 我们在这个目录创建一个cert目录
mkdir /etc/nginx/cert
# 并将我们上传的文件重命名为domain.name前缀
mv 3913414_www.zcj.net.cn.key domain.name.key
mv 3913414_www.zcj.net.cn.pem domain.name.pem

server {
    listen       80;
    server_name  zcj.net.cn zcj.net.cn;
    return 301 https://www.zcj.net.cn$request_uri;

    location / {
        root /usr/share/nginx/html;
        index index.html index.htm;
    }
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }
}

server {
    listen 443 ssl;
    ssl on;
    # 更换证书时候把此处文件替换掉重启服务就行了
    ssl_certificate  /etc/nginx/cert/domain.name.pem;
    ssl_certificate_key /etc/nginx/cert/domain.name.key;
    location / {
        root /usr/share/nginx/html;
        index index.html index.htm;
    }
}

systemctl reload nginx
ss -antp |grep nginx
LISTEN     0      128          *:80                       *:*                   users:(("nginx",pid=21376,fd=6),("nginx",pid=21375,fd=6))
LISTEN     0      128          *:443                      *:*                   users:(("nginx",pid=21376,fd=7),("nginx",pid=21375,fd=7))

# 打开浏览器再次访问即可看到用户和我们网站是https链接了   
[root@blog cert]# curl www.zcj.net.cn -I
HTTP/1.1 301 Moved Permanently
Server: nginx/1.16.1
Date: Thu, 14 May 2020 06:41:36 GMT
Content-Type: text/html
Content-Length: 169
Connection: keep-alive
Location: https://www.zcj.net.cn/

SSL配置

超文本传输安全协议(缩写:HTTPS,英语:Hypertext Transfer Protocol Secure)是超文本传输协议和SSL/TLS的组合,用以提供加密通讯及对网络服务器身份的鉴定。HTTPS连接经常被用于万维网上的交易支付和企业信息系统中敏感信息的传输。HTTPS不应与在RFC 2660中定义的安全超文本传输协议(S-HTTP)相混。HTTPS 目前已经是所有注重隐私和安全的网站的首选,随着技术的不断发展,HTTPS 网站已不再是大型网站的专利,所有普通的个人站长和博客均可以自己动手搭建一个安全的加密的网站。

创建SSL证书,如果你购买的证书,就可以直接下载

sudo mkdir /etc/nginx/ssl
# 创建了有效期100年,加密强度为RSA2048的SSL密钥key和X509证书文件。
sudo openssl req -x509 -nodes -days 36500 -newkey rsa:2048 -keyout /etc/nginx/ssl/nginx.key -out /etc/nginx/ssl/nginx.crt
# 上面命令,会有下面需要填写内容
Country Name (2 letter code) [AU]:US
State or Province Name (full name) [Some-State]:New York
Locality Name (eg, city) []:New York City
Organization Name (eg, company) [Internet Widgits Pty Ltd]:Bouncy Castles, Inc.
Organizational Unit Name (eg, section) []:Ministry of Water Slides
Common Name (e.g. server FQDN or YOUR name) []:your_domain.com
Email Address []:admin@your_domain.com
创建自签证书
首先,创建证书和私钥的目录
# mkdir -p /etc/nginx/cert
# cd /etc/nginx/cert
创建服务器私钥,命令会让你输入一个口令:
# openssl genrsa -des3 -out nginx.key 2048
创建签名请求的证书(CSR):
# openssl req -new -key nginx.key -out nginx.csr
在加载SSL支持的Nginx并使用上述私钥时除去必须的口令:
# cp nginx.key nginx.key.org
# openssl rsa -in nginx.key.org -out nginx.key
最后标记证书使用上述私钥和CSR:
# openssl x509 -req -days 365 -in nginx.csr -signkey nginx.key -out nginx.crt
编译nginx加入ssl模块
./configure --prefix=/usr/local/nginx --with-http_stub_status_module --with-http_ssl_module

# 运行完成之后还需要make(不用make install)

# 备份nginx的二进制文件
cp -rf /usr/local/nginx/sbin/nginx  /usr/local/nginx/sbin/nginx.bak
# 覆盖nginx的二进制文件
cp -rf objs/nginx   /usr/local/nginx/sbin/
HTTP Server
server {
    listen       443 ssl;
    server_name  localhost;

    ssl_certificate /etc/nginx/ssl/nginx.crt;
    ssl_certificate_key /etc/nginx/ssl/nginx.key;
    # 禁止在header中出现服务器版本,防止黑客利用版本漏洞攻击
    server_tokens off;
    # 设置ssl/tls会话缓存的类型和大小。如果设置了这个参数一般是shared,buildin可能会参数内存碎片,默认是none,和off差不多,停用缓存。如shared:SSL:10m表示我所有的nginx工作进程共享ssl会话缓存,官网介绍说1M可以存放约4000个sessions。 
    ssl_session_cache    shared:SSL:1m; 

    # 客户端可以重用会话缓存中ssl参数的过期时间,内网系统默认5分钟太短了,可以设成30m即30分钟甚至4h。
    ssl_session_timeout  5m; 

    # 选择加密套件,不同的浏览器所支持的套件(和顺序)可能会不同。
    # 这里指定的是OpenSSL库能够识别的写法,你可以通过 openssl -v cipher 'RC4:HIGH:!aNULL:!MD5'(后面是你所指定的套件加密算法) 来看所支持算法。
    ssl_ciphers  HIGH:!aNULL:!MD5;

    # 设置协商加密算法时,优先使用我们服务端的加密套件,而不是客户端浏览器的加密套件。
    ssl_prefer_server_ciphers  on;

    location / {
        root   html;
        index  index.html index.htm;
    }
}

爬虫过滤

根据 User-Agent 过滤请求,通过一个简单的正则表达式,就可以过滤不符合要求的爬虫请求(初级爬虫)。

~* 表示不区分大小写的正则匹配

location / {
    if ($http_user_agent ~* "python|curl|java|wget|httpclient|okhttp") {
        return 503;
    }
    # 正常处理
    # ...
}
posted @ 2019-11-18 19:30  常见-youmen  阅读(1055)  评论(0编辑  收藏  举报