怎样才算是良好的退出体验?

Kenji Baheux
Kenji Baheux

退出

当用户退出网站时,他们是在表明自己希望完全摆脱个性化用户体验。因此,请务必尽可能贴近用户的心智模型。例如,适当的退出体验还应考虑用户在决定退出之前可能打开的所有标签页。

要想提供出色的退出体验,关键在于在用户体验的视觉和状态方面保持一致。本指南提供了有关注意事项和如何提供良好退出体验的具体建议。

主要注意事项

在网站上实现退出功能时,请注意以下几点,以确保退出流程顺畅、安全且直观:

  • 清晰且一致的退出体验:提供清晰且始终可见的退出按钮或链接,确保用户在整个网站上都能轻松识别和访问。避免使用含糊不清的标签,也不要将退出功能隐藏在不显眼的菜单、子页面或其他不直观的位置。
  • 确认提示:在完成退出流程之前实现确认提示。这有助于防止用户意外退出登录,并让用户重新考虑是否真的需要退出登录,例如,如果他们已使用强密码或其他身份验证机制妥善锁定了设备。
  • 处理多个标签页:如果用户在不同标签页中打开了同一网站的多个网页,请确保从一个标签页中退出登录后,该网站的所有其他打开的标签页也会随之更新。
  • 重定向到安全着陆页:用户成功退出登录后,请将其重定向到一个安全着陆页,并明确指明用户已退出登录。避免将用户重定向到包含任何个性化信息的网页。同样,确保其他标签页也不再反映已登录状态。此外,请确保您没有构建攻击者可以利用的开放重定向
  • 会话清理:用户退出账号后,请彻底移除与用户会话相关联的所有敏感用户会话数据、Cookie 或临时文件。这可防止对用户信息或账号活动进行未经授权的访问,还可防止浏览器从其各种缓存(尤其是返回/前进缓存)中恢复包含敏感信息的网页。
  • 错误处理和反馈:如果用户在退出账号时遇到任何问题,请向用户提供清晰的错误消息或反馈。如果退出账号流程失败,请告知用户任何潜在的安全风险或数据泄露。
  • 无障碍功能注意事项:确保残障用户(包括使用屏幕阅读器或键盘导航等辅助技术的用户)可以使用退出机制。
  • 跨浏览器兼容性:在不同的浏览器和设备上测试退出功能,确保其能够一致且可靠地运行。
  • 持续监控和更新:定期监控退出账号流程,以防出现任何潜在漏洞或安全漏洞。及时实施更新和补丁,以解决发现的任何问题。
  • 身份联合:如果用户使用联合身份登录,请查看是否支持同时从身份提供方退出登录,以及是否需要这样做。此外,如果身份提供程序支持自动登录,请务必阻止自动登录

正确做法

  • 如果您在退出账号流程(或其他访问权限撤消流程)中在服务器上使 Cookie 失效,请务必一并删除用户设备上的 Cookie。
  • 清理您可能存储在用户设备上的所有敏感数据:Cookie、localStoragesessionStorageindexedDBCacheStorage 和任何其他本地数据存储区。
  • 确保返回的任何包含敏感数据的资源(尤其是 HTML 文档)都带有 Cache-control: no-store HTTP 标头,以便浏览器不会将这些资源存储在永久存储空间(例如磁盘)中。同样,返回敏感数据的 XHR/fetch 调用也应设置 Cache-Control: no-store HTTP 标头,以防止任何缓存。
  • 确保用户设备上所有打开的标签页都已更新为反映服务器端撤消的访问权限。

在退出账号时清理敏感数据

退出账号后,不妨考虑清除临时数据和本地存储的敏感数据。之所以重点关注敏感数据,是因为清除所有数据会导致用户体验明显下降,因为这类用户很可能会回访。例如,如果您要清除所有本地存储的数据,那么用户就必须重新确认 Cookie 意见征求提示,并完成其他流程,就像他们从未访问过您的网站一样。

如何清理 Cookie

在用于确认已退出账号状态的页面的响应中,附加 Set-Cookie HTTP 标头以清除与敏感数据相关的或包含敏感数据的所有 Cookie。为安全起见,请将 expires 值设置为很久以前的日期,并将 Cookie 的值设置为空字符串。

Set-Cookie: sensitivecookie1=; expires=Thu, 01 Jan 1970 00:00:00 GMT; secure
Set-Cookie: sensitivecookie2=; expires=Thu, 01 Jan 1970 00:00:00 GMT; secure
...

离线场景

虽然上述方法对于常规用例来说已经足够,但如果用户在离线状态下工作,则无法使用此方法。您不妨考虑使用两个 Cookie 来跟踪已登录状态:一个仅限 HTTPS 的安全 Cookie,一个可通过 JavaScript 访问的常规 Cookie。如果用户在离线状态下尝试退出登录,您可以清除 JavaScript Cookie,并尽可能继续执行其他清理操作。如果您使用的是服务工件,则可能还需要利用 Background Fetch API 在用户稍后在线时重试请求,以清除服务器上的状态。

如何清理存储空间

在用于确认已退出账号状态的页面的响应中,请务必从各种数据存储区中清理敏感数据:

  • sessionStorage:虽然此存储空间会在用户终止与您网站的会话时清除,但建议您在用户退出账号时主动清理敏感数据,以防用户忘记关闭在您网站上打开的所有标签页。

    // Remove sensitive data from sessionStorage
    sessionStorage.removeItem('sensitiveSessionData1');
    // ...
    
    // Or if everything in sessionStorage is sensitive, clear it all
    sessionStorage.clear();
    
  • localStorageindexedDBCache/Service Worker API:在用户退出账号时,清理您可能使用这些 API 存储的所有敏感数据,因为此类数据会跨会话保留。

    // Remove sensitive data from localStorage:
    localStorage.removeItem('sensitiveData1');
    // ...
    
    // Or if everything in localStorage is sensitive, clear it all:
    localStorage.clear();
    
    // Delete sensitive object stores in indexedDB:
    const name = 'exampleDB';
    const version = 1;
    const request = indexedDB.open(name, version);
    
    request.onsuccess = (event) => {
      const db = request.result;
      db.deleteObjectStore('sensitiveStore1');
      db.deleteObjectStore('sensitiveStore2');
    
      // ...
    
      db.close();
    }
    
    // Delete sensitive resources stored via the Cache API:
    caches.open('cacheV1').then((cache) => {
      await cache.delete("/personal/profile.png");
    
      // ...
    }
    
    // Or better yet, clear a cache bucket that contains sensitive resources:
    caches.delete('personalizedV1');
    

如何清理缓存

  • HTTP 缓存:只要您对包含敏感数据的资源设置了 Cache-control: no-store,HTTP 缓存便不会保留任何敏感数据。
  • 前进/返回缓存:同样,如果您遵循了有关 Cache-control: no-store 和在用户退出时清除敏感 Cookie(例如与身份验证相关的安全 HTTPS 专用 Cookie)的建议,则无需担心敏感数据会保留在前进/返回缓存中。事实上,如果往返缓存功能检测到以下一个或多个信号,则会移除使用 Cache-control: no-store HTTP 标头提供的同源网页:
    • 一个或多个仅限 HTTPS 的安全 Cookie 已被修改或删除。
    • 网页发出的 XHR/fetch 调用的一条或多条响应包含 Cache-control: no-store HTTP 标头。

跨标签页提供一致的用户体验

在决定退出账号之前,用户可能会打开您网站的多个标签页。到那时,他们可能已经忘记了其他标签页,甚至其他浏览器窗口。最好避免依赖用户关闭所有相关标签页和窗口。相反,请采取积极主动的态度,确保用户在各个标签页中的登录状态保持一致。

操作方法

如需在各个标签页中实现一致的已登录状态,请考虑组合使用 pageshow/pagehide 事件和 Broadcast Channel API。

  • pageshow 事件:在持久化 pageshow 后,检查用户的登录状态,并在用户不再处于登录状态时清除敏感数据(甚至整个网页)。请注意,在从返回/前进导航恢复后,pageshow 事件将在页面首次呈现之前触发,这可确保您的登录状态检查可让您将页面重置为非敏感状态。

    window.addEventListener('pageshow', (event) => {
      if (event.persisted && !document.cookie.match(/my-cookie)) {
        // The user has logged out.
        // Force a reload, or otherwise clear sensitive information right away.
        body.innerHTML = '';
        location.reload();
      }
    });
    
  • Broadcast Channel API:使用此 API 在标签页和窗口之间传达登录状态更改。如果用户已退出账号,请清除所有敏感数据,或者在包含敏感数据的所有标签页和窗口中重定向到退出账号页面。

    // Upon logout, broadcast new login state so that other tabs can clean up too:
    const bc = new BroadcastChannel('login-state');
    bc.postMessage('logged out');
    
    // [...]
    const bc = new BroadcastChannel('login-state');
    bc.onMessage = (msgevt) => {
      if (msgevt.data === 'logged out') {
        // Clean up, reload or navigate to the sign-out page.
        // ...
      }
    }
    

总结

按照本文档中的指南,您将能够设计出出色的退出用户体验,以防止意外退出并保护用户的个人信息。