2015-10-06 38 views
4

我們的網絡應用程序目前使用C#在Windows和IIS上運行。我們嚴重依賴於此環境中包含的Windows身份驗證方案。啓用Windows身份驗證後,我們可以檢測連接用戶的身份,並對他們可以使用的屏幕和操作執行授權。Phoenix框架是否支持Windows身份驗證?

如果我設置了Phoenix Web應用程序,可以根據當前的Windows登錄名檢測連接用戶的身份嗎?如果沒有,是否有一個易於使用的替代Windows認證?

回答

1

我剛剛在週末做了這個。對的,這是可能的。您必須使用IIS的HttpPlatformHandler加載項才能使其工作。 HttpPlatformHandler具有forwardWindowsAuthToken配置設置,您可以使用該設置將經過身份驗證的用戶的Windows用戶令牌從IIS轉發到作爲子進程運行的Phoenix應用程序。您必須使用NIF來處理令牌並獲取Windows用戶名或SID。正如您從文檔中注意到的那樣,您需要調用CloseHandle爲每個請求釋放Windows用戶令牌。

(如果下面的代碼不符合最佳實踐,我會提前致歉。我是Elixir的新手,正在積極努力學習如何編寫更好的代碼,這也是來自黑客會話,試圖找出解決方案,所以它也不一定要打磨)

要做到這一點,我把所有東西都打包成一個自定義插件,我可以放入管道中(我刪除了Logger語句來壓縮示例的大小):

defmodule MyApp.WindowsAuthentication do 
    import Plug.Conn 

    require Logger 

    @on_load :load_nifs 

    def load_nifs do 
    if match? {:win32, _}, :os.type do 
     :erlang.load_nif("./priv/windows_authentication", 0) 
    else 
     :ok 
    end 
    end 

    def init(options), do: options 

    def call(conn, _options) do 
    if match? {:win32, _}, :os.type do 
     case get_req_header(conn, "x-iis-windowsauthtoken") do 
     [token_handle_string] -> 
      # token_handle_string is a hex string 
      token_handle = String.to_integer(token_handle_string, 16) 
      case do_get_windows_username(token_handle) do 
      {:ok, {domain_name, username}} -> 
       conn = assign(conn, :windows_user, {domain_name, username}) 
      error -> 
       Logger.error IO.inspect(error) 
      end 

      do_close_handle(token_handle) 
     [] -> 
      Logger.debug "X-IIS-WindowsAuthToken was not present" 
     end 
    end 

    conn 
    end 

    def do_get_windows_username(_token_handle) do 
    raise "do_get_windows_username/1 is only available on Windows" 
    end 

    def do_close_handle(_handle) do 
    raise "do_close_handle/1 is only available on Windows" 
    end 
end 

NIF的C源代碼如下:

#include <Windows.h> 
#include <erl_nif.h> 

static const char* error_atom = "error"; 
static const char* invalid_token_handle_atom = "invalid_token_handle"; 
static const char* ok_atom = "ok"; 
static const char* win32_error_atom = "win32_error"; 

#define MAX_NAME 256 

static HANDLE get_user_token(ErlNifEnv *env, ERL_NIF_TERM token) { 
    HANDLE token_handle; 

    if (!enif_get_ulong(env, token, (unsigned long *)&token_handle)) { 
    return NULL; 
    } 

    return token_handle; 
} 

static ERL_NIF_TERM make_win32_error_tuple(ErlNifEnv* env, DWORD error_code) { 
    return enif_make_tuple2(
    env, 
    enif_make_atom(env, error_atom), 
    enif_make_ulong(env, error_code) 
); 
} 

static ERL_NIF_TERM make_invalid_token_handle_error(ErlNifEnv* env) { 
    return enif_make_tuple2(
    env, 
    enif_make_atom(env, error_atom), 
    enif_make_atom(env, invalid_token_handle_atom) 
); 
} 

static ERL_NIF_TERM do_get_windows_username(ErlNifEnv* env, int argc, ERL_NIF_TERM argv[]) { 
    HANDLE token_handle; 
    DWORD token_user_length; 
    PTOKEN_USER token_user; 
    DWORD last_error; 
    WCHAR username[MAX_NAME]; 
    DWORD username_length = MAX_NAME; 
    WCHAR domain_name[MAX_NAME]; 
    DWORD domain_name_length = MAX_NAME; 
    size_t converted_chars; 
    char converted_username[MAX_NAME * 2]; 
    char converted_domain_name[MAX_NAME * 2]; 
    errno_t err; 
    BOOL succeeded; 
    SID_NAME_USE sid_name_use; 

    token_handle = get_user_token(env, argv[0]); 
    if (!token_handle) { 
    return make_invalid_token_handle_error(env); 
    } 

    if (!GetTokenInformation(token_handle, TokenUser, NULL, 0, &token_user_length)) { 
    last_error = GetLastError(); 
    if (ERROR_INSUFFICIENT_BUFFER != last_error) { 
     return make_win32_error_tuple(env, last_error); 
    } 
    } 

    token_user = (PTOKEN_USER)malloc(token_user_length); 
    if (!GetTokenInformation(token_handle, TokenUser, token_user, token_user_length, &token_user_length)) { 
    free(token_user); 
    return make_win32_error_tuple(env, GetLastError()); 
    } 

    succeeded = LookupAccountSidW(
    NULL, 
    token_user->User.Sid, 
    username, 
    &username_length, 
    domain_name, 
    &domain_name_length, 
    &sid_name_use); 
    if (!succeeded) { 
    free(token_user); 
    return make_win32_error_tuple(env, GetLastError()); 
    } 

    err = wcstombs_s(&converted_chars, converted_username, 512, username, username_length); 
    err = wcstombs_s(&converted_chars, converted_domain_name, 512, domain_name, domain_name_length); 

    free(token_user); 
    return enif_make_tuple2(
    env, 
    enif_make_atom(env, ok_atom), 
    enif_make_tuple2(
     env, 
     enif_make_string(env, converted_domain_name, ERL_NIF_LATIN1), 
     enif_make_string(env, converted_username, ERL_NIF_LATIN1) 
    ) 
); 
} 

static ERL_NIF_TERM do_close_handle(ErlNifEnv* env, int argc, ERL_NIF_TERM argv[]) { 
    HANDLE token_handle; 

    token_handle = get_user_token(env, argv[0]); 
    if (!token_handle) { 
    return make_invalid_token_handle_error(env); 
    } 

    if (!CloseHandle(token_handle)) { 
    return make_win32_error_tuple(env, GetLastError()); 
    } 

    return enif_make_atom(env, ok_atom); 
} 

static ErlNifFunc nif_functions[] = { 
    { "do_close_handle", 1, do_close_handle }, 
    { "do_get_windows_username", 1, do_get_windows_username } 
}; 

ERL_NIF_INIT(
    Elixir.MyApp.WindowsAuthentication, 
    nif_functions, 
    NULL, 
    NULL, 
    NULL, 
    NULL 
) 

您可以使用64位Visual Studio C++工具(打開x64 VS命令提示符)編譯C代碼。我用新的VS2017工具嘗試了這一點。將該DLL放入應用程序的priv目錄中。

cl /LD /I "C:\Program Files\erl-8.2\erts-8.2\include" /DDEBUG windows_authentication.c advapi32.lib 

要運行該插件,將其添加到您的管道在網絡/ router.ex:

pipeline :browser do 
    plug :accepts, ["html"] 
    plug MyApp.WindowsAuthentication 
    plug :fetch_session 
    plug :fetch_flash 
    plug :protect_from_forgery 
    plug :put_secure_browser_headers 
end 

這樣做的最終結果是,conn.assigns.windows_user將包含有形式{domain_name, username}的元組已驗證用戶的Windows域用戶名。

注意:當我嘗試這個時,發現erl.exe的CPU和內存泄漏問題,當作爲IIS的子進程運行時。如果你看到它,我仍然試圖弄清楚。我發佈了一個關於它的問題here

我可能將它作爲一個庫在hex.pm上發佈時,我已經清理它並修復了內存/ CPU問題,但現在,這裏是允許您使用Windows身份驗證的代碼。

相關問題