前几天实现了在 nginx 中使用 lua 实现远程鉴权,今天想试试在 IIS 中能不能实现相同的功能。查询资料发现需要使用 URL 重写和 HTTP 请求模块,没有深究。干脆使用 ASP.NET 中间件来实现吧。
在 StratUp.cs 的 Configure 方法中,或 Program.cs 文件中添加以下代码:
// 远程鉴权 app.Use(async (context, next) => { var ip = context.Connection.RemoteIpAddress!.ToString(); var ua = context.Request.Headers.UserAgent.ToString(); var host = context.Request.Host.Host; var uri = new Uri(context.Request.GetDisplayUrl()).PathAndQuery; var client = new HttpClient(); client.Timeout = TimeSpan.FromSeconds(1); // 设置超时时间 try { var requestUrl = "https://鉴权地址/"; var requestMessage = new HttpRequestMessage(HttpMethod.Get, requestUrl); requestMessage.Headers.Add("X-Real-IP", ip); requestMessage.Headers.Add("User-Agent", ua); requestMessage.Headers.Add("X-Forwarded-Host", host); requestMessage.Headers.Add("X-Forwarded-Uri", uri); // 发送请求 var response = await client.SendAsync(requestMessage); // 检查响应状态码 if (response.StatusCode == HttpStatusCode.Forbidden) { // 如果返回403,则拒绝访问 context.Response.StatusCode = (int)HttpStatusCode.Forbidden; await context.Response.WriteAsync("Access Denied"); } else { // 如果返回其他状态码,则继续执行管道中的下一个中间件 await next(); } } catch (TaskCanceledException ex) when (ex.CancellationToken.IsCancellationRequested) { // 如果请求超时(任务被取消),则继续执行管道中的下一个中间件 await next(); } catch { // 如果遇到错误,则继续执行管道中的下一个中间件 await next(); } });
代码很简单,使用 HttpClient 发送请求,若返回 403 则拒绝访问,其它情况继续执行业务逻辑,超时或报错的情况按需修改即可。
若鉴权接口在私网中,建议将鉴权接口域名和私网 IP 添加到 hosts 文件中。
location 优先于 access_by_lua_block,即使 access_by_lua_block 放在 http 中。
location 的 3 种匹配的优先级,精确匹配(=) > 正则匹配(~) > 前缀匹配(无符号,直接是“/”开头的 uri 前缀)。
相同匹配类型的多个 location(比如有多个正则匹配),按定义顺序来进行匹配。
一旦有一个 location 被匹配到,就不再继续匹配其它 location。
如果使用 access_by_lua_block 鉴权,那么不要放在任何 location 中,这样,匹配完 location 后只要没有命中直接返回语句,就会继续执行 access_by_lua_block。并且,如果 access_by_lua_block 中鉴权拒绝,后续的业务处理(如 php 业务)不会继续进行。
http { ... lua_shared_dict cpu_cache 1m; # 定义共享字典用于缓存 CPU 使用率 access_by_lua_block { local cpu_cache = ngx.shared.cpu_cache local cache_time = cpu_cache:get("cache_time") or 0 local current_time = ngx.now() if current_time - cache_time > 5 then -- 每 5 秒更新一次 local cpu_info = io.popen("top -bn1 | grep 'Cpu(s)'"):read("*all") local cpu_usage = cpu_info:match("(%d+%.%d+) id") -- 获取空闲 CPU 百分比 local cpu_used = 100 - tonumber(cpu_usage) -- 计算使用率 cpu_cache:set("cpu_usage", cpu_used) cpu_cache:set("cache_time", current_time) end local cpu_usage = cpu_cache:get("cpu_usage") ngx.log(ngx.INFO, "CPU 使用率: " .. cpu_usage .. "%") -- 将 cpu_usage 发送到远程鉴权接口,可根据服务器压力来决定是否拒绝一些不重要的请求 } }
代码解析:
共享字典:使用 lua_shared_dict 定义一个共享字典 cpu_cache,用于存储 CPU 使用率和缓存时间。
获取 CPU 使用率:在 access_by_lua_block 中,检查缓存时间,如果超过 5 秒,则重新获取 CPU 使用率,并更新共享字典。
记录日志:使用 ngx.log 将 CPU 使用率记录到 Nginx 日志中。
注意事项:
确保 Nginx 配置中已经加载了 Lua 模块(如 ngx_http_lua_module)。
根据实际需求调整缓存时间,以平衡性能和数据的实时性。
在 access_by_lua_block 代码块中实现远程鉴权:
#鉴权-START resolver 223.5.5.5; # 使用公共 DNS access_by_lua_block { local http = require("resty.http") local httpc = http.new() httpc:set_timeout(500) -- 连接超时 local res, err = httpc:request_uri("https://鉴权地址/", { method = "GET", headers = { ["X-Real-IP"] = ngx.var.remote_addr, ["User-Agent"] = ngx.var.http_user_agent, ["X-Forwarded-Host"] = ngx.var.host, ["X-Forwarded-Uri"] = ngx.var.request_uri, }, ssl_verify = false, -- 禁用 SSL 验证 timeout = 500, -- 读取超时 }) if not res then ngx.log(ngx.ERR, "Failed to request: " .. err) end if res and res.status == 403 then ngx.exit(ngx.HTTP_FORBIDDEN) -- return ngx.redirect("https://一个显示403友好信息的页面.html") end } #鉴权-END
注意更改接口地址和友情显示 403 页面地址。
本示例仅捕获 403 状态码,500、408 等其它异常情况视为允许访问,请根据业务需求自行添加状态码的判断。
若超时也会进入
if not res then
代码块。建议将此代码部署在 nginx 主配置文件的 http 代码块中(宝塔面板中的路径:/www/server/nginx/nginx/conf/nginx.conf),如果你只想为单个网站鉴权,也可以放在网站配置文件的 server 块中。
若鉴权接口在私网中,建议将鉴权接口域名和私网 IP 添加到 hosts 文件中。
附
直接输出字符串
ngx.header.content_type = "text/plain"; ngx.say("hello world!")
输出到日志
ngx.log(ngx.ERR, "Response status: " .. res.status)
日志在网站的 站名.error.log 中查看。
宝塔面板查看方式:日志 - 网站日志 - 异常
若想获取服务器 CPU 使用率等信息并传递给远程鉴权接口,请参考此文。
常见问题
no resolver defined to resolve
原因:没有定义 DNS 解析器
解决方法:在 http 块或 server 块中添加
resolver 8.8.8.8 valid=30s;
,当然使用接入商自己的公共 DNS 可能更合适。unable to get local issuer certificate
原因:没有配置 SSL 证书信息
解决方法:添加 request_uri 参数:
ssl_verify = true, -- 启用 SSL 验证 ssl_trusted_certificate = "证书路径", -- 指定 CA 证书路径
或
ssl_verify = false, -- 禁用 SSL 验证
若您不想用 lua,可以用 nginx 原生自带的 auth_request 模块来实现。
安装 OpenResty
在 宝塔面板 - 软件商店 中搜索 nginx,安装版本选择 openresty 版
安装 resty.http
luarocks install lua-resty-http
检测已安装的组件
nginx -v lua -v openresty -v luarocks --version
测试代码
access_by_lua_block { local http = require("resty.http") }
或
access_by_lua ' local http = require("resty.http") ';
安装依赖
sudo yum install -y epel-release sudo yum install -y lua lua-devel gcc make
下载安装 LuaRocks
访问 LuaRocks 的官方网站 获取最新版本的 LuaRocks。你可以使用 wget 命令下载:
wget https://luarocks.org/releases/luarocks-x.x.x.tar.gz tar zxpf luarocks-x.x.x.tar.gz cd luarocks-x.x.x ./configure && make && sudo make install
设置环境变量(可选)
通常,LuaRocks 会自动处理这一步,但如果需要手动设置,可以编辑 ~/.bash_profile 或 ~/.bashrc 文件,添加以下行:
export PATH=$PATH:/usr/local/bin
然后运行以下命令使更改生效:
source ~/.bash_profile
验证安装
luarocks --version
引擎 | 连接方式 |
mysqli | mysqli_connect("域名或IP:端口", "用户名", '密码', "数据库名") |
Scaffold-DbContext | Scaffold-DbContext "server=域名或IP;port=端口;uid=用户名;pwd=密码;database=数据库名;" Pomelo.EntityFrameworkCore.MySql -OutputDir 模型路径 -Force |
如果端口配置不正确,会报错:
Unable to connect to any of the specified MySQL hosts.
鉴权方式有许多,譬如可以用 lua 的 access_by_lua 来实现,这里用 nginx 自带的 auth_request,虽然功能简单,但效率更高。
第一步,确保 nginx 已编译安装 auth_request 模块,见此文。
第二步,打开需要鉴权的网站的 nginx 配置文件,添加以下代码块:
#鉴权-START location / { auth_request /auth; error_page 403 = @error403; #PHP-INFO-START PHP引用配置,可以注释或修改 include enable-php-**.conf; #PHP-INFO-END } location = /auth { internal; proxy_pass https://鉴权地址; proxy_pass_request_body off; proxy_set_header Content-Length ""; proxy_set_header X-Real-IP $remote_addr; proxy_set_header User-Agent $http_user_agent; proxy_set_header X-Forwarded-Host $host; proxy_set_header X-Forwarded-Uri $request_uri; proxy_intercept_errors on; #proxy_read_timeout 1s; # 超时会报 500 } location @error403 { #default_type text/plain; #return 403 'forbidden'; return 302 https://一个显示403友好信息的页面.html; } #鉴权-END
一般放在所有的 location 之前。
这里自定义请求头 X-Forwarded-Host
与 X-Forwarded-Uri
用来传递 host 与 uri。API 应从 Header 中相应取值。
宝塔面板中是通过 include enable-php-**.conf;
的方式调用 PHP ,那么可以将此行移入上面的 location /
代码块中,因为此代码块能匹配所有的请求路径。
最后,若鉴权接口在私网中,将鉴权接口域名和私网 IP 添加到 hosts 文件中。
首先使用命令查看是否已安装所需要的模块
nginx -V
若没有找到需要的模块,则需要编译安装。但是宝塔面板中安装的 nginx 如果像自行安装的 nginx 一样的方式去编译可能会出问题。宝塔面板有自己的添加模块方式。
在宝塔面板的软件商店找到 nginx,如果已安装则需要先卸载,卸载不会删除已有网站的配置文件(保险起见可以先备份一下),且安装时原有的模块不需要重新填写。
选择编译安装,添加自定义选装模块:
以添加“--with-http_auth_request_module”模块为例,名称按格式填写即可,模块参数填写--with-http_auth_request_module
,如添加其它模块,按需填写前置脚本。添加完成后,启用它:
开始安装,等待完成。
要在 Alibaba Cloud Linux 3 上设置开机启动 ossftp,你可以通过创建一个系统服务来实现。以下是具体步骤:
安装 ossftp:
按照阿里云官方文档安装 ossftp。
创建服务文件:
首先,你需要创建一个服务文件。使用以下命令创建一个新的服务文件:
sudo nano /etc/systemd/system/ossftp.service
编辑服务文件:
在打开的编辑器中,添加以下内容:
[Unit] Description=OSS FTP Service After=network.target [Service] ExecStart=/bin/bash /root/ossftp-1.2.0-linux-mac/start.sh Restart=always User=root [Install] WantedBy=multi-user.target
这里的 ExecStart 指向你的 start.sh 脚本的路径。
保存并退出。
重新加载系统服务:
运行以下命令以重新加载系统服务,使新创建的服务生效:
sudo systemctl daemon-reload
启用服务开机启动:
使用以下命令启用服务,使其在系统启动时自动运行:
sudo systemctl enable ossftp.service
启动服务:
你可以立即启动服务以测试其是否正常工作:
sudo systemctl start ossftp.service
检查服务状态:
使用以下命令检查服务的状态,确保它正在运行:
sudo systemctl status ossftp.service
如果一切设置正确,ossftp 应该会在每次系统启动时自动运行。
如果在你的系统上没有安装 nano 编辑器,你可以使用其他文本编辑器,比如 vi 或 vim。
如果你需要安装 nano,可以使用以下命令:
sudo yum install nano