electron在windows系统上如何进行用户授权

electron在windows系统上的用户授权

我们经常在开发electron项目时,当遇到一些敏感操作需要进行用户授权时(弹出windows安全中心弹窗,要求输入当前用户登录命令,从而确认是本人操作),往往不太好处理。

这是因为electron在macos上提供了直接的api可供调用systemPreferences.promptTouchID。

但是在windows上,electron却没有提供相关的api,那么我们如何才能唤起windows安全中心的授权弹窗呢。

解决方案

windows系统提供了CredUIPromptForCredentialsW api可直接唤起系统自带的凭证输入弹窗。并进行密码身份认证。但是electron并无法直接调用这个系统级的api。此时该怎么办呢。

我们可以通过c++去调用windows系统的api,写一段c++脚本去调用这个api,唤起凭证输入框进行身份认证。然后再通过node-gyp去编译这个原生模块,编译后会生成一个.node的模块,该模块可通过node.js直接调用。

此时,我们可以着手准备写一个本地包文件来处理这个事情

cpp 复制代码
// Include security_defs.h first to define SECURITY_WIN32
#include "security_defs.h"

#include <napi.h>

#ifdef _WIN32
#include <windows.h>
#include <wincred.h>
#include <lmcons.h>
#pragma comment(lib, "credui.lib")
#pragma comment(lib, "advapi32.lib")
#endif

Napi::Boolean PromptCredentials(const Napi::CallbackInfo& info) {
  Napi::Env env = info.Env();

#ifdef _WIN32
  try {
    // Convert message if provided
    std::string message = "Enter your password to continue";
    // Get theme parameter if provided ('dark' or 'light')
    std::string theme = "system";
    
    if (info.Length() > 0 && info[0].IsString()) {
      message = info[0].As<Napi::String>().Utf8Value();
    }
    
    // Get theme parameter
    if (info.Length() > 1 && info[1].IsString()) {
      theme = info[1].As<Napi::String>().Utf8Value();
    }

    // Convert to wide string
    int wideLen = MultiByteToWideChar(CP_UTF8, 0, message.c_str(), -1, NULL, 0);
    if (wideLen <= 0) {
      return Napi::Boolean::New(env, false);
    }

    wchar_t* wideMessage = new wchar_t[wideLen];
    MultiByteToWideChar(CP_UTF8, 0, message.c_str(), -1, wideMessage, wideLen);

    // Setup credential UI to match the dark Windows Security Center style
    CREDUI_INFOW credInfo;
    ZeroMemory(&credInfo, sizeof(credInfo));
    credInfo.cbSize = sizeof(credInfo);
    credInfo.hwndParent = NULL;
    credInfo.pszMessageText = L"请输入您的Windows登录密码以继续";
    credInfo.pszCaptionText = L"Windows 安全中心";
    credInfo.hbmBanner = NULL;

    // 准备Windows凭据缓冲区
    PVOID outAuthBuffer = NULL;
    ULONG outAuthBufferSize = 0;
    BOOL save = FALSE;
    
    // Get current username and prepare password buffer
    wchar_t username[CREDUI_MAX_USERNAME_LENGTH + 1] = { 0 };
    wchar_t password[CREDUI_MAX_PASSWORD_LENGTH + 1] = { 0 };
    DWORD usernameSize = CREDUI_MAX_USERNAME_LENGTH + 1;
    GetUserNameW(username, &usernameSize);

    // Set flags for credential dialog
    DWORD flags = CREDUI_FLAGS_GENERIC_CREDENTIALS | CREDUI_FLAGS_ALWAYS_SHOW_UI |
                 CREDUI_FLAGS_KEEP_USERNAME | CREDUI_FLAGS_DO_NOT_PERSIST;
    
    // Add theme-specific flags
    if (theme == "dark") {
      // Dark theme settings - similar to Chrome's dark mode
      flags |= CREDUI_FLAGS_EXPECT_CONFIRMATION; // Adds additional styling for modern look
    } else if (theme == "light") {
      // Light theme settings - standard Windows credential dialog
      // No additional flags needed for light theme
    } else {
      // Default/system theme - add confirmation flag for better styling
      flags |= CREDUI_FLAGS_EXPECT_CONFIRMATION;
    }
    
    // Show credential dialog with appropriate style
    DWORD result = CredUIPromptForCredentialsW(
        &credInfo,
        L"Windows",
        NULL,
        0,
        username,
        CREDUI_MAX_USERNAME_LENGTH + 1,
        password,
        CREDUI_MAX_PASSWORD_LENGTH + 1,
        &save,
        flags);

    // Clean up
    delete[] wideMessage;

    // Process result
    if (result == ERROR_CANCELLED) {
      return Napi::Boolean::New(env, false);
    }

    // Simple validation - if password is provided and dialog wasn't canceled
    bool success = (result == NO_ERROR && wcslen(password) > 0);
    
    // Verify credentials by attempting to login
    if (success) {
      HANDLE hToken = NULL;
      success = LogonUserW(
        username,
        NULL, // Domain name, NULL for local accounts
        password,
        LOGON32_LOGON_INTERACTIVE,
        LOGON32_PROVIDER_DEFAULT,
        &hToken
      );
      
      if (success && hToken != NULL) {
        CloseHandle(hToken);
      }
      
      // Clear the password from memory
      SecureZeroMemory(password, sizeof(password));
    }
    

    return Napi::Boolean::New(env, success);
  } catch (...) {
    return Napi::Boolean::New(env, false);
  }
#else
  // Non-Windows platform
  return Napi::Boolean::New(env, false);
#endif
}

Napi::Object Init(Napi::Env env, Napi::Object exports) {
  exports.Set(Napi::String::New(env, "promptCredentials"), 
              Napi::Function::New(env, PromptCredentials));
  return exports;
}

NODE_API_MODULE(win_auth, Init) 

这里需要用到一个头文件security_defs.h

cpp 复制代码
#ifndef SECURITY_DEFS_H
#define SECURITY_DEFS_H

// This header defines SECURITY_WIN32 macro required by security.h
#define SECURITY_WIN32

#endif // SECURITY_DEFS_H

我们还需要添加一个gyp配置文件

javascript 复制代码
{
  "targets": [
    {
      "target_name": "win_auth",
      "cflags!": [ "-fno-exceptions" ],
      "cflags_cc!": [ "-fno-exceptions" ],
      "sources": [ "win_auth.cc" ],
      "include_dirs": [
        "<!@(node -p \"require('node-addon-api').include\")"
      ],
      "defines": [ "NAPI_DISABLE_CPP_EXCEPTIONS", "SECURITY_WIN32" ],
      "conditions": [
        ["OS=='win'", {
          "libraries": [ "-ladvapi32.lib", "-lcredui.lib" ],
          "msvs_settings": {
            "VCCLCompilerTool": {
              "ExceptionHandling": 1,
              "AdditionalOptions": [ "/D SECURITY_WIN32", "/utf-8" ]
            }
          }
        }]
      ]
    }
  ]
}

此时,我们需要将这个c++模块通过node-gyp编译成node模块。需要先准备环境

环境准备

全局安装node-gyp:

npm install -g node-gyp

node-gyp 需要两个核心依赖:Python 和 C++ 编译工具链。

安装python3.7+
python下载地址

安装 Visual Studio 的 C++ 构建工具。
Visual Studio下载地址

Visual Studio下载完成后,需要勾选win10/win11的sdk以及勾选 "C++ 生成工具" 或勾选 "使用 C++ 的桌面开发"进行安装(安装完成后最好重启一下电脑,让配置生效)

编译

此时,就可以进行编译了,允许node-gyp rebuild, 此时会在根目录下生成build文件夹,文件夹下的build/Release/xxx.node(xxx就是自己定义的包名)就是你需要的原生node模块

引用

接下来,我们需要暴露一个js方法,供业务侧去调用,在此js方法内,就会去调用我们编译好的build/Release/xxx.node模块,从而唤起希望凭证验证弹窗

index.js

javascript 复制代码
const os = require('os');

let nativeModule = null;
if (os.platform() === 'win32') {
  try {
    nativeModule = require('./build/Release/win_auth.node');
  } catch (e) {
    console.error('无法加载Windows认证模块:', e);
  }
}

/**
 * 显示Windows认证对话框
 * @param {string} message 提示信息
 * @param {string} theme 主题色,可选值为 'light' | 'dark',默认根据系统自动判断
 * @returns {Promise<boolean>} 认证是否成功
 */
function promptCredentials(message, theme) {
  return new Promise((resolve) => {
    if (!nativeModule) {
      resolve(false);
      return;
    }

    try {
      // 传递主题参数到原生模块
      const result = nativeModule.promptCredentials(message, theme);
      resolve(result);
    } catch (e) {
      console.error('认证错误:', e);
      resolve(false);
    }
  });
}

module.exports = {
  promptCredentials
};

准备打包

npm init 准备package.json文件,修改入口文件,添加安装包时的允许命令

javascript 复制代码
{
  "name": "fellou-win-auth",
  "version": "1.0.0",
  "main": "index.js",
  "scripts": {
    "install": "node-gyp rebuild"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  
  "description": "",
  "dependencies": {
    "node-addon-api": "^8.5.0",
    "node-gyp": "^11.4.2"
  }
}

此时就可以通过本地包的方式引用这个包文件了,在项目的package.json的依赖中添加此包文件

"win-auth": "file:.../win-auth",

运行npm install,就会自动安装此包文件

使用

在业务代码中定义一个方法,方法中去调用该方法

javascript 复制代码
try {
  const winAuth = require("win-auth")
  // 调用Windows认证,传入主题参数
  const result = await winAuth.promptCredentials(tip || "请输入您的Windows登录密码以继续", theme || 'dark');
  return result;
} catch (e) {
  console.error("windows_auth_failed", e);
  return false;
}

到此,就已经完成了全部工作了

相关推荐
Hilaku7 小时前
为什么大厂对前端算法要求极高?
前端·javascript·程序员
你可如意7 小时前
从Promise开始,一篇让你彻底建立起JS的异步编程模型
javascript
用户84298142418107 小时前
AI时代,JS混淆加密还有用吗?
前端·javascript
阿扬ABCD7 小时前
ipmitool的Windows版本编译
windows·驱动开发
申君健24864182772028 小时前
MCP TypeScript SDK v2 完整升级变化说明
javascript
Ivanqhz8 小时前
Rust Lazy浅析
java·javascript·rust
铁皮饭盒9 小时前
面试官:如何用 Bun + JS 实现安全的文件 MCP 工具集
前端·javascript·后端
王维同学10 小时前
Credential Provider、Filter 与 PLAP 的 CLSID 枚举
c++·windows·安全·注册表
weixin_BYSJ198711 小时前
springboot3家政平台小程序--附源码00904
java·javascript·spring boot·python·django·flask·php
寒水馨12 小时前
Windows下载、安装 Codex CLI(附安装包codex-x86_64-pc-windows-msvc.exe)
windows·rust·codex·终端工具·自动化编程·codex cli·ai编码代理