Nginx双向认证

创建客户端需要的CA根目录

mkdir -p /etc/nginx/client_ca/{certs,private,newcerts,clientcerts}
cd /etc/nginx/client_ca
# 序列号文件,初始为01,为每个新证书分配唯一序列号
echo "01" > serial
# 证书数据库文件,记录已签发/吊销的证书信息
touch index.txt

CA配置文件

/etc/nginx/client_ca/openssl.cnf

vim /etc/nginx/client_ca/openssl.cnf

编辑文件写入以下内容

[ ca ]
default_ca = CA_default # 定义默认的 CA(证书颁发机构)配置段为 CA_default,当执行 openssl ca 命令时自动引用此配置。

[ CA_default ]
dir = /etc/nginx/client_ca # CA 的工作根目录,所有文件(密钥、证书等)存储于此路径。
certs = $dir/certs # 存放已签发的证书。
new_certs_dir = $dir/newcerts # 新签发证书的存放目录(按序列号命名)。
database = $dir/index.txt # 证书数据库文件,记录已签发/吊销的证书信息。
serial = $dir/serial # 序列号文件(需预置初始值如 01),为每个新证书分配唯一序列号。
RANDFILE = $dir/private/.rand # 随机数种子文件,增强密钥生成安全性

private_key = $dir/private/ca.key # CA 的私钥文件路径。
certificate = $dir/ca.crt # CA 的根证书文件路径。

default_days = 365 # 默认证书有效期(365 天)。
default_crl_days = 30 # 证书吊销列表(CRL)的默认有效期(30 天)。
default_md = sha256 # 默认摘要算法(SHA-256 哈希)。
preserve = no # 不保留证书请求中的 DN(Distinguished Name)字段顺序。
policy = policy_match # 引用 [ policy_match ] 段,定义 DN 字段的匹配规则。

[ policy_match ]
countryName = match # 国家代码(如 CN)必须与 CA 证书一致。
stateOrProvinceName = match # 州/省名称必须一致。
organizationName = match # 组织名称必须一致。
organizationalUnitName = optional # 部门名称可选(可不同于 CA)。
commonName = supplied # 必填字段,必须提供 Common Name(但不要求匹配 CA)。
emailAddress = optional # 邮箱地址可选。

[ req ]
default_bits = 2048 # 默认密钥长度(2048 位 RSA)。
default_keyfile = /etc/nginx/client_ca/private/ca.key # 默认私钥输出路径(用于生成 CA 密钥)。
default_md = sha256 # 默认摘要算法(SHA-256 哈希)。
prompt = no # 禁用交互式提示,直接从配置读取 DN 信息。
distinguished_name = root_ca_distinguished_name # 引用 DN 字段的预定义值。
x509_extensions = root_ca_extensions # 引用 X.509 证书扩展配置(用于生成根证书)。

[ root_ca_distinguished_name ]
countryName = CN # 固定值(用于生成自签名根证书) 中国
stateOrProvinceName = Chongqing # 州/省名称 重庆
localityName = Chongqing # 城市 重庆
0.organizationName = IT # 组织名
organizationalUnitName = IT Department # 部门名称
commonName = IT Internal CA # 显示名称
emailAddress = admin@youcats.cn # 邮箱

[ root_ca_extensions ]
basicConstraints = critical,CA:true # 关键扩展:声明此为 CA 证书,可签发下级证书。
keyUsage = keyCertSign, cRLSign # 密钥用途:允许签发证书和 CRL。

[ client_ca_extensions ]
basicConstraints = CA:false # 声明此证书不能作为 CA(仅终端实体)。
keyUsage = digitalSignature, keyEncipherment # 密钥用途:支持数字签名和密钥加密(如 TLS)。
extendedKeyUsage = clientAuth # 扩展用途:专用于客户端身份验证(如 VPN/Web 登录)。

生成私有CA证书

cd /etc/nginx/client_ca

# 生成CA私钥
openssl genrsa -out private/ca.key 2048

# 生成CA根证书
openssl req -config openssl.cnf -new -x509 -days 3650 -key private/ca.key -out ca.crt -extensions root_ca_extensions

创建客户端证书

脚本:

vim generate_client_cert.sh
#!/bin/bash

if [ $# -ne 3 ]; then
    echo "Usage: $0 <username> <email> <effective_days>"
    exit 1
fi

USERNAME=$1
EMAIL=$2
EFFECTIVE_DAYS=$3
CLIENT_DIR="/etc/nginx/client_ca/clientcerts/$USERNAME"
CA_DIR="/etc/nginx/client_ca"

cd $CA_DIR

# 创建客户端证书目录
mkdir -p $CLIENT_DIR

echo "生成私钥..."
openssl genrsa -out ${CLIENT_DIR}/${USERNAME}.key 2048

# 创建临时配置文件
cat > /tmp/client_${USERNAME}.cnf << EOF
[ req ]
default_bits = 2048
prompt = no
default_md = sha256
distinguished_name = dn

[ dn ]
countryName = CN
stateOrProvinceName = Chongqing
localityName = Chongqing
organizationName = CMIOT
organizationalUnitName = IT Department
commonName = $USERNAME
emailAddress = $EMAIL
EOF

# 生成证书请求
echo "生成证书请求..."
openssl req -new -key ${CLIENT_DIR}/${USERNAME}.key -out ${CLIENT_DIR}/${USERNAME}.csr -config /tmp/client_${USERNAME}.cnf

# 签发证书
echo "签发客户端证书..."
openssl ca -config openssl.cnf -in ${CLIENT_DIR}/${USERNAME}.csr -out ${CLIENT_DIR}/${USERNAME}.crt -extensions client_ca_extensions -days ${EFFECTIVE_DAYS} -batch

# 生成p12文件(交互式输入密码)这里移除了 -passout pass: 参数,OpenSSL会提示输入密码,不用密码则添加 -passout pass:
echo "生成P12格式证书..."
echo "请为P12文件设置密码(最少4个字符):"
openssl pkcs12 -export -out ${CLIENT_DIR}/${USERNAME}.p12 \
  -inkey ${CLIENT_DIR}/${USERNAME}.key \
  -in ${CLIENT_DIR}/${USERNAME}.crt \
  -certfile ${CA_DIR}/ca.crt

# 设置文件权限
chmod 600 ${CLIENT_DIR}/${USERNAME}.key
chmod 644 ${CLIENT_DIR}/${USERNAME}.crt
chmod 600 ${CLIENT_DIR}/${USERNAME}.p12

# 清理临时文件
rm -f /tmp/client_${USERNAME}.cnf
rm -f ${CLIENT_DIR}/${USERNAME}.csr

echo "================================================"
echo "客户端证书已成功生成"
echo "P12文件: ${CLIENT_DIR}/${USERNAME}.p12"
echo "证书文件: ${CLIENT_DIR}/${USERNAME}.crt"
echo "私钥文件: ${CLIENT_DIR}/${USERNAME}.key"
echo "证书有效期: ${EFFECTIVE_DAYS}天"
echo "用户信息: $USERNAME ($EMAIL)"
echo "================================================"

echo "证书详细信息:"
openssl x509 -in ${CLIENT_DIR}/${USERNAME}.crt -noout -subject -dates
chmod +x generate_client_cert.sh
./generate_client_cert.sh <username> <email> <effective_days>

创建IP白名单

/etc/nginx/ip_whitelist.conf

allow 192.168.1.0/24;
allow 10.0.0.0/8;
allow 172.16.0.0/12;
deny all;

Server块配置

server {
  listen 10501 ssl;
  listen [::]:10501 ssl;
  server_name example.com;

  #隐藏nginx版本号
  server_tokens off;
  ssl_certificate /etc/nginx/youcats.cn/youcats.cn_bundle.crt;
  ssl_certificate_key /etc/nginx/youcats.cn/youcats.cn.key;
  #服务端要支持 协议配置
  ssl_protocols TLSv1.2 TLSv1.3;
  ssl_ciphers TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256:ECDHE-ECDSA-CHACHA20-POLY105:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-ES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
  ssl_ecdh_curve X25519:secp384r1;
  ssl_prefer_server_ciphers on;
  ssl_session_tickets off;
  ssl_session_cache shared:SSL:1m;
  ssl_session_timeout  5m;
  #客户端验证
  ssl_verify_client on;
  ssl_client_certificate /etc/nginx/client_ca/ca.crt;
  ssl_verify_depth 2;
  
  #ip白名单
  include /etc/nginx/ip_whitelist.conf;

  location / {
    proxy_ssl_server_name on;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "Upgrade";
    proxy_pass https://127.0.0.1:442;
  }

  if ($scheme = http) {
    return 301 https://$host$request_uri;
  }

  location /undefined {
      return 403;
  }
}

客户端安装证书

点击分配的证书,name.p12,安装到个人证书,重启浏览器,访问目标地址,会弹出证书选择框,选择刚才导入的证书即可访问对应服务。

吊销证书(可选)

revoke_certs_batch.sh

#!/bin/bash

if [ $# -eq 0 ]; then
    echo "Usage: $0 <username1> <username2> ..."
    echo "或者: $0 --file user_list.txt"
    exit 1
fi

CA_DIR="/etc/nginx/client_ca"
cd $CA_DIR

# 检查是否从文件读取用户名
if [ "$1" = "--file" ]; then
    if [ ! -f "$2" ]; then
        echo "错误: 文件不存在: $2"
        exit 1
    fi
    USERS=$(cat "$2")
else
    USERS="$@"
fi

for USERNAME in $USERS; do
    CLIENT_CERT="${CA_DIR}/clientcerts/${USERNAME}/${USERNAME}.crt"

    if [ -f "$CLIENT_CERT" ]; then
        echo "吊销证书: $USERNAME"
        openssl ca -config openssl.cnf -revoke "$CLIENT_CERT"

        if [ $? -eq 0 ]; then
            echo "✓ $USERNAME 吊销成功"
        else
            echo "✗ $USERNAME 吊销失败"
        fi
    else
        echo "⚠ $USERNAME 证书文件不存在"
    fi
done

# 生成新的CRL
echo "生成新的证书吊销列表..."
openssl ca -config openssl.cnf -gencrl -out ${CA_DIR}/crl.pem

echo "================================================"
echo "批量吊销完成"
echo "CRL文件已更新: ${CA_DIR}/crl.pem"
echo "================================================"
chmod +x revoke_certs_batch.sh

./revoke_certs_batch.sh <username1> <username2> ...
# CRL配置(新增)
ssl_crl /etc/nginx/client_ca/crl.pem;