标签 Nginx 下的文章

没有题头。

搭建CA

关于该部分,以后会详细阐述(大概,因为openssl有点难用),如果仍需接着实现本文内容,可以使用XCA这个图形化工具。

https://www.hohnstaedt.de/xca/

已更新,参见:

使用

只需要两行命令(http中):

# 假设你已经开启了https
# 客户端证书所属的根/中间CA证书
ssl_client_certificate /etc/nginx/ssl_cert/email_ca.crt;
# 开启客户端验证
ssl_verify_client on;
# 指定crl列表
ssl_crl /etc/nginx/ssl_cert/email_ca.crl;
#error_page 400 /req_cert.html;

400.jpg

(猫猫来源: https://http.cat/)

重启即可生效。不过只实现了简单的验证,后端无法知道客户端证书的主题信息,这时需要使用ngx_stream_ssl_module模块内置的一些变量,将它附加到头上传给后端即可。

#proxy_set_header ...;
# 客户端证书主题信息
add_header SSL_CLIENT_CERT_DN $ssl_client_s_dn;
# 判断验证状态(SUCCESS)
add_header SSL_CLINET_VERIFY $ssl_client_verify;

21082501.png

21082502.png

可以看到属性是以,分隔的。

ngx_stream_ssl_module模块变量表

(1.11.8起完全支持)

变量名说明
$ssl_cipher返回当前使用的加密套件
$ssl_ciphers返回使用客户端所支持的加密套件(已知名称列出,未知以十六进制显示)
$ssl_client_cert返回pem格式的客户端证书(除第一行外其余行末尾均有制表符)
$ssl_client_fingerprint返回客户端证书的sha1指纹
$ssl_client_i_dn返回客户端证书颁发者主题信息
$ssl_client_raw_cert返回pem格式的客户端证书
$ssl_client_s_dn返回客户端证书使用者主题信息
$ssl_client_serial返回客户端证书序列号
$ssl_client_v_end返回客户端证书截止日期
$ssl_client_v_remain返回客户端证书距离截止的天数
$ssl_client_v_start返回客户端证书颁发日期
$ssl_client_verify返回客户端证书验证状态("SUCCESS"/"FAILED:reason"/"NONE")
$ssl_curves返回客户端支持的ECC算法套件(已知名称列出,未知以十六进制显示)
$ssl_protocol返回已建立的ssl连接的协议
$ssl_server_name返回请求的sni名称
$ssl_session_id返回当前ssl会话标识符
$ssl_session_reusedssl会话复用标识(复用"r"/未复用".")

网上大部分是修改源码来实现,但我不喜欢折腾源码编译,所以使用了一些神奇的方法。

Nginx(Debian:nginx-full)中有两个神奇的指令,add_before_bodyadd_after_body,分别用来在响应内容前后插入内容,所以只要注入到末尾,用Javascript处理就行了。

location / {
    # ......
    add_after_body .uindex.html;
}

在目录下创建一个隐藏文件.uindex.html:

<!-- uindex  -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js" integrity="sha512-894YE6QWD5I59HgZOGReFYm4dnWc1Qt5NtvYSaNcOP+u1T9qYdvdihz0PPSiiqn/+/3e7Jo4EaG7TubfWGUrMQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.4.1/css/bootstrap.min.css" integrity="sha512-Dop/vW3iOtayerlYAqCgkVr2aTr2ErwwTYOvRFUpzl2VhCMJyjQF0Q9TjUXIo6JhuM/3i0vVEt2e/7QQmnHQqw==" crossorigin="anonymous" referrerpolicy="no-referrer" />
<script>
let dirlist = $("pre").html().split("\n");
function getFileInfo(line) {
    line = line.replace('    ', '|');
    line = line.replace('<a href="', '');
    line = line.replace('">', '|');
    line = line.replace('</a>', '|');
    line = line.split('|');
    temp = [];
    for(i in line) {
        if(line[i] != "") temp.push(line[i].replace(/(^\s*)|(\s*$)/g, ""));
    }
    line = null;
    //if(temp.length < 3) return [];
    return temp;
}
function outputHtml(dirlist) {
    let list = [];
    for(i in dirlist) {
        let temp = getFileInfo(dirlist[i]);
        if(temp) list.push(temp);
    }
    let temp = '';
    list.forEach((i) => {
        if(i.length >= 2) {
            if(i.length > 3) {
                temp += `<tr><td><a href="${i[0]}">${i[1]}</a></td><td>${i[2].replace(' -','')}</td><td>`;
                temp += `${i[3]}</td></tr>`;
            } else if(i.length == 2) {
                temp += `<tr><td><a href="${i[0]}">${i[1]}</a></td><td></td><td></td></tr>`;
            } else {
                temp += `<tr><td><a href="${i[0]}">${i[1]}</a></td><td>${i[2].replace(' -','')}</td><td>`;
                temp += `</td></tr>`;
            }
        }
    });
    return `<div class="container"><div class="row"><div class="col-md-12"><table class="table table-striped table-hover">
  <caption class="h3">${$("h1").text()}</caption>
  <thead><tr><th>fileName</th><th>date</th><th>size</th></tr></thead><tbody>${temp}</tbody></table></div></div></div><p style="text-align:center;font-size:14px;color:#dddddd;text-align:center;font-size:14px;color:#dddddd;">&copy; 2021 uindex.</p>`;
}
$(() => {
    //$("body").css({"display": "none"})
    let html = outputHtml(dirlist);
    $("h1").remove();
    $("hr").remove();
    $("pre").remove();
    $("body").append(html);
    //$("body").css({"display": "inline"})
});
</script>

不会正则,所以先替换再分割了一遍,旨在能用就行。使用时会一闪一下,正常。

21081701.png

一般情况下Nginx自带两个有关于鉴权的模块,一个是auth_basic,另一个是auth_request,本文主要介绍auth_request

流程

21080701.png

Nginx的配置

(server段中,也可以做成一个配置文件,通过include引用)

#设置401错误跳转,用于跳转到登录页
location @error401 {
    #这里的url方便我们登录成功的跳转
    return 302 https://auth.example.com/?url=https://$http_host$request_uri;
}
#内部路由
location /auth {
    internal;
    #鉴权服务器地址
    proxy_pass http://127.0.0.1:8888/auth;
    #不传递body内容,当然请求头会被传递
    proxy_pass_request_body off;
    proxy_set_header Content-Length "";
}

(location段中)
location / {
    #指定内部路由与返回401的处理
    auth_request /auth;
    error_page 401 = @error401;
}

鉴权验证

401.jpg
(猫猫来源: https://http.cat/)

Nginx会通过所返回的状态码来判断是否放行请求,返回2XX放行,返回4XX则拦截请求,我们可以通过设置cookie来保证登录态的持续:

(Python3)
from sanic import Sanic, html, json, redirect
app = Sanic(__name__)

password = "19260817"

@app.get("/")
async def index(request):
    return html("<form action="" method="POST"><input name="pw"><button type="submit">check</button></form>")

@app.post("/")
async def vindex(request):
    pw = request.form.get("pw", None)
    url = request.args.get("url", None)
    if pw == password:
        res = redirect(url)
        res.cookies["login"] = True
        res.cookies["login"]["httponly"] = True
        #全域cookie
        res.cookies["login"]["domain"] = ".example.com"
        return res
    return redirect("/")

@app.get("/auth")
async def auth(request):
    login = request.cookies.get("login", False)
    if login:
        return json({"code": 200}, 200)
    return json({"code": 401}, 401)

if __name__ == "__main__":
    app.run(port=8888)

由MaxMind提供,有ASN,国家与市三种类型,支持IPv4与IPv6,使用mmdb或CSV格式分发。
注意:自2019年12月30起,需要注册账号下载。
https://dev.maxmind.com/geoip/geoip2/geolite2/

Python的使用

安装官方提供的模块:

pip3 install geoip2

示例:

import geoip2.database
gi = geoip2.database.Reader('GeoLite2-Country.mmdb')
gn = gi.country('1.1.1.1')
#city,country,asnisp
#print(gn)
print(gn.country.names['zh-CN'])
print(gn.country.iso_code)
#输出:
澳大利亚
AU

Nginx的使用

Nginx需要编译Geoip2模块(有Geoip模块,但只适用旧版的dat格式)。
https://github.com/leev/ngx_http_geoip2_module

(http)
geoip2 /etc/nginx/geoip2/GeoLite2-Country.mmdb {
    #自动重载
    auto_reload 5m;
    $geoip2_metadata_country_build metadata build_epoch;
    #国家代码
    $geoip2_country_code default=US country iso_code;
    #国家名称
    #$geoip2_country_name country names zh-CN;
}
if($geoip2_country_code != CN) {
    deny all;
}

注意替换example.com与证书路径!
配置如下:

server {
    listen [::]:443;
    listen 443;
    server_name duckgo.example.com;
    ssl on;
    ssl_certificate /root/ssl_cert/duck.crt;
    ssl_certificate_key /root/ssl_cert/duck.key;
    sub_filter_types text/html text/css application/x-javascript text/xml;
    access_log off;

    location / {
        proxy_pass https://duckduckgo.com;
        proxy_set_header Host duckduckgo.com;
        proxy_set_header Accept-Encoding "";
        proxy_set_header Referer "";
        sub_filter "duckduckgo.com" "duckgo.example.com";
        sub_filter_once off;
    }
}

server {
    listen [::]:443;
    listen 443;
    server_name images.duckgo.example.com;
    ssl on;
    ssl_certificate /root/ssl_cert/duck.crt;
    ssl_certificate_key /root/ssl_cert/duck.key;
    access_log off;

    location / {
        proxy_pass https://images.duckduckgo.com;
        proxy_set_header Host images.duckduckgo.com;
    }
}

server {
    listen [::]:443;
    listen 443;
    server_name icons.duckgo.example.com;
    ssl on;
    ssl_certificate /root/ssl_cert/duck.crt;
    ssl_certificate_key /root/ssl_cert/duck.key;
    access_log off;

    location / {
        proxy_pass https://icons.duckduckgo.com;
        proxy_set_header Host icons.duckduckgo.com;
    }
}

server {
    listen [::]:443;
    listen 443;
    server_name ac.duckgo.example.com;
    ssl on;
    ssl_certificate /root/ssl_cert/duck.crt;
    ssl_certificate_key /root/ssl_cert/duck.key;
    access_log off;

    location / {
        proxy_pass https://ac.duckduckgo.com;
        proxy_set_header Host ac.duckduckgo.com;
    }
}

server {
    listen [::]:443;
    listen 443;
    server_name external-content.duckgo.example.com;
    ssl on;
    ssl_certificate /root/ssl_cert/duck.crt;
    ssl_certificate_key /root/ssl_cert/duck.key;
    access_log off;

    location / {
        proxy_pass https://external-content.duckduckgo.com;
        proxy_set_header Host external-content.duckduckgo.com;
    }
}
server {
    listen [::]:443;
    listen 443;
    server_name improving.duckgo.example.com;
    ssl on;
    ssl_certificate /root/ssl_cert/duck.crt;
    ssl_certificate_key /root/ssl_cert/duck.key;
    access_log off;

    location / {
        proxy_pass https://improving.duckduckgo.com;
        proxy_set_header Host improving.duckduckgo.com;
    }
}

安装

多数的文章都是从头编译安装的,但是自Nginx 1.9(具体不知道)后,插件可以热加载,不再需要一开始就要编译入Nginx中,使用我们可以使用包管理器来快速安装:

(Debian/Nginx) sudo apt-get install nginx-full libnginx-mod-rtmp -y

配置

打开/etc/nginx/nginx.conf的配置文件,在末尾加入:

rtmp {
    server {
        listen 1926;
        chunk_size 4096;
        application live {
            #开启直播
            live on;
            #开启HLS
            #hls on;
            #HLS视频流目录
            #hls_path /var/www/html/live;
            #每个视频片段长度
            #hls_fragment 5s;
            #播放列表长度
            #hls_playlist_length 15s;
            #连续模式
            #hls_continuous on;
            #删除多余流片段
            #hls_cleanup on;
            #嵌套模式
            #hls_nested on;
        }
    }
}

若需要HLS,则需要在HTTP配置文件中添加:

#root /var/www/html/;
#这里的路径应与上方视频流目录一致
location /live {
            types {
                    application/vnd.apple.mpegurl m3u8;
                    video/mp2t ts;
            }
            alias /var/www/html/live;
 }

推流

可以使用FFmpeg,但我这里使OBS

rtmp://<your_device_ip>:1926/live

HLS

将以下HTML保存到服务器,可通过游览器查看推流效果:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" name="viewport" content="width=device-width,initial-scale=1" />
    <title>HLS Live Test</title>
    <script src="https://cdn.bootcdn.net/ajax/libs/hls.js/8.0.0-beta.3/hls.min.js"></script>
</head>
<body>
        <video id="video" controls width="100%"></video>
        <script>
            var video = document.getElementById('video');
            var hls = new Hls();
            hls.loadSource('http://<your_device_ip>/live/index.m3u8');
            hls.attachMedia(video);
            hls.on(Hls.Events.MANIFEST_PARSED,function() {
            video.play();
            });
        </script>
</body>
</html>

访问验证解决方案(

使用

Nginx自带,无需编译配置。

#适用于http,server,location段中
#默认off,当启用时一般填字符串,该字符串会显示在验证弹窗中
auth_basic 'Server Info';
#存储用户验证信息的文件(htpasswd)
auth_basic_user_file htpasswd;

添加一个用户

你设置的htpasswd文件位置是以Nginx的配置文件目录为根目录,比如只填写htpasswd,那么该文件应当在/etc/nginx目录下。
该文件内容如下:

username:password

password需要转换为不可逆的密文,使用openssl生成:

openssl passwd yourpassword

添加一个测试用户于文件中:

test:HZGvodhKflZRk

效果

93_0.PNG

Nginx("engine x")是一款是由俄罗斯的程序设计师Igor Sysoev所开发高性能的 Web和 反向代理 服务器,也是一个 IMAP/POP3/SMTP 代理服务器。

安装

通过包管理器安装

(debain/ubuntu) sudo apt-get install nginx

编译安装

有人写了一键安装脚本,安装时间稍微长一些,如果没有特殊需求的话,一般使用包管理器提供的版本,尽管不是最新,但一般情况下很稳定。
https://lnmp.org/auto.html

基础配置

示例版本:nginx/1.14.2

重启Nginx:(debain/ubuntu) sudo servcie nginx restart

基础网站配置

所有的网站配置都在/etc/nginx/sites-available/(通过包管理器安装的,如果编译安装,请查看编译配置)中,默认有一个default配置
我们把注释删减下,得到如下的配置:

server {
    #监听端口 ipv4&ipv6
    listen 80;
    listen [::]:80;
    #网站根目录
    root /var/www/html;
    #主页
    index index.html index.htm index.nginx-debian.html;
    #服务器名称,如果有多个站点,这里填写你的域名
    server_name _;
    #路径配置块
    location / {
        #404配置,寻找文件,先寻找文件后寻找目录
        try_files $uri $uri/ =404;
    }
}

PHP配置

做站怎么能没有PHP,在配置中添加:

location ~ \.php$ {
    #注意你的PHP版本,这里配置的是php7.3-fpm
    include snippets/fastcgi-php.conf;
    fastcgi_pass unix:/run/php/php7.3-fpm.sock;
}

编译安装的这里会报错,因为你没有snippets/fastcgi-php.conf这个配置文件(这个配置中又引用了其他配置,记得全部拷贝回来), 你可以在编译前从包管理器的版本中拷贝一份,记得删除包管理器版本。

安全配置

现在的站点基本配有SSL,我们也要想办法装上一个

获取证书

你可以在这搞一张证书:https://freessl.cn/
当然也可以买一张便宜的证书,一般是Sectigo(Comodo),的比较便宜,一年5刀

SSL配置

在1.11.0之后的版本中,支持RSA与ECC双证书,填写两张证书路径,配置优先ECC算法即可。

#将你的监听端口改为:
listen 443 ssl;
listen [::]:443 ssl;
#在老版本中,启用ssl的配置为:
#ssl on;
#而不是在监听端口后加ssl
#证书/私钥配置(目录为你的证书存放目录)
ssl_certificate /etc/nginx/ssl_cert/main.crt;
ssl_certificate_key /etc/nginx/ssl_cert/main.key;
#其他ssl配置
ssl_session_timeout 5m; #会话过期时间
ssl_protocols TLSv1 TLSv1.1 TLSv1.2; #支持握手协议(通常使用TLSv1.2)
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE; #优先选择算法
ssl_prefer_server_ciphers on;  #启用服务器偏好配置

跳转设置

将http访问全部变为https访问:

#在上一个server{...}后新建:
server {
    listen 80;
    server_name yourdomain;
    rewrite ^(.*) https://$server_name$1 permanent;
}

关闭服务器token

Nginx会在响应头标识服务器版本,我们要关掉它(在/etc/nginx/nginx.conf(通过包管理器安装的,如果编译安装,请查看编译配置) 中):

#http段中
server_tokens off;

禁止访问

#禁止访问passwd路径(路径支持正则)下的所有文件
location /passwd {
    deny all;
}

优化配置

gzip压缩

使用gzip压缩来减少传输时间,将/etc/nginx/nginx.conf(通过包管理器安装的,如果编译安装,请查看编译配置)中关于gzip配置注 释删除即可。

gzip_vary on; #启用压缩标识
gzip_proxied any; #反向代理时,无条件启用压缩
gzip_comp_level 6; #压缩等级
gzip_buffers 16 8k; #缓存空间大小
gzip_http_version 1.1; #版本
gzip_types text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript; #启用压缩的文件类型

使用优化模型

#配置头直接填写
worker_processes auto; #自动配置工作进程数量
#worker_cpu_affinity 01 10 01 10; #不同进程绑定不同逻辑cpu(四进程双核示例)
worker_rlimit_nofile 65535; #Nginx能打开最大的文件描述符(Linux好像为65535)
#events段中
use epoll; #使用epoll轮询处理
# kqueue | rtsig | epoll | /dev/poll | select | poll (Nginx支持的事件模型)
worker_connections 1024; 每个进程最大的连接数

其它应用

反向代理

恰饭链接

浏览目录(文件服务器)

再次恰饭链接

后续

现在你可以开始关心网站的SEO,以及前端优化了。