为 ASP.NET Core Blazor Web 应用中的 TOTP 验证器应用启用 QR 码生成

本文介绍如何为 ASP.NET Core Blazor Web 应用配置针对 TOTP 验证器应用的 QR 码生成功能。

要了解使用基于时间的一次性密码 (TOTP) 的验证器应用的双重身份验证 (2FA),请参阅为 ASP.NET Core 中的 TOTP 验证器应用启用 QR 码生成

在应用中搭建 Enable Authenticator 组件的基架

按照在 ASP.NET Core 项目中搭建 Identity 的基架中的指南操作,在应用中搭建 Pages\Manage\EnableAuthenticator 的基架。

注意

尽管在本示例中仅选择了 EnableAuthenticator 组件进行基架搭建,但基架搭建当前会将所有 Identity 组件添加到应用中。 此外,在应用中搭建基架的过程中可能会引发异常。 如果数据库迁移时发生异常,请在每次发生异常时停止应用并重启应用。 有关详细信息,请参阅 Blazor Web 应用的基架搭建异常 (dotnet/Scaffolding #2694)

执行迁移时请耐心等待。 根据系统的速度,数据库迁移可能最多需要一到两分钟的时间才能完成。

有关详细信息,请参阅 ASP.NET Core 项目中的基架 Identity。 有关使用 .NET CLI 而不是 Visual Studio 的指南,请参阅 dotnet aspnet-codegenerator 命令

将 QR 码添加到 2FA 配置页面

这些说明使用 Shim Sangminqrcode.js:适用于 JavaScript 的跨浏览器 QRCode 生成器davidshimjs/qrcodejs GitHub 存储库)。

qrcode.min.js 库下载到解决方案的服务器项目的 wwwroot 文件夹。 该库没有依赖项。

App 组件 (Components/App.razor) 中,将库脚本引用置于 Blazor 的 <script> 标记后

<script src="qrcode.min.js"></script>

EnableAuthenticator 组件是应用中 QR 码系统的一部分,用于向用户显示 QR 码,它采用具有增强导航的静态服务器端呈现(静态 SSR)。 因此,当组件在增强导航下加载或更新时,无法执行普通脚本。 加载页面时,需要执行其他步骤来触发 QR 码以加载到 UI 中。 若要完成 QR 代码加载,可采用带有静态服务器端呈现(静态 SSR)的 ASP.NET Core Blazor JavaScript 中介绍的方法。

将以下 JavaScript 初始化表达式添加到服务器项目的 wwwroot 文件夹。 {NAME} 占位符必须是应用程序集的名称,Blazor 才能自动查找和加载文件。 如果服务器应用的程序集名称为 BlazorSample,则该文件将命名为 BlazorSample.lib.module.js

wwwroot/{NAME}.lib.module.js

const pageScriptInfoBySrc = new Map();

function registerPageScriptElement(src) {
  if (!src) {
    throw new Error('Must provide a non-empty value for the "src" attribute.');
  }

  let pageScriptInfo = pageScriptInfoBySrc.get(src);

  if (pageScriptInfo) {
    pageScriptInfo.referenceCount++;
  } else {
    pageScriptInfo = { referenceCount: 1, module: null };
    pageScriptInfoBySrc.set(src, pageScriptInfo);
    initializePageScriptModule(src, pageScriptInfo);
  }
}

function unregisterPageScriptElement(src) {
  if (!src) {
    return;
  }

  const pageScriptInfo = pageScriptInfoBySrc.get(src);
  
  if (!pageScriptInfo) {
    return;
  }

  pageScriptInfo.referenceCount--;
}

async function initializePageScriptModule(src, pageScriptInfo) {
  if (src.startsWith("./")) {
    src = new URL(src.substr(2), document.baseURI).toString();
  }

  const module = await import(src);

  if (pageScriptInfo.referenceCount <= 0) {
    return;
  }

  pageScriptInfo.module = module;
  module.onLoad?.();
  module.onUpdate?.();
}

function onEnhancedLoad() {
  for (const [src, { module, referenceCount }] of pageScriptInfoBySrc) {
    if (referenceCount <= 0) {
      module?.onDispose?.();
      pageScriptInfoBySrc.delete(src);
    }
  }

  for (const { module } of pageScriptInfoBySrc.values()) {
    module?.onUpdate?.();
  }
}

export function afterWebStarted(blazor) {
  customElements.define('page-script', class extends HTMLElement {
    static observedAttributes = ['src'];

    attributeChangedCallback(name, oldValue, newValue) {
      if (name !== 'src') {
        return;
      }

      this.src = newValue;
      unregisterPageScriptElement(oldValue);
      registerPageScriptElement(newValue);
    }

    disconnectedCallback() {
      unregisterPageScriptElement(this.src);
    }
  });

  blazor.addEventListener('enhancedload', onEnhancedLoad);
}

将以下共享 PageScript 组件添加到服务器应用。

Components/PageScript.razor

<page-script src="@Src"></page-script>

@code {
    [Parameter]
    [EditorRequired]
    public string Src { get; set; } = default!;
}

向位于 Components/Account/Pages/Manage/EnableAuthenticator.razorEnableAuthenticator 组件添加以下并置 JS 文件。 该 onLoad 函数使用组件 @code 块中的 GenerateQrCodeUri 方法生成的 QR 代码 URI,通过 Sangmin 的 qrcode.js 库创建 QR 码。

Components/Account/Pages/Manage/EnableAuthenticator.razor.js

export function onLoad() {
  const uri = document.getElementById('qrCodeData').getAttribute('data-url');
  new QRCode(document.getElementById('qrCode'), uri);
}

EnableAuthenticator 组件中的 <PageTitle> 组件下,添加 PageScript 组件并在其中包含指向并置 JS 文件的路径:

<PageScript Src="./Components/Account/Pages/Manage/EnableAuthenticator.razor.js" />

注意

PageScript 组件方法的替代方法是使用在 afterWebStartedJS 初始化表达式中注册的事件侦听器 (blazor.addEventListener("enhancedload", {CALLBACK})) 来侦听由增强导航引起的页面更新。 回调({CALLBACK} 占位符)会执行 QR 代码初始化逻辑。

将回调方法用于 enhancedload 时,即使没有呈现 QR 码 <div> ,也会为每个增强导航执行代码。 因此,必须添加额外的代码,以在执行添加 QR 码的代码之前验证是否存在 <div>

删除包含 QR 码指令的 <div> 元素:

- <div class="alert alert-info">
-     Learn how to <a href="https://go.microsoft.com/fwlink/?Linkid=852423">enable 
-     QR code generation</a>.
- </div>

在页面中找到要显示 QR 码并存储 QR 码数据的两个 <div> 元素。

做出以下更改:

  • 对于空 <div>,将该元素的 id 指定为 qrCode
  • 对于具有 data-url 属性的 <div> 元素,请将该元素的 id 指定为 qrCodeData
- <div></div>
- <div data-url="@authenticatorUri"></div>
+ <div id="qrCode"></div>
+ <div id="qrCodeData" data-url="@authenticatorUri"></div>

更改 EnableAuthenticator 组件的 GenerateQrCodeUri 方法中的站点名称。 默认值为 Microsoft.AspNetCore.Identity.UI。 将值更改为有意义的站点名称,以便用户可以在验证器应用中轻松识别该名称以及其他应用的其他 QR 码。 保留 URL 值的编码状态。 开发人员通常会设置与公司名称匹配的站点名称。 例如、Amazon、Etsy、Microsoft、Zoho。

在以下示例中,{SITE NAME} 占位符是站点(公司)名称的位置:

private string GenerateQrCodeUri(string email, string unformattedKey)
{
    return string.Format(
        CultureInfo.InvariantCulture,
        AuthenticatorUriFormat,
-       UrlEncoder.Encode("Microsoft.AspNetCore.Identity.UI"),
+       UrlEncoder.Encode("{SITE NAME}"),
        UrlEncoder.Encode(email),
        unformattedKey);
}

运行应用并确保 QR 码可供扫描且代码可验证。

引用源中的 EnableAuthenticator 组件

可以在引用源中检查 EnableAuthenticator 组件:

引用源中的 EnableAuthenticator 组件

注意

指向 .NET 参考源的文档链接通常会加载存储库的默认分支,该分支表示针对下一个 .NET 版本的当前开发。 若要为特定版本选择标记,请使用“切换分支或标记”下拉列表。 有关详细信息,请参阅如何选择 ASP.NET Core 源代码的版本标记 (dotnet/AspNetCore.Docs #26205)

其他资源