【练习/后端】JWT实现新设备登录顶掉旧设备的一种简单实现

中心思想就是在JWT令牌中生成一个随机数,并记录这个令牌的第一次生成的时间。如果之后使用这个令牌进行登录但发现当前记录的最近一次的登录令牌不是这个,就要阻止登录。

下面的checkAndDealWithExistedLogin()方法返回true说明允许登录,false则是令牌失效。

public class LoginManager {
    private static final ConcurrentHashMap<Integer, DeviceInfo> LOGIN_USER_DEVICE_MAP = new ConcurrentHashMap<>();
    private static final ConcurrentHashMap<Integer, Lock> USER_LOCK_MAP = new ConcurrentHashMap<>();

    public static boolean checkAndDealWithExistedLogin() {
        Integer currentUserId = getCurrentUserId();
        String currentUserDeviceId = getCurrentUserDeviceId();
        // 为每个用户加一个锁,防止一个用户同时在多个设备登录并避免阻塞过多线程
        lock(currentUserId);
        try {
            // 没登录过直接放行,并记录到map
            if (!LOGIN_USER_DEVICE_MAP.containsKey(currentUserId)) {
                LOGIN_USER_DEVICE_MAP.put(currentUserId, new DeviceInfo(currentUserDeviceId, System.currentTimeMillis()));
                return true;
            }
            Long currentUserLoginTimestamp = getCurrentUserLoginTimestamp();
            DeviceInfo deviceInfo = LOGIN_USER_DEVICE_MAP.get(currentUserId);
            // 仍然是上一个设备登录,直接放行
            if (deviceInfo.id.equals(currentUserDeviceId)) {
                return true;
            } else {
                // 当前登录新于已存在登录,修改信息并放行
                if (deviceInfo.loginTimestamp < currentUserLoginTimestamp) {
                    LOGIN_USER_DEVICE_MAP.put(currentUserId, new DeviceInfo(getCurrentUserDeviceId(), getCurrentUserLoginTimestamp()));
                    return true;
                }
                // 当前为旧登录,拒绝访问
                return false;
            }
        } finally {
            unlock(currentUserId);
        }
    }

    private static void lock(Integer userId) {
    	// 使用putIfAbsent实现原子性
        USER_LOCK_MAP.putIfAbsent(userId, new ReentrantLock());
        USER_LOCK_MAP.get(userId).lock();
    }

    private static void unlock(Integer userId) {
        USER_LOCK_MAP.get(userId).unlock();
    }

	// 下面3个方法使用了ThreadLocal,
	// 就是在LoginFilter过滤器中把请求头中携带的token数据进行解析,
	// 并放入其中,供本次请求对应的线程在处理整个流程中获取
	// 代码见下面
    public static String getCurrentUserDeviceId() {
        return LoginFilter.DECODED_JWT_THREADLOCAL.get().getClaim("deviceId").asString();
    }

    public static Integer getCurrentUserId() {
        return LoginFilter.DECODED_JWT_THREADLOCAL.get().getClaim("userId").asInt();
    }

    public static Long getCurrentUserLoginTimestamp() {
        return LoginFilter.DECODED_JWT_THREADLOCAL.get().getClaim("loginTimestamp").asLong();
    }

    @Data
    @AllArgsConstructor
    private static class DeviceInfo {
        String id;
        Long loginTimestamp;
    }
}

LoginFilter中的片段:

String jwt = request.getHeader(LoginFilter.TOKEN_KEY);
try {
    DECODED_JWT_THREADLOCAL.set(JwtUtils.verify(jwt));
    boolean pass = LoginManager.checkAndDealWithExistedLogin();
    if (pass) {
    	// 通过就继续处理
        filterChain.doFilter(request, response);
    } else {
    	// 否则抛异常和jwt无效一同处理
        throw new RuntimeException();
    }
} catch (Exception e) {
    response.getOutputStream().write(gson.toJson(Resp.error(RespCode.JWT_TOKEN_INVALID,
            e.getMessage())).getBytes());
} finally {
	// 在这里一定要移除在ThreadLocal中存储的登录令牌
	// 因为不手动移除,ThreadLocal的设计可能会导致内存泄漏
    DECODED_JWT_THREADLOCAL.remove();
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,以下是一个简单的用户注册和登录功能的实现: 1. HTML 页面 首先,我们需要创建一个用于用户注册和登录的 HTML 页面。页面包含一个表单,用户可以在其中输入用户名和密码。此外,我们还需要为表单中的按钮添加点击事件,以便触发注册和登录功能。以下是一个示例 HTML 页面: ```html <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>用户注册和登录</title> </head> <body> <h1>用户注册和登录</h1> <form id="user-form"> <label>用户名:</label> <input type="text" id="username" name="username"><br> <label>密码:</label> <input type="password" id="password" name="password"><br> <button type="button" id="register-btn">注册</button> <button type="button" id="login-btn">登录</button> </form> <script src="app.js"></script> </body> </html> ``` 2. CSS 样式 为了美化我们的页面,我们可以添加一些 CSS 样式。以下是一个示例 CSS 文件: ```css h1 { text-align: center; } form { margin: 0 auto; width: 300px; text-align: center; } label { display: inline-block; width: 80px; text-align: right; margin-right: 10px; } input[type="text"], input[type="password"] { width: 200px; margin-bottom: 10px; } button { margin-right: 10px; } ``` 3. Node.js 后端 接下来,我们需要使用 Node.js 来实现后端功能。我们将使用 Express 框架来搭建服务器,并使用 jsonwebtoken 库来处理用户认证。 首先,我们需要安装 Express 和 jsonwebtoken: ``` npm install express jsonwebtoken ``` 然后,我们可以创建一个名为 "server.js" 的文件,并在其中编写以下代码: ```javascript const express = require('express'); const bodyParser = require('body-parser'); const jwt = require('jsonwebtoken'); const app = express(); const JWT_SECRET = 'my_secret_key'; const users = []; app.use(bodyParser.json()); app.post('/register', (req, res) => { const { username, password } = req.body; // 检查用户名是否已存在 if (users.find(user => user.username === username)) { return res.status(409).send('用户名已存在'); } // 将用户添加到用户列表 users.push({ username, password }); // 创建 JWT token const token = jwt.sign({ username }, JWT_SECRET); // 返回 JWT token res.status(201).send({ token }); }); app.post('/login', (req, res) => { const { username, password } = req.body; // 查找用户 const user = users.find(user => user.username === username); // 检查用户名和密码 if (!user || user.password !== password) { return res.status(401).send('用户名或密码错误'); } // 创建 JWT token const token = jwt.sign({ username }, JWT_SECRET); // 返回 JWT token res.send({ token }); }); app.listen(3000, () => { console.log('服务器正在运行'); }); ``` 以上代码创建了一个 Express 应用程序,并定义了两个路由:一个用于用户注册,另一个用于用户登录。当用户注册或登录成功时,服务器将使用 jsonwebtoken 库创建一个 JWT token 并将其返回给客户端。 4. JavaScript 客户端 最后,我们需要在客户端中编写 JavaScript 代码来处理用户输入并将其发送到后端。以下是一个示例 JavaScript 文件: ```javascript const userForm = document.querySelector('#user-form'); const registerBtn = document.querySelector('#register-btn'); const loginBtn = document.querySelector('#login-btn'); const API_URL = 'http://localhost:3000'; registerBtn.addEventListener('click', () => { const username = document.querySelector('#username').value; const password = document.querySelector('#password').value; // 发送注册请求 fetch(`${API_URL}/register`, { method: 'POST', body: JSON.stringify({ username, password }), headers: { 'Content-Type': 'application/json' } }) .then(response => response.json()) .then(data => { // 将 JWT token 存储到本地存储中 localStorage.setItem('token', data.token); alert('注册成功'); }) .catch(error => { console.error(error); alert('注册失败'); }); }); loginBtn.addEventListener('click', () => { const username = document.querySelector('#username').value; const password = document.querySelector('#password').value; // 发送登录请求 fetch(`${API_URL}/login`, { method: 'POST', body: JSON.stringify({ username, password }), headers: { 'Content-Type': 'application/json' } }) .then(response => response.json()) .then(data => { // 将 JWT token 存储到本地存储中 localStorage.setItem('token', data.token); alert('登录成功'); }) .catch(error => { console.error(error); alert('登录失败'); }); }); ``` 以上代码在客户端中添加了两个点击事件,用于注册和登录用户。当用户点击 "注册" 或 "登录" 按钮时,JavaScript 将用户输入发送到后端,并在成功时存储 JWT token 到本地存储中。 以上就是一个简单的用户注册和登录功能的实现。当用户成功注册或登录后,他们将获得一个 JWT token,并可以使用该 token 来访问受保护的资源。在实际应用中,我们可能需要更多的安全性和功能来确保用户数据的保护和隐私。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值