博客 (26)

前几天实现了在 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 文件中。

xoyozo 2 天前
27
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)。

根据实际需求调整缓存时间,以平衡性能和数据的实时性。

如何配置 nginx 远程鉴权

xoyozo 3 天前
44
  1. 使 lua 支持 resty.http

  2. 在 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 块中。

  3. 若鉴权接口在私网中,建议将鉴权接口域名和私网 IP 添加到 hosts 文件中。


  1. 直接输出字符串

    ngx.header.content_type = "text/plain";
    ngx.say("hello world!")
  2. 输出到日志

    ngx.log(ngx.ERR, "Response status: " .. res.status)

    日志在网站的 站名.error.log 中查看。

    宝塔面板查看方式:日志 - 网站日志 - 异常

  3. 若想获取服务器 CPU 使用率等信息并传递给远程鉴权接口,请参考此文

  4. 常见问题

    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 验证
  5. 若您不想用 lua,可以用 nginx 原生自带的  auth_request 模块来实现

xoyozo 6 天前
115

鉴权方式有许多,譬如可以用 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-HostX-Forwarded-Uri 用来传递 host 与 uri。API 应从 Header 中相应取值。

宝塔面板中是通过 include enable-php-**.conf; 的方式调用 PHP ,那么可以将此行移入上面的 location / 代码块中,因为此代码块能匹配所有的请求路径。

最后,若鉴权接口在私网中,将鉴权接口域名和私网 IP 添加到 hosts 文件中。

xoyozo 10 天前
113

首先使用命令查看是否已安装所需要的模块

nginx -V

若没有找到需要的模块,则需要编译安装。但是宝塔面板中安装的 nginx 如果像自行安装的 nginx 一样的方式去编译可能会出问题。宝塔面板有自己的添加模块方式。

在宝塔面板的软件商店找到 nginx,如果已安装则需要先卸载,卸载不会删除已有网站的配置文件(保险起见可以先备份一下),且安装时原有的模块不需要重新填写。

选择编译安装,添加自定义选装模块:

QQ20241211-152109.png

以添加“--with-http_auth_request_module”模块为例,名称按格式填写即可,模块参数填写--with-http_auth_request_module,如添加其它模块,按需填写前置脚本。添加完成后,启用它:

QQ20241211-205622.png

开始安装,等待完成。

xoyozo 10 天前
81

宝塔面板中-安全-系统防火墙 功能非常丰富,特别是“地区规则”用来屏蔽来自国外的请求非常有用,那些通过 URL 来找网站漏洞的恶意请求大多来自国外。但是添加规则经常无反应

image.png

于是找到这个功能对应的配置文件:/etc/firewalld/zones/public.xml

<?xml version="1.0" encoding="utf-8"?>
<zone>
  <short>Public</short>
  <description>For use in public areas. You do not trust the other computers on networks to not harm your computer. Only selected incoming connections are accepted.</description>
  <service name="ssh"/>
  <service name="dhcpv6-client"/>
  <port protocol="tcp" port="80"/>
  <port protocol="tcp" port="443"/>
  <masquerade/>
  <rule family="ipv4">
    <source address="43.131.232.135"/>
    <drop/>
  </rule>
  <rule family="ipv4">
    <source address="103.150.11.45"/>
    <drop/>
  </rule>
  <rule>
    <source ipset="US"/>
    <drop/>
  </rule>
  <rule>
    <source ipset="GB"/>
    <drop/>
  </rule>
</zone>

修改完成后重启防火墙。

但是看到的效果好像并不一致,甚至有时候服务器重启后防火墙是关闭的,但是有时候又是生效的。

关键的问题是CPU占用异常高。

反正宝塔的系统防火墙功能挺好挺强大,但是用起来不稳定不顺手,有些暴殄天物了。

相关阅读:

系统防火墙添加规则转圈卡死

2024年8月30日补充 :宝塔面板中配置禁用IP等规则时,提示保存成功或未提醒是否成功,但效果是未成功。其实在宝塔自己的配置里已经记录了(包含备注),但在系统防火墙配置文件中未更改成功,手动修改系统防火墙配置文件就能完全修改成功。(系统防火墙配置文件中没有备注信息,根据IP地址等规则与宝塔自己的配置文件里的记录关联后,在宝塔面板安全模块中展示出来就有备注了。)

2024年8月30日下午补充:在宝塔的软件商店找到这个应用,看到红色的说明,啥都明白了:

image.png

于是果断关闭了系统防火墙,开始研究其它替代方案。

xoyozo 4 个月前
370

可能的原因是 IIS 安装了“WebDAV 发布”,在 IIS - 模块 中可见 WebDAVModule

解决方法:在网站的 web.config 中设置:

<system.webServer>
    <modules>
        <remove name="WebDAVModule" />
    </modules>
</system.webServer>

或在 IIS 中,在该网站的模块中删除 WebDAVModule。

如果网站需要用到 WebDAV 模块,那么考虑使用 Fetch API 或 XMLHttpRequest 来发送请求。


xoyozo 1 年前
1,066

HTML:

<script id="tb_content" type="text/plain">{{content}}</script>

JS:

var app = new Vue({
    el: "#app",
    data: {
        content: 'abc',
        ue: {},
    }, mounted: function () {
        this.ue = UE.getEditor('tb_content');
    }, methods: {
        fn_save: function () {
            console.log(this.ue.getContent())
        }
    }
});


xoyozo 3 年前
1,477

普通的 nginx http 反向代理 https 时是需要配置证书的,但我们又不可能由源域名的证书,所以要使用 nginx 的 stream 模块。普通的 nginx 反向代理属于第七层代理,而 stream 模块是第四层代理,通过转发的 tcp/ip 协议实现高功能,所以不需要证书。

我们这里以使用宝塔安装的 nginx 为例,其实其他系统也是类似,只要找到编译的 nginx 的源码目录就行了

编译前先将已经安装的 nginx 文件进行备份

通过 ps 命令查看 nginx 文件的路径。以下所有步骤都以自身 nginx 路径为准

# ps -elf | grep nginx
# cd /www/server/nginx/sbin/
# cp nginx nginx.bak

然后查看当前 nginx 编译的参数

/www/server/nginx/sbin/nginx -V

将 ./configure arguents:之后的内容复制到记事本备用(备注:我们这里其实使用的是 Tengine-2.3.1,所以下面的编译参数可能跟普通 nginx 不是很一样)

内容如下:

--user=www --group=www --prefix=/www/server/nginx --add-module=/www/server/nginx/src/ngx_devel_kit --with-openssl=/www/server/nginx/src/openssl --add-module=/www/server/nginx/src/ngx_cache_purge --add-module=/www/server/nginx/src/nginx-sticky-module --add-module=/www/server/nginx/src/lua_nginx_module --with-http_stub_status_module --with-http_ssl_module --with-http_v2_module --with-http_image_filter_module --with-http_gzip_static_module --with-http_gunzip_module --with-ipv6 --with-http_sub_module --with-http_flv_module --with-http_addition_module --with-http_realip_module --with-http_mp4_module --with-ld-opt=-Wl,-E --with-pcre=pcre-8.42 --with-cc-opt=-Wno-error --add-module=/www/server/nginx/src/ngx-pagespeed

进入 src 目录

cd /www/server/nginx/src

我们在上面的内容中加入两个参数

./configure --user=www --group=www --prefix=/www/server/nginx --add-module=/www/server/nginx/src/ngx_devel_kit --with-openssl=/www/server/nginx/src/openssl --add-module=/www/server/nginx/src/ngx_cache_purge --add-module=/www/server/nginx/src/nginx-sticky-module --add-module=/www/server/nginx/src/lua_nginx_module --with-http_stub_status_module --with-http_ssl_module --with-http_v2_module --with-http_image_filter_module --with-http_gzip_static_module --with-http_gunzip_module --with-ipv6 --with-http_sub_module --with-http_flv_module --with-http_addition_module --with-http_realip_module --with-http_mp4_module --with-ld-opt=-Wl,-E --with-pcre=pcre-8.42 --with-cc-opt=-Wno-error --add-module=/www/server/nginx/src/ngx-pagespeed --with-stream --with-stream_ssl_preread_module

并执行它

然后

make && make install

重启 nginx

service nginx restart

nginx -V 看看模块是不是加载了

image.png

新建个站点,配置反向代理


卸载

使用 nginx.bak 文件替换掉自编译的 nginx 文件,替换后重启 nginx。

转自 笨牛网 3 年前
2,579

当我们用 ASP.NET Web API 作服务端接口时,如果客户端以 GET 方式请求的 URL 中,由于拼接错误导致“?”和“&”并列出现,会发生 400 Bad Request,那么需要在 IIS 中使用 URL 重写模块来纠正。

请求 URL 如:

http://www.abc.com/api/obj?&foo=bar

需要重写为:

http://www.abc.com/api/obj?foo=bar


打开 IIS 管理器中对应网站的 URL 重写

image.png


添加空白规则。

匹配的 URL 不包含域名,如上述 URL 则匹配的范围是:

api/obj

不包含我们要查找的字符串 ?&,所以此处模式只能匹配所有:

.*

又因为重定向路径中需要包含这部分,所以用括号包裹,即:

(.*)

image.png

继续添加条件匹配:

条件输入:{QUERY_STRING},表示匹配查询字符串,

匹配的部分是“?”后面的字符串:

&foo=bar

那么可以填写模式:

^\&.*

因为 & 后面的部分仍然需要使用,所以用括号包裹:

^\&(.*)

image.png

“操作”中选择类型“重定向”,URL 填写:

{R:1}?{C:1}

其中 {R:1} 指代 api/obj,{C:1} 指代 foo=bar

取消“附加查询字符串”前面的勾

重定向类型在测试时可以选择 302 或 307,没问题后选择 301。

image.png

完成。

最终我们可以在网站的配置文件 web.config 中看到:

<rewrite>
    <rules>
        <rule name="uni-app-h5-replace-q" stopProcessing="true">
            <match url="(.*)" />
            <conditions>
                <add input="{QUERY_STRING}" pattern="^\&amp;(.*)" />
            </conditions>
            <action type="Redirect" url="{R:1}?{C:1}" appendQueryString="false" redirectType="Temporary" />
        </rule>
    </rules>
</rewrite>

我们要把这段代码复制到开发环境源代码的 web.config 中,以免发布网站时将我们设置好的配置覆盖掉。

xoyozo 6 年前
8,869