但是,僅將會話 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,他們將能夠更改密碼或檢索該用戶的個人信息。 

實施安全會話管理

大多數會話管理技術都適用于服務器端。這是因為客戶端只處理如何存儲會話 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

上一篇:

Rails 過度數據暴露:示例與預防

下一篇:

.NET 安全指南
#你可能也喜歡這些API文章!

我們有何不同?

API服務商零注冊

多API并行試用

數據驅動選型,提升決策效率

查看全部API→
??

熱門場景實測,選對API

#AI文本生成大模型API

對比大模型API的內容創意新穎性、情感共鳴力、商業轉化潛力

25個渠道
一鍵對比試用API 限時免費

#AI深度推理大模型API

對比大模型API的邏輯推理準確性、分析深度、可視化建議合理性

10個渠道
一鍵對比試用API 限時免費