单设备登录

SDL(Single Device Login)是一种单设备登录的机制,它允许用户在同一时间只能在一个设备上登录,当用户在其他设备上登录时,之前登录的设备会被挤下线。

应用场景

  1. 视频影音,防止一个账号共享,防止一些账号贩子
  2. 社交媒体平台:社交媒体平台通常有多种安全措施来保护用户账户,其中之一就是单设备登录。这样可以防止他人在未经授权的情况下访问用户的账户,并保护用户的个人信息和隐私
  3. 对于在线购物和电子支付平台,用户的支付信息和订单详情是敏感的。通过单设备登录,可以在用户进行支付操作时增加额外的安全层级,确保只有授权设备可以进行支付操作
  4. 对于电子邮箱和通讯应用,用户的个人和机密信息都存储在其中。通过单设备登录机制,可以确保用户的电子邮箱或通讯应用只能在一个设备上登录,避免账户被他人恶意使用

实现思路

设计数据结构
{
 id:{
    socket:ws实例
    fingerprint:浏览器指纹
  }
}
  1. 第一次登录的时候记录用户id,并且记录socket信息,和浏览器指纹
  2. 当有别的设备登录的时候发现之前已经连接过了,便使用旧的socket发送下线通知,并且关闭旧的socket,更新socket替换成当前新设备的ws连接
浏览器指纹

指纹技术有很多种,这里采用canvas指纹技术

实现代码

nodejs端

import express from 'express'
import { WebSocketServer } from 'ws'
import cors from 'cors'
const app = express()
app.use(cors())
app.use(express.json())
//存放数据结构
const connection = {}

const server = app.listen(3000)
const wss = new WebSocketServer({ server })

wss.on('connection', (ws) => {
    ws.on('message', (message) => {
        const data = JSON.parse(message)
        if (data.action === 'login') {
            if (connection[data.id] && connection[data.id].fingerprint) {
                console.log('账号在别处登录')
                //提示旧设备
                connection[data.id].socket.send(JSON.stringify({
                    action:'logout',
                    message:`你于${new Date().toLocaleString()}账号在别处登录` 
                }))
                connection[data.id].socket.close() //断开旧设备连接
                connection[data.id].socket = ws //更新ws
            } else {
                console.log('首次登录')
                connection[data.id] = {
                    socket: ws, //记录ws
                    fingerprint: data.fingerprint //记录指纹
                }
            }
        }
    })
})

浏览器端

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <h1>SDL</h1>
    <script src="./md5.js"></script>
    <script>
       //浏览器指纹
        const createBrowserFingerprint = () => {
            const canvas = document.createElement('canvas')
            const ctx = canvas.getContext('2d')
            ctx.fillStyle = 'red'
            ctx.fillRect(0, 0, 1, 1)
            return md5(canvas.toDataURL())
        }
        //谷歌abf12f62e03d160f7f24144ef1778396
        //火狐80bea69bfc7cad5832d12e41714cf677
        //Edge abf12f62e03d160f7f24144ef1778396

        const ws = new WebSocket('ws://192.168.120.145:3000') //socket本地IP+端口
        ws.addEventListener('open', () => {
            ws.send(JSON.stringify({
                action: 'login', //动作登录
                id: 1, //用户ID
                fingerprint: createBrowserFingerprint() //浏览器指纹
            }))
        })
        ws.addEventListener('message', (message) => {
            const data = JSON.parse(message.data)
            if (data.action === 'logout') {
                alert(data.message) //监听到挤下线操作提示弹框
            }
        })

    </script>
</body>

</html>
04-24 05:29