问题背景:
接到个需求,客户有两个系统要互相访问文件,文件服务器是通过nginx搭建的,原来的访问地址如下:http://abc.cn/file/fa1a8d99a47b4c8c9d59152728af9930.docx
客户说这个不安全,任何人都能访问,一定要做权限校验
接到这个需求我觉得安全隐患不是很大,因为文件名是随机的,nginx也不支持在线预览目录,盲猜是很难猜出正确地址的,除非被抓包,这种概率很小
没办法,客户坚持说有问题,只能安排加上token验证了
这个还是多系统的互相访问,涉及到互相认证,有点复杂,经过一番研究解决了,特地记录下
解决方法:
首先贴出解决方法,nginx配置文件如下:
docker-compose
services: nginx-9002: image: nginx:latest restart: always hostname: nginx-9002 container_name: nginx-9002 #privileged: true environment: - "TZ=Asia/Shanghai" ports: - 9002:80 volumes: - ./conf/nginx.conf:/etc/nginx/nginx.conf - ./conf/conf.d:/etc/nginx/conf.d/ - ./logs/:/var/log/nginx/ - ./ssl_key/:/data/ssl_key/ - ./www/:/usr/share/nginx/html/
这个配置涉及http和server模块,所以把nginx.conf和conf.d分别映射出来了
nginx.conf
user nginx; worker_processes auto; error_log /var/log/nginx/error.log notice; pid /var/run/nginx.pid; events { worker_connections 1024; } http { include /etc/nginx/mime.types; default_type application/octet-stream; log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for" "$host"'; access_log /var/log/nginx/access.log main; sendfile on; #tcp_nopush on; #keepalive_timeout 65; gzip on; include /etc/nginx/conf.d/*.conf; client_max_body_size 300M; #安全加固 keepalive_timeout 55; client_body_timeout 10; client_header_timeout 10; send_timeout 10; limit_conn ops 20; limit_conn_zone $binary_remote_addr zone=ops:10m; autoindex off; dav_methods off; server_tokens off; client_body_buffer_size 1K; client_header_buffer_size 1k; large_client_header_buffers 2 1k; add_header Content-Security-Policy "default-src 'self' http://abc.cn/ http://1.1.1.1:9002 'unsafe-inline' 'unsafe-eval' blob: data:;"; #add_header Content-Security-Policy "default-src 'self' 'unsafe-inline'";
add_header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload"; add_header X-Permitted-Cross-Domain-Policies "master-only"; add_header 'Referrer-Policy' 'origin'; add_header X-Download-Options "noopen" always; add_header Clear-Site-Data "storage"; add_header Cross-Origin-Embedder-Policy require-corp; add_header Cross-Origin-Opener-Policy same-site; add_header Cross-Origin-Resource-Policy same-site; add_header Permissions-Policy "interest-cohort=()"; #防止XSS攻击 add_header X-Frame-Options "SAMEORIGIN"; add_header X-XSS-Protection "1; mode=block"; add_header X-Content-Type-Options "nosniff"; #判断文件访问链接参数,进行校验接口分发 map $request_uri $auth_url { #存在'&source=yg'参数的链接走对应系统的校验地址 ~*&source=yg http://1.1.1.1:9001/file/link/validate; #把自有系统接口地址设置为默认出参 default http://192.168.100.93:8085/file/link/validate; } }
default.conf
server { listen 80; listen [::]:80; server_name localhost; #access_log /var/log/nginx/host.access.log main; #安全加固 #防止盗链或者恶意域名解析 if ( $host !~* 'abc.cn' ) { return 403; } #限制请求类型 if ($request_method !~ ^(GET|HEAD|POST)$ ) { return 501; } #封杀各种user-agent if ($http_user_agent ~* "python|perl|ruby|curl|bash|echo|uname|base64|decode|md5sum|select|concat|httprequest|nmap|scan|nessus|wvs" ) { return 403; } #if ($http_user_agent ~* "" ) { # return 403; #} #封杀特定的文件扩展名比如.bak以及目录; location ~* \.(bak|swp|save|sh|sql|mdb|svn|git|old)$ { rewrite ^/(.*)$ $host permanent; } location /(admin|phpadmin|status) { deny all; } stub_status off; location / { proxy_buffer_size 64k; proxy_buffers 32 32k; proxy_busy_buffers_size 128k; root /usr/share/nginx/html; index index.html index.htm; try_files $uri $uri/ /index.html; } #正式环境api接口 location ^~/online-api/ { proxy_buffer_size 64k; proxy_buffers 32 32k; proxy_busy_buffers_size 128k; proxy_pass http://192.168.100.93:8085/; } #正式环境文件接口 location ^~/online-file-api/ { proxy_buffer_size 64k; proxy_buffers 32 32k; proxy_busy_buffers_size 128k; proxy_pass http://192.168.100.93:9000/; #优化安全策略,使自有页面可以加载图片 add_header X-Frame-Options "ALLOW-FROM http://aq.njjt.com.cn/"; #把链接参数赋值到nginx内部参数 set $token $arg_token; set $from $arg_from; #参数调试 #add_header token $token; #add_header auth_url $auth_url; #add_header from $from; #add_header source $arg_source; #proxy_set_header X-Original-URI $request_uri; #跳转token校验 auth_request /auth; #校验接口返回401直接跳转未认证链接处理 error_page 401 = /unauthorized; } #token校验接口 location = /auth {
#只能内部调用 internal; proxy_pass_request_body off; proxy_set_header Content-Length ""; proxy_set_header X-Original-URI $request_uri; #设置token和from值到head proxy_set_header token $token; proxy_set_header from $from; #指定nginx域名解析dns #resolver 218.2.135.1 61.147.37.1; #把map的出参传入代理地址 proxy_pass $auth_url; } location = /unauthorized { #未认证用户直接返回403 return 403 'Access denied'; } error_page 500 502 503 504 /50x.html; location = /50x.html { root /usr/share/nginx/html; } #安全素描 location /sketch { proxy_pass http://192.168.100.93:7777; } }
配置详解:
1.auth_request
由于我们项目已经开发完毕,这个只是安全改造,所以最佳的方式就是结合nginx来进行权限校验,找了一圈还真找到了
https://blog.csdn.net/u014374743/article/details/135937481
https://blog.csdn.net/nalanxiaoxiao2011/article/details/133769412
先来说思路,为nginx增加ngx_http_auth_request_module模块,实现基于子请求的结果的客户端的授权。如果子请求返回2xx响应码,则允许访问。如果它返回401或403,则访问被拒绝并显示相应的错误代码。子请求返回的任何其他响应代码都被认为是错误的。即在原来的基础上加了一层后端鉴权服务。
auth_request 这个就是这个配置的核心,当有请求来匹配到静态资源,会优先通过auth_request跳转到校验接口进行token校验,当校验成功则返回200状态,校验失败则返回401状态,auth_request收到200会返回相应的资源,收到401会抛出401报错
同时还发现另一种思路:https://www.cnblogs.com/lowmanisbusy/p/11718345.html
就是通过internal; 参数让静态文件只能接收内部访问,然后通过接口做校验,校验通过走内部访问,校验失败拒绝访问,这种是从接口层面直接进行token校验
但是考虑到多系统互相访问,要进行多系统分流校验,这种方式并不合适
于是就确定了用auth_request的方案,本以为问题就此愉快解决的时候,没想到,坑才刚刚开始。。
2.auth_request 根据参数分别请求不同验证地址
本来想法很简单
请求地址变为:http://abc.cn/file/fa1a8d99a47b4c8c9d59152728af9930.docx?token=xxxx&from=web&source=abc
后端把文件地址自动拼上token和其他参数传给前端,前端页面来访问
from=web/app 来区分客户端
source=abc/def 来区分系统
nginx自带参数arg_source来获取链接里的参数
在使用if语句判断,然后设置接口校验地址,最后把地址传给auth_request 不就行了,类似于这样
location / { root /usr/share/nginx/html; index index.html index.htm; # 从查询参数中获取 token,并赋值给token变量 set $token $arg_token; set $source $arg_source; # 自定义验证失败时的处理页面 error_page 401 = /auth-required; if ($source ~ "jt") { set $flag $source; set $auth_path autha; } if ($source ~ "yg") { set $flag $source; set $auth_path authb; } add_header token $token; add_header from $arg_from; add_header flag $flag; add_header auth_path $auth_path; #auth_request /$auth_path; auth_request /authFileValidA; }
本来想的很完美,但是实际做起来 auth_request 并不能识别传参,打开http://abc.cn/file/fa1a8d99a47b4c8c9d59152728af9930.docx 照样能访问,nginx日志报错, invalid URL prefix in "auth-required" auth_request 参数为空
把 auth_request 写成实际参数的时候,可以拒绝访问,试了很多次都是这样,无奈只好换种方式,于是我想到了给校验接口传参
类似于这样
location = /authFileValidA { internal; # 只允许内部访问 proxy_pass $auth_path; proxy_pass_request_body off; proxy_set_header Content-Length ""; proxy_set_header X-Original-URI $request_uri; # 设置AccessToken 的值为token proxy_set_header token "$token"; proxy_set_header from "$arg_from"; }
想法没毛病对吧,但是实际测试访问http://abc.cn/file/fa1a8d99a47b4c8c9d59152728af9930.docx 报500报错,token正确也报500,查看nginx日志报错 invalid URL prefix in "" auth request unexpected status: 500,也是识别不到nginx
百度了一下,不得不说百度AI这次帮了不少忙,问了一下百度AI
proxy_pass 支持传参吗
proxy_pass本身并不直接支持传参。proxy_pass主要用于将请求转发到另一个服务器,但它本身并不处理或传递参数。在Nginx配置中,proxy_pass指令用于定义代理服务器的地址,它将客户端的请求转发到该地址。
如果你需要在代理过程中传递参数,你可能需要使用其他方法或工具来实现。一种常见的方法是在Nginx配置中使用变量来构建代理URL,并在该URL中包含所需的参数。这样,每次请求时,Nginx都会根据这些变量动态地生成代理URL,并将请求转发到相应的地址。
需要注意的是,具体的实现方式可能因你的具体需求和Nginx版本而有所不同。因此,在实际应用中,你可能需要查阅Nginx的官方文档或相关资源,以了解如何根据你的需求进行配置和传参。
另外,如果你正在使用某种特定的编程语言或框架来处理代理请求,那么可能会有其他方式来实现传参的功能。在这种情况下,你可以查阅该语言或框架的文档以获取更多信息。
好吧,只能另辟蹊径了,于是我又问AI:
auth_request 根据参数分别请求不同验证地址 在Nginx配置中,auth_request指令允许你根据请求的参数来决定向不同的验证地址发送请求。以下是一个配置示例,其中使用了$arg_param变量来决定请求的验证地址: http { map $arg_param $auth_url { default /auth; value1 /auth1; value2 /auth2; } server { location / { auth_request /auth; error_page 401 = /unauthorized; } location = /auth { internal; proxy_pass_request_body off; proxy_set_header Content-Length ""; proxy_pass http://backend$auth_url; } location = /unauthorized { return 401 'Access denied'; } } } 在这个配置中: map块定义了一个映射,根据请求参数param的值决定使用哪个验证URL。 当请求到达服务器块的location时,会自动触发auth_request指令,并将请求转发到/auth内部location。 proxy_pass使用了变量$auth_url,这是基于请求参数param的值动态决定的。 如果认证失败,会返回401错误。
说的很清楚,有木有,这就是我想要的map!
本以为问题就此迎刃而解的时候,一个坑又接踵而至。。
3.map条件判断
标签:文件,Nginx,request,校验,auth,header,token,proxy,nginx From: https://www.cnblogs.com/allay/p/18185137