使用NTLM的Windows身份验证的nginx反向代理

时间:2014-01-22 13:57:57

标签: nginx reverse-proxy ntlm

任何人都知道是否可以使用使用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"';
}

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

5 个答案:

答案 0 :(得分:13)

使用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 "";
    ...
    }
 }

- 拉蒙

答案 1 :(得分:3)

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

答案 2 :(得分:3)

我已经为此提出了另一种解决方案。这仍然与nginx执行NTLM不同(如果nginx团队实现了这一点,这将是很好的)。但是,就目前而言,我正在为我们工作。

我写了一些使用加密cookie的lua代码。加密的cookie包含用户的id,他验证的时间以及他验证的IP地址。我在这里附上这些东西以供参考。它没有被抛光,但也许你可以用它来开发你自己的类似方案。

基本上,它是如何工作的:

  1. 如果cookie不可用或者它已过期或无效,nginx会向后端IIS应用程序发送服务调用(pre-auth),传递客户端的IP地址,然后将客户端重定向到IIS Web应用程序,我有“ Windows身份验证“已启用。后端IIS应用程序的pre-auth服务生成GUID并在数据库中为该guid存储一个条目,并指示一个标志,指示此GUID即将被验证。
  2. 浏览器被nginx重定向到传递GUID的身份验证器应用程序。
  3. IIS应用程序通过Windows身份验证对用户进行身份验证,并使用用户ID和经过身份验证的时间更新该GUID和客户端IP地址的数据库记录。
  4. IIS应用程序将客户端重定向回原始请求。
  5. nginx lua代码拦截此调用并再次对IIS应用程序进行后门服务调用(post-auth)并要求验证用户ID和时间。此信息在加密的cookie中设置并发送到浏览器。允许请求通过,并发送REMOTE_USER。
  6. 浏览器的后续请求传递cookie,nginx lua代码通过传递REMOTE_USER请求头,直接查看有效的cookie并代理请求(当然不需要再次进行身份验证)。
  7. <强> 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 conf:

    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;
    }
    

答案 3 :(得分:0)

好吧,我们为nginx / openresty写了lua code,它解决了ntlm反向代理问题,但存在一些可解决的局限性,而无需商业化的nginx版本

答案 4 :(得分:0)

根据nginx文档:

允许使用NTLM身份验证进行代理请求。一旦客户端发送带有“授权”标头字段值(以“协商”或“ NTLM”开头)的请求,则上游连接将绑定到客户端连接。进一步的客户端请求将通过相同的上游连接进行代理,同时保留身份验证上下文。

upstream http_backend {
    server 127.0.0.1:8080;

    ntlm;
}

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

您可以在非生产环境中使用此模块。

gabihodoroaga/nginx-ntlm-module

该模块尚不完整,但足以解决我的问题。