【问题标题】:Does the Phoenix framework support Windows authentication?Phoenix 框架是否支持 Windows 身份验证?
【发布时间】:2015-10-06 15:07:29
【问题描述】:

我们的网络应用程序目前使用 C# 在 Windows 和 IIS 上运行。我们严重依赖此环境中包含的 Windows 身份验证方案。启用 Windows 身份验证后,我们可以检测连接用户的身份,并对他们能够使用的屏幕和操作进行授权。

如果我设置了 Phoenix Web 应用程序,是否可以根据当前 Windows 登录来检测已连接用户的身份?如果没有,是否有易于使用的替代 Windows 身份验证?

【问题讨论】:

    标签: phoenix-framework


    【解决方案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++ 工具编译 C 代码(打开 x64 VS 命令提示符)。我用新的 VS2017 工具对此进行了尝试。将 DLL 放在应用程序的 priv 目录中。

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

    要运行插件,请将其添加到 web/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 在作为 IIS 的子进程运行时存在 CPU 和内存泄漏问题。我仍在努力解决这个问题,以防你看到它。我发布了一个关于它的问题here

    当我清理并修复内存/CPU 问题后,我可能会在 hex.pm 上将其作为库发布,但现在,这里的代码可让您在 Phoenix 中使用 Windows 身份验证。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-07-12
      • 2016-05-01
      • 1970-01-01
      • 1970-01-01
      • 2020-01-08
      • 2021-01-29
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多