所有文章 > 技术杂货铺 > React 损坏认证指南: 示例和预防
React 损坏认证指南: 示例和预防

React 损坏认证指南: 示例和预防

我确信您有时必须在 React 应用中设置身份验证。但您如何有效地处理会话管理,同时兼顾社交场合? 

React 应用程序中的身份验证漏洞可能会导致攻击者入侵用户帐户。但这不是您可以检测和预防的单一漏洞。您需要开发一种范例来保护您的应用程序免受许多身份验证漏洞的影响。在这篇文章中,我将带您了解它在 React 应用程序中的样子以及如何预防它。

什么是失效身份验证?

身份验证失效包括一系列身份验证漏洞,攻击者可以利用这些漏洞进行攻击。攻击者可以利用这些漏洞代表应用的合法用户进行身份验证。这让攻击者可以窃取用户的凭据并访问该特定用户的私有资源。 

身份验证主要在服务器端处理。但是,您可以在客户端实施一些技术来防止身份验证失败。让我们详细了解这些技术。

由于实际场景导致的身份验证失败

很多时候,开发人员都假设他们的用户将始终使用私人设备在他们的应用中进行身份验证。但在很多现实世界中,情况并非如此。 

例如,用户可能在网吧使用公共计算机。或者他们可能使用公共网络/WiFi 使用您的 Web 应用。或者他们家里有访客或出租车上有陌生人使用他们的设备做某事。您是否看过由基努·里维斯主演的《敲门》,其中陌生人使用基努登录的 Facebook 帐户发布内容?这是一个社交场景,攻击者利用用户的疏忽对其造成损害。 

用户通常容易忘记退出账户,尤其是在自己的设备上。如果用户的设备丢失或被盗,攻击者就可以完全控制用户的账户。 

如果您在应用中实施身份验证时从未考虑过这些情况,那么您的身份验证必定会失败。那么解决方案是什么呢?

将会话 ID 映射到设备 ID 和 IP 地址/位置

Session-Id 是您创建的唯一 UUID,用于将会话与数据库中的用户进行映射。例如,如果用户在您的应用中进行了身份验证,您的后端服务器将发回一个会话 ID。当用户注销时,此会话 ID 将被清除。

但是,仅将会话 ID 映射到用户是不够的。想象一下攻击者试图伪装成合法用户从自己的设备进行身份验证的情况。在这种情况下,设备 ID 将能够告诉您会话是否是从其他设备触发的。

React 中的 useDeviceId Hook

您需要从客户端检索设备 ID。那么我们该怎么做呢?我们可以在 React 应用程序中使用名为 FingerprintJS 的库来实现这一点。在这里,我创建了一个 React 钩子,它从 FingerprintJS 库中获取设备 ID 并将其返回:

import FingerprintJS from '@fingerprintjs/fingerprintjs-pro'
import { useEffect, useState } from 'react';


export default function useDeviceId(){

const [fingerPrintInstance,setFingerPrintInstance]=useState();
const [deviceId,setDeviceId]=useState();

useEffect(()=>{
const fpPromise = FingerprintJS.load({
token: process.env.REACT_APP_FINGERPRINT_BROWSER_TOKEN
})
setFingerPrintInstance(fpPromise)
},[])

useEffect(()=>{
if(fingerPrintInstance){
getFingerPrintInfo()
}
},[fingerPrintInstance])

const getFingerPrintInfo=()=>{
fingerPrintInstance
.then(fp => fp.get())
.then(result => setDeviceId(result.visitorId))
}
return { deviceId }
}

您需要一个 Fingerprint 浏览器令牌(相当于 API 密钥)才能使上述操作生效。我通过在 FingerprintJS 上创建一个帐户并从我的仪表板获取该密钥来获取我的令牌。 

接下来,我们还需要用户的位置数据。我们可以使用 Geolocation API 来检索用户的位置信息和 IP 地址。请注意,您可以使用更好的服务来获取更准确、更合适的位置数据。但是,基本原理是一样的。

React 中的 useLocation Hook

这是您可以在 React 应用中使用的另一个钩子,它可以为您提供用户的位置信息:

import {useState, useEffect} from 'react';

export default function useLocation(){

const [locationData,setLocationData]=useState();

useEffect(()=>{
getLocationData();
},[])

const getLocationData=async()=>{
const response=await fetch('https://geolocation-db.com/json/');
const data=await response.json();
setLocationData(data)
}

return {locationData}
}

现在让我们快速使用这些钩子来查看它们是否有效。在 App.js 内部,我只是调用了这些钩子并在模板中显示了信息:

import './App.css';
import useDeviceId from './hooks/useDeviceId';
import useLocation from './hooks/useLocation';

function App() {

const {deviceId}=useDeviceId()

const {locationData}=useLocation();

return (
<div className="App">
<header className="App-header">
<h3>Device ID - {deviceId}</h3>
<h3>Location Info -
<div style={{color:'darkgray',display:'flex',flexDirection:'column'}}>
{Object.keys(locationData).map(locationDataKey=>
<span style={{marginRight:10}}>{locationDataKey} - {locationData[locationDataKey]}</span>)}
</div>
</h3>
</header>
</div>
);
}

export default App;

如果你检查浏览器,你应该会得到该信息:

用户位置信息的示例。

您可以将这些钩子与客户端身份验证集成。每次用户尝试登录或注册时,您都可以将此信息发送到服务器。然后,服务器可以验证设备 ID 和位置信息,以发回一个标志,指示会话是否来自不同的位置、不同的设备等。

将会话 ID 与用户的位置和设备 ID 映射。

然后,您可以相应地提醒用户,或者通过调用您的注销 API 自动将其注销。 

空闲用户自动退出

当用户空闲时,会话超时很有用。

空闲用户的会话超时比您想象的更重要。大多数金融网站实施它们仅仅是为了安全目的。有三个步骤可以为您的应用的空闲用户实现自动会话超时。 

首先,您需要一个空闲会话 TTL。这意味着您需要检查空闲用户多长时间才能触发会话终止。接下来,您需要检测用户活动以检查用户是处于活动状态还是空闲状态。最后,当您的 React 应用检测到您的用户空闲了指定的时间时,您需要调用您的 Logout API。

如何实现空闲用户的自动退出。

假设您从服务器收到空闲会话超时通知。其余两个步骤需要在客户端执行。我们可以使用名为react-idle-timer 的包轻松检测用户在 React 中是处于活动状态还是空闲状态。

React 中的 useIdle Hook

您需要先运行以下命令安装react-idle-timer :

npm install react-idle-timer --save

我们将这个逻辑抽象到一个名为useIdle.js的单独钩子中,如下所示:

import { useState, useEffect } from 'react';
import { useIdleTimer } from 'react-idle-timer'

export default function useIdle({
onIdle, //Function that gets executed when user is idle
debounce=500, //Debounce, default value is 500
idleTime=15 //Idle time in minutes
}){
const [isIdle,setIsIdle]=useState();

const handleOnIdle = event => {
setIsIdle(true);
console.log('user is idle', event)
console.log('last active', getLastActiveTime())
onIdle();
}

const handleOnActive = event => {
setIsIdle(false)
console.log('user is active', event)
console.log('time remaining', getRemainingTime())
}

const handleOnAction = event => {
setIsIdle(false)
console.log('user did something', event)
}

const { getRemainingTime, getLastActiveTime } = useIdleTimer({
timeout: 1000 * 60 * idleTime,
onIdle: handleOnIdle,
onActive: handleOnActive,
onAction: handleOnAction,
debounce: 500
})

return {
getRemainingTime,
getLastActiveTime,
isIdle
}
}

您可以通过 GitHub从react-idle-timer了解useIdleTimer钩子的工作原理。简而言之,这个钩子给我们返回一个标志,我们可以使用它来检查用户是空闲还是活动。我们给它传递一个名为onIdle 的函数。每当检测到用户空闲时,就会触发此函数。 

由于我们需要在检测到空闲时注销用户,因此我们可以将注销函数传递给useIdle钩子。以下是它在 App.js 中的简单用法:

import './App.css';
import useIdle from './hooks/useIdle';

function App() {

const logout=()=>console.log('user logout');


const {isIdle}=useIdle({onIdle:logout,idleTime:0.25})

return (
<div className="App">
<header className="App-header">
{ isIdle ? 'User will be logged out' : 'User is not idle'}
</header>
</div>
);
}
export default App;

您需要在上面使用的注销功能中调用相关的注销服务。

由于会话管理不善导致身份验证失败

会话管理是指如何处理用户会话。它包括以下内容: 

  • 如何在每次会话中为用户生成会话 ID
  • 您在前端存储会话 ID 的位置
  • 在前端存储 JWT/身份验证/刷新令牌的位置
  • 如何处理应用程序的会话超时

所有这些因素可帮助您验证会话管理是否足够差,是否允许攻击者以合法用户身份进入您的应用程序。

不良的会话管理通常会导致应用程序的身份验证中断。

如果您不注意如何创建和存储会话 ID,攻击者可以代表用户发出身份验证请求。 

例如,如果攻击者掌握了用户的会话 ID,他们将能够更改密码或检索该用户的个人信息。 

实施安全会话管理

大多数会话管理技术都适用于服务器端。这是因为客户端只处理如何存储会话 ID。但是,您需要小心在 React 应用中如何以及在何处存储会话 ID。

避免使用查询字符串来存储与会话相关的信息。

很多时候,开发人员会将session-id存储在前端 URL 中,这样用户和攻击者都可以清楚地看到它。相反,您可以将session-id存储在浏览器存储中,并通过自定义 React hook 在应用程序的任何组件或页面中使用它。 

React 中的 useSession Hook

以下是 React 中useSession钩子的简单实现。它将本地存储中的会话 ID 与自己的状态同步。它返回此会话 ID ,因此您只需调用此钩子即可从 React 应用程序中的任何位置检索会话 ID:

import { useEffect, useState } from "react"

export default function useSession({session_id}){

const [sessionId,setSessionId]=useState()

useEffect(()=>{
if(session_id) setSessionId(session_id);
else{
if(localStorage.session_id) setSessionId(localStorage.session_id)
}
},[])

useEffect(()=>{
if(!localStorage.session_id) localStorage.setItem('session_id',sessionId)
},[sessionId])

return{
sessionId
}
}

因此,现在每当您需要在特定页面上使用会话 ID 时,都可以从此钩子中获取它。因此,您无需将其作为查询参数传递到 React 应用的路由中。 

结论

我们创建了一系列 React 钩子来防止 React 应用中身份验证失效。不过,您也可以使用相同的逻辑并在其他框架中实现相同的功能。

您甚至可以在用户注册时实施强密码检查。弱凭证是攻击者破坏您的身份验证工作流程的绝佳机会。最糟糕的是,您无法对泄露和绕过的凭证采取任何措施。但是,您可以通过确保用户设置的密码难以通过暴力破解来防止很多此类情况发生。 

文章来源:React Broken Authentication Guide: Examples and Prevention

#你可能也喜欢这些API文章!