【问题标题】:nginx reverse proxy with Windows authentication that uses NTLM使用 NTLM 的具有 Windows 身份验证的 nginx 反向代理
【发布时间】:2014-02-12 15:06:16
【问题描述】:

任何人都知道是否可以通过使用 NTLM 的 Windows 身份验证进行反向代理?我找不到这方面的任何例子。 more_set_headers 字段的值应该是多少?

location / {
            proxy_http_version      1.1;
            proxy_pass_request_headers 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;


            more_set_input_headers  'Authorization: $http_authorization';

            proxy_set_header  Accept-Encoding  "";

            proxy_pass              http://host/;
            proxy_redirect          default;
            #This is what worked for me, but you need the headers-more mod
            more_set_headers        -s 401 'WWW-Authenticate: Basic realm="host.local"';
}

如果我直接访问主机,如果我使用反向代理访问,则每次验证都会失败。

【问题讨论】:

  • 对于像我这样的 nginx 新手来说,more_set_input_headers 和 more_set_headers 配置行一开始可能会让你的 nginx 崩溃(在 HTTP 日志中显示 Aborted),这是因为你需要安装 headers-more-nginx-模块,更多信息在这里github.com/openresty/headers-more-nginx-module#installation

标签: nginx reverse-proxy ntlm


【解决方案1】:

使用 Nginx 启用 NTLM 直通 -

upstream http_backend {
    server 2.3.4.5:80;
    keepalive 16;
}
server {
    ...
    location / {
       proxy_pass http://http_backend/;
       proxy_http_version 1.1;
       proxy_set_header Connection "";
    ...
    }
 }

-- 拉蒙

【讨论】:

  • 我有一个与此非常相似的question,如果您有时间,并且可以回答,目前有赏金。
  • @Fizz 你的意思是我们不需要商业版的 nginx 来支持 NTLM?
  • 这对我不起作用。为什么?在后端使用 tomcat。
  • @EdgarChavolla 它与 express-ntlm 一起使用,前提是您有会话。否则,用户将互相登录。
  • 这似乎有效,但实际上并没有。在我的情况下,它导致用户会话混淆(!!!),即用户可能突然以另一个用户身份登录。看到这个回复mailman.nginx.org/pipermail/nginx/2016-February/049889.html
【解决方案2】:

据我所知,目前 nginx 无法做到这一点。不久前,我自己对此进行了深入调查。基本问题是 NTLM 身份验证将要求在后续请求中使用相同的套接字,但代理不这样做。在 nginx 开发团队为此行为提供某种支持之前,我处理此问题的方式是在反向代理本身中进行身份验证。我目前正在使用 apache 2.2、mod_proxy、mod_auth_sspi(不完美,但有效)来执行此操作。祝你好运!抱歉,nginx,我爱你,但我们真的可以为这个常见用例提供一些帮助。

【讨论】:

  • Tony,所以如果我猜对了,您执行了以下操作:用户连接到 Apache 服务器,然后 Apache 使用 NTLM 对用户 itlsef 进行身份验证。之后 Apache 启动与某个服务器的反向代理连接并将其发送回用户?
  • 基本上,是的。客户端向运行 mod_auth_sspi 的 apache 进行身份验证。 apache 将请求代理到某个服务器,同时将用户 ID 注入请求标头。例如,这是 siteminder 的一种操作模式。只要确保防止直接访问您的后端服务器。这是这样做的一种方法。更好的方法是可能的。例如,重定向到身份验证服务器并使用 oauth2 样式的令牌机制。
【解决方案3】:

我已经为此提出了另一种解决方案。这仍然与 nginx 执行 NTLM 不同(如果 nginx 团队曾经实现这一点会很好)。但是,就目前而言,我所做的对我们有用。

我编写了一些使用加密 cookie 的 lua 代码。加密的 cookie 包含用户的 id、他认证的时间和他认证的 ip 地址。我在这里附上这些东西以供参考。它没有经过完善,但也许您可以使用它来开发自己的类似方案。

基本上,它的工作原理是:

  1. 如果 cookie 不可用或者过期或无效,nginx 会向后端 IIS 应用程序发出服务调用(预授权),并传递客户端的 IP 地址,然后将客户端重定向到我拥有的 IIS Web 应用程序“ Windows 身份验证”已打开。后端 IIS 应用程序的预身份验证服务会生成一个 GUID,并将该 GUID 的条目存储在 db 中,以及一个指示此 GUID 即将进行身份验证的标志。
  2. 浏览器由 nginx 重定向到传递 GUID 的身份验证器应用程序。
  3. IIS 应用程序通过 Windows 身份验证对用户进行身份验证,并使用用户 ID 和验证时间更新该 GUID 和客户端 IP 地址的数据库记录。
  4. IIS 应用程序将客户端重定向回原始请求。
  5. nginx lua 代码拦截此调用并再次对 IIS 应用程序进行后门服务调用(身份验证后),并询问用户 ID 和身份验证时间。此信息设置在加密的 cookie 中并发送到浏览器。请求被允许通过,同时 REMOTE_USER 被发送。
  6. 浏览器的后续请求传递 cookie,nginx lua 代码看到有效的 cookie 并通过传递 REMOTE_USER 请求标头直接代理请求(当然不需要再次验证)。

access.lua:

local enc     = require("enc");
local strings = require("strings");
local dkjson  = require("dkjson");


function beginAuth()
    local headers = ngx.req.get_headers();
    local contentTypeOriginal = headers["Content-Type"];
    print( contentTypeOriginal ); 
    ngx.req.set_header( "Content-Type", "application/json" );
    local method = ngx.req.get_method();
    local body = "";
    if method == "POST" then
        local requestedWith = headers["X-Requested-With"];
        if requestedWith ~= nil and requestedWith == "XMLHttpRequest" then
            print( "bailing, won't allow post during re-authentication." );
            ngx.exit(ngx.HTTP_GONE); -- for now, we are NOT supporting a post for re-authentication.  user must do a get first.  cookies can't be set on these ajax calls when redirecting, so for now we can't support it.
            ngx.say("Reload the page.");
            return;
        else
            print( "Attempting to handle POST for request uri: " .. ngx.var.uri );
        end
        ngx.req.read_body();
        local bodyData = ngx.req.get_body_data();
        if bodyData ~= nil then
            body = bodyData;
        end
    end
    local json = dkjson.encode( { c = contentTypeOriginal, m = method, d = body } );
    local origData = enc.base64encode( json );
    local res = ngx.location.capture( "/preauth", { method = ngx.HTTP_POST, body = "{'clientIp':'" .. ngx.var.remote_addr .. "','originalUrl':'" .. ngx.var.FrontEndProtocol .. ngx.var.host .. ngx.var.uri .. "','originalData':'" .. origData .. "'}" } );
    if contentTypeOriginal ~= nil then
        ngx.req.set_header( "Content-Type", contentTypeOriginal );
    else
        ngx.req.clear_header( "Content-Type" );
    end
    if res.status == 200 then
        ngx.header["Access-Control-Allow-Origin"] = "*";
        ngx.header["Set-Cookie"] = "pca=guid:" .. enc.encrypt( res.body ) .. "; path=/"
        ngx.redirect( ngx.var.authurl .. "auth/" .. res.body );
    else
        ngx.exit(res.status);
    end
end

function completeAuth( cookie )
    local guid = enc.decrypt( string.sub( cookie, 6 ) );
    local contentTypeOriginal = ngx.header["Content-Type"];
    ngx.req.set_header( "Content-Type", "application/json" );
    local res = ngx.location.capture( "/postauth", { method = ngx.HTTP_POST, body = "{'clientIp':'" .. ngx.var.remote_addr .. "','guid':'" .. guid .. "'}" } );
    if contentTypeOriginal ~= nil then
        ngx.req.set_header( "Content-Type", contentTypeOriginal );
    else
        ngx.req.clear_header( "Content-Type" );
    end
    if res.status == 200 then
        local resJson = res.body;
        -- print( "here a1" );
        -- print( resJson );
        local resTbl = dkjson.decode( resJson );
        if resTbl.StatusCode == 0 then
            resTbl = resTbl.Result;
            local time = os.time();
            local sessionData = dkjson.encode( { u = resTbl.user, t = time, o = time } );
            ngx.header["Set-Cookie"] = "pca=" .. enc.encrypt( sessionData ) .. "; path=/"
            ngx.req.set_header( "REMOTE_USER", resTbl.user );
            if resTbl.originalData ~= nil and resTbl.originalData ~= "" then
                local tblJson = enc.base64decode( resTbl.originalData );
                local tbl = dkjson.decode( tblJson );
                if tbl.m ~= nil and tbl.m == "POST" then
                    ngx.req.set_method( ngx.HTTP_POST );
                    ngx.req.set_header( "Content-Type", tbl.c );
                    ngx.req.read_body();
                    ngx.req.set_body_data( tbl.d );
                end
            end
        else
            ngx.log( ngx.ERR, "error parsing json " .. resJson );
            ngx.exit(500);
        end
    else
        print( "error completing auth." );
        ngx.header["Set-Cookie"] = "pca=; path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT; token=deleted;"
        print( res.status );
        ngx.exit(res.status);
    end
end


local cookie = ngx.var.cookie_pca;
print( cookie );
if cookie == nil then 
    beginAuth();
elseif strings.starts( cookie, "guid:" ) then
    completeAuth( cookie );
else
    -- GOOD TO GO...
    local json = enc.decrypt( cookie );
    local d = dkjson.decode( json );
    local now = os.time();
    local diff = now - d.t;
    local diffOriginal = 0;
    if d.o ~= nil then 
        diffOriginal = now - d.o;
    end
    if diff > 3600 or diffOriginal > 43200 then
        beginAuth();
    elseif diff > 300 then
        print( "regenerating new cookie after " .. tostring( diff ) .. " seconds." );
        local sessionData = dkjson.encode( { u = d.u, t = now, o = d.t } );
        ngx.header["Set-Cookie"] = "pca=" .. enc.encrypt( sessionData ) .. "; path=/"
    end
    ngx.req.set_header( "REMOTE_USER", d.u );
end

strings.lua:

local private = {};
local public = {};
strings = public;

function public.starts(String,Start)
   return string.sub(String,1,string.len(Start))==Start
end

function public.ends(String,End)
   return End=='' or string.sub(String,-string.len(End))==End
end

return strings;

enc.lua:

-- for base64, try something like: http://lua-users.org/wiki/BaseSixtyFour
local private = {};
local public = {};
enc = public;

local aeslua = require("aeslua");

private.key = "f8d7shfkdjfhhggf";

function public.encrypt( s )
    return base64.base64encode( aeslua.encrypt( private.key, s ) );
end

function public.decrypt( s )
    return aeslua.decrypt( private.key, base64.base64decode( s ) );
end

return enc;

示例 nginx 配置:

upstream dev {
    ip_hash;
    server app.server.local:8080;
}
set $authurl http://auth.server.local:8082/root/;
set $FrontEndProtocol https://;
location / {
    proxy_pass     http://dev/;
    proxy_set_header Host $host;
    proxy_redirect default;
    proxy_http_version 1.1;
    proxy_set_header Connection "";
    proxy_set_header X-Real-IP $remote_addr;
    proxy_buffers 128 8k;
    access_by_lua_file conf/lua/app/dev/access.lua;
}

【讨论】:

  • 嗨托尼。我很想看看你是如何真正实现这一点的。你会考虑把一个帖子放在一起让某人通过它,或者以任何方式帮助我吗?我目前有两个 apache 前端 Web 服务器,它们通过 NTML 对所有用户进行身份验证,而且速度非常慢。我很欣赏人们会说我可以调整 Apache(等等,等等),但我对 nginx 的体验是它更快,我更喜欢使用它。我有 IIS 框可供使用,所以如果我可以复制您的设置并使用 IIS 框进行身份验证并依靠 nginx 来完成网络服务器工作,那就太棒了。
  • BeardedGeek,我刚刚在读这篇文章。如果我有时间,我会尝试一步一步地拼凑起来,甚至只是捆绑起来。
  • @BeardedGeek,我没有验证每个请求,而是只验证对应用程序的访问,因此从那时起使用应用程序的会话 cookie。这确实加快了整个过程。
【解决方案4】:

好的,我们为 nginx/openresty 写了lua code,它解决了 ntlm 反向代理问题,具有一些可解决的限制,并且不需要商业 nginx 版本

【讨论】:

    【解决方案5】:

    根据 nginx 文档:

    允许使用 NTLM 身份验证代理请求。一旦客户端发送带有“Authorization”标头字段值以“Negotiate”或“NTLM”开头的请求,上游连接就会绑定到客户端连接。进一步的客户端请求将通过相同的上游连接代理,保持身份验证上下文。

    upstream http_backend {
        server 127.0.0.1:8080;
    
        ntlm;
    }
    

    但是 ntlm;该选项仅适用于商业订阅 (Nginx Plus)

    您可以将此模块用于非生产环境。

    gabihodoroaga/nginx-ntlm-module

    模块不完整,但足以解决我的问题。在hodo.dev 上还有一篇关于此模块的博客文章。

    【讨论】:

      猜你喜欢
      • 2016-03-12
      • 1970-01-01
      • 1970-01-01
      • 2018-04-16
      • 2011-06-28
      • 1970-01-01
      • 1970-01-01
      • 2017-07-27
      • 1970-01-01
      相关资源
      最近更新 更多