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

肯吉·巴赫克斯 (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-only Cookie,以及一个可通过 JavaScript 访问的常规 Cookie。如果您的用户尝试在离线状态下退出,您可以清除 JavaScript Cookie,并在可能的情况下继续执行其他清理操作。如果您有 Service Worker,则可能还想利用 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 事件和广播通道 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.
        // ...
      }
    }
    

总结

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