HTML 实现登录状态记录 深入全面讲解教程

登录状态记录是Web开发的核心需求之一,其本质是在客户端存储用户身份凭证,让用户在一段时间内无需重复登录。HTML本身不直接处理登录逻辑,但可结合浏览器存储(Cookie/LocalStorage/SessionStorage)、HTTP协议和JavaScript实现完整的登录状态管理。本教程从核心原理到实战实现,全面讲解登录状态记录的设计、实现与安全规范。

一、登录状态记录核心原理

1. 登录流程与状态记录逻辑

完整的登录状态记录流程分为3步:

复制代码
用户输入账号密码 → 前端提交验证 → 后端验证通过后生成身份凭证 → 前端存储凭证 → 后续请求携带凭证 → 后端验证凭证有效性
  • 核心载体:浏览器存储(Cookie/LocalStorage)是前端记录登录状态的关键;
  • 核心原则 :凭证必须可验证、防篡改、有有效期,避免明文存储敏感信息。

2. 前端存储方案对比(核心选择)

登录状态记录的核心是选择合适的存储方式,以下是3种主流方案的对比:

存储方式 存储位置 生命周期 容量 自动携带到后端 安全性 适用场景
Cookie 浏览器(磁盘) 可设置过期时间(expires/max-age),默认会话结束失效 4KB 是(同域名请求自动携带) 支持HttpOnly/Secure/SameSite 后端主导的登录状态(推荐)
LocalStorage 浏览器(内存/磁盘) 永久存储(除非手动清除) 5MB 否(需手动携带) 无内置安全属性 前端临时存储非敏感登录信息
SessionStorage 浏览器(内存) 会话结束(关闭标签页)失效 5MB 否(需手动携带) 无内置安全属性 临时登录(仅当前会话有效)

核心结论

生产环境优先使用 Cookie(带HttpOnly/Secure) 存储登录凭证(如Token/SessionID),LocalStorage仅作为补充(如存储用户昵称等非敏感信息),SessionStorage仅用于临时会话。

二、登录状态记录核心技术

Cookie是服务器通过HTTP响应头设置、浏览器存储的小型文本数据,是登录状态记录的标准方案。

(1)Cookie 核心属性(安全关键)
属性 作用 安全意义
name=value 键值对(存储SessionID/Token) 核心凭证载体
expires 过期时间(GMT格式) 控制登录状态有效期
max-age 有效期(秒),优先级高于expires 更灵活的有效期设置
domain 生效域名(如.example.com 限制Cookie仅在指定域名生效
path 生效路径(如/ 限制Cookie仅在指定路径生效
HttpOnly 禁止JS访问Cookie 防止XSS攻击窃取凭证
Secure 仅在HTTPS协议下传输Cookie 防止HTTP明文传输被劫持
SameSite 限制跨域请求携带Cookie(Strict/Lax/None) 防止CSRF攻击
(2)设置Cookie的两种方式
  • 后端设置(推荐) :通过HTTP响应头Set-Cookie设置(可配置HttpOnly/Secure);

    示例(Node.js/Express):

    javascript 复制代码
    // 登录验证通过后设置Cookie
    app.post('/login', (req, res) => {
      const { username, password } = req.body;
      // 模拟验证(实际需查数据库)
      if (username === 'admin' && password === '123456') {
        // 生成SessionID/Token(实际需用加密算法)
        const sessionId = 'xxx-xxx-xxx';
        // 设置Cookie:有效期7天,HttpOnly+Secure+SameSite
        res.cookie('login_token', sessionId, {
          maxAge: 7 * 24 * 60 * 60 * 1000, // 7天有效期(毫秒)
          httpOnly: true, // 禁止JS访问
          secure: process.env.NODE_ENV === 'production', // 生产环境开启HTTPS
          sameSite: 'Lax', // 防止CSRF
          domain: '.example.com', // 主域名生效(子域名共享)
          path: '/'
        });
        res.json({ code: 200, msg: '登录成功' });
      } else {
        res.json({ code: 401, msg: '账号密码错误' });
      }
    });
  • 前端设置(仅用于非敏感信息):通过JS设置(无法配置HttpOnly);

    javascript 复制代码
    // 前端设置Cookie(无HttpOnly,仅演示)
    function setCookie(name, value, days) {
      const date = new Date();
      date.setTime(date.getTime() + days * 24 * 60 * 60 * 1000);
      const expires = `expires=${date.toGMTString()}`;
      // 注意:前端设置的Cookie无法加HttpOnly
      document.cookie = `${name}=${value};${expires};path=/;SameSite=Lax`;
    }
    
    // 登录成功后调用
    setCookie('username', 'admin', 7); // 存储用户名(非敏感)
(3)读取Cookie(前端)

前端可通过document.cookie读取非HttpOnly的Cookie:

javascript 复制代码
// 获取指定名称的Cookie值
function getCookie(name) {
  const cookies = document.cookie.split('; ');
  for (const cookie of cookies) {
    const [key, value] = cookie.split('=');
    if (key === name) return decodeURIComponent(value);
  }
  return '';
}

// 示例:读取用户名
const username = getCookie('username');
if (username) {
  console.log('已登录用户:', username);
}
(4)删除Cookie(退出登录)
javascript 复制代码
// 删除Cookie(设置过期时间为过去)
function removeCookie(name) {
  document.cookie = `${name}=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/`;
}

// 退出登录时调用
function logout() {
  removeCookie('username');
  // 通知后端销毁Session/Token
  fetch('/logout', { method: 'POST' })
    .then(res => res.json())
    .then(data => {
      if (data.code === 200) {
        window.location.href = '/login.html'; // 跳转到登录页
      }
    });
}

2. LocalStorage/SessionStorage 实现(前端主导)

适用于前端需要自主管理登录状态的场景(如纯前端项目、前后端分离且Token由前端携带)。

(1)存储登录信息(登录成功后)
javascript 复制代码
// 登录请求
async function login(username, password) {
  try {
    const res = await fetch('/api/login', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ username, password })
    });
    const data = await res.json();
    if (data.code === 200) {
      // 存储Token(核心凭证)和用户信息
      localStorage.setItem('token', data.token);
      localStorage.setItem('userInfo', JSON.stringify(data.userInfo));
      // SessionStorage存储临时状态(关闭标签页失效)
      sessionStorage.setItem('isLogin', 'true');
      // 跳转到首页
      window.location.href = '/index.html';
    } else {
      alert(data.msg);
    }
  } catch (err) {
    alert('登录失败:' + err.message);
  }
}
(2)验证登录状态(页面加载时)
javascript 复制代码
// 页面初始化时检查登录状态
function checkLogin() {
  const token = localStorage.getItem('token');
  const isLogin = sessionStorage.getItem('isLogin');
  // 无Token则跳转到登录页
  if (!token || !isLogin) {
    window.location.href = '/login.html';
    return false;
  }
  // 有Token则验证有效性(可选,调用后端接口)
  verifyToken(token);
  return true;
}

// 验证Token有效性
async function verifyToken(token) {
  try {
    const res = await fetch('/api/verifyToken', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${token}` // 携带Token到后端
      }
    });
    const data = await res.json();
    if (data.code !== 200) {
      // Token失效,清除存储并跳转登录
      logout();
    }
  } catch (err) {
    logout();
  }
}

// 页面加载时执行
window.onload = function() {
  checkLogin();
  // 渲染用户信息
  const userInfo = JSON.parse(localStorage.getItem('userInfo') || '{}');
  if (userInfo.username) {
    document.getElementById('userName').textContent = userInfo.username;
  }
};
(3)退出登录(清除存储)
javascript 复制代码
function logout() {
  // 清除LocalStorage
  localStorage.removeItem('token');
  localStorage.removeItem('userInfo');
  // 清除SessionStorage
  sessionStorage.clear();
  // 通知后端失效Token
  fetch('/api/logout', {
    method: 'POST',
    headers: { 'Authorization': `Bearer ${localStorage.getItem('token')}` }
  });
  // 跳转登录页
  window.location.href = '/login.html';
}

3. Token 认证(现代主流方案)

Token(令牌)是无状态的登录凭证,相比传统Session更适合分布式系统,结合Cookie/LocalStorage使用:

  • JWT(JSON Web Token):最常用的Token格式,由Header.Payload.Signature三部分组成,后端签名防篡改;
  • Token传输
    • 方案1:后端将JWT写入HttpOnly Cookie(推荐,防XSS);
    • 方案2:前端将JWT存储在LocalStorage,请求时通过Authorization头携带。
示例:JWT + LocalStorage 实现
javascript 复制代码
// 登录成功后存储JWT
async function loginWithJWT(username, password) {
  const res = await fetch('/api/login', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ username, password })
  });
  const data = await res.json();
  if (data.code === 200) {
    // 存储JWT Token
    localStorage.setItem('jwt_token', data.token);
    // 解析Token获取用户信息(可选,Payload部分)
    const payload = JSON.parse(atob(data.token.split('.')[1]));
    localStorage.setItem('expire_time', payload.exp); // 存储过期时间
    window.location.href = '/index.html';
  }
}

// 检查Token是否过期
function isTokenExpired() {
  const expireTime = localStorage.getItem('expire_time');
  if (!expireTime) return true;
  // 对比当前时间戳(秒)和过期时间戳
  return Date.now() / 1000 > expireTime;
}

// 请求拦截:携带Token
async function requestWithToken(url, options = {}) {
  const token = localStorage.getItem('jwt_token');
  if (token && !isTokenExpired()) {
    options.headers = {
      ...options.headers,
      'Authorization': `Bearer ${token}`
    };
  }
  return fetch(url, options);
}

// 示例:获取用户信息(带Token请求)
requestWithToken('/api/userInfo')
  .then(res => res.json())
  .then(data => console.log(data));

三、完整登录状态记录实战(前后端分离)

1. 前端登录页面(login.html)

包含表单、验证、登录请求、状态存储:

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>登录页</title>
  <style>
    .login-form { max-width: 300px; margin: 50px auto; padding: 20px; border: 1px solid #ddd; }
    .form-item { margin-bottom: 15px; }
    label { display: block; margin-bottom: 5px; }
    input { width: 100%; padding: 8px; box-sizing: border-box; }
    button { width: 100%; padding: 10px; background: #007bff; color: white; border: none; cursor: pointer; }
    .error { color: red; font-size: 12px; margin-top: 5px; display: none; }
  </style>
</head>
<body>
  <div class="login-form">
    <h3>用户登录</h3>
    <div class="form-item">
      <label for="username">用户名</label>
      <input type="text" id="username" required placeholder="请输入用户名">
    </div>
    <div class="form-item">
      <label for="password">密码</label>
      <input type="password" id="password" required placeholder="请输入密码">
      <div class="error" id="errorTip"></div>
    </div>
    <div class="form-item">
      <label>
        <input type="checkbox" id="remember"> 记住我(7天免登录)
      </label>
    </div>
    <button onclick="handleLogin()">登录</button>
  </div>

  <script>
    // 登录处理函数
    async function handleLogin() {
      const username = document.getElementById('username').value.trim();
      const password = document.getElementById('password').value.trim();
      const remember = document.getElementById('remember').checked;
      const errorTip = document.getElementById('errorTip');

      // 前端基础验证
      if (!username || !password) {
        errorTip.textContent = '用户名和密码不能为空';
        errorTip.style.display = 'block';
        return;
      }

      try {
        // 提交登录请求
        const res = await fetch('/api/login', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ username, password, remember })
        });
        const data = await res.json();

        if (data.code === 200) {
          // 存储登录状态(根据"记住我"选择存储方式)
          const expires = remember ? 7 : 0; // 7天或会话有效
          if (remember) {
            // 记住我:LocalStorage存储Token(7天)+ Cookie存储用户名
            localStorage.setItem('token', data.token);
            setCookie('username', username, 7);
          } else {
            // 不记住:SessionStorage存储Token
            sessionStorage.setItem('token', data.token);
            setCookie('username', username, 0);
          }
          // 跳转到首页
          window.location.href = '/index.html';
        } else {
          errorTip.textContent = data.msg;
          errorTip.style.display = 'block';
        }
      } catch (err) {
        errorTip.textContent = '网络错误,请重试';
        errorTip.style.display = 'block';
      }
    }

    // Cookie操作工具函数
    function setCookie(name, value, days) {
      let expires = '';
      if (days > 0) {
        const date = new Date();
        date.setTime(date.getTime() + days * 24 * 60 * 60 * 1000);
        expires = `expires=${date.toGMTString()}`;
      }
      document.cookie = `${name}=${encodeURIComponent(value)};${expires};path=/;SameSite=Lax`;
    }

    // 页面加载时检查是否已登录(防止重复登录)
    window.onload = function() {
      const token = localStorage.getItem('token') || sessionStorage.getItem('token');
      if (token) {
        window.location.href = '/index.html';
      }
    };
  </script>
</body>
</html>

2. 首页(index.html):验证登录状态 + 展示用户信息

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>首页</title>
</head>
<body>
  <div id="userPanel">
    <span id="userName"></span>
    <button onclick="logout()">退出登录</button>
  </div>
  <div id="content" style="display: none;">
    <h1>欢迎访问首页</h1>
    <!-- 登录后可见的内容 -->
  </div>

  <script>
    // 页面加载时验证登录状态
    window.onload = async function() {
      const token = localStorage.getItem('token') || sessionStorage.getItem('token');
      const username = getCookie('username');

      // 无Token或用户名,跳转到登录页
      if (!token || !username) {
        window.location.href = '/login.html';
        return;
      }

      // 验证Token有效性
      const isValid = await verifyToken(token);
      if (!isValid) {
        logout();
        return;
      }

      // 渲染用户信息和内容
      document.getElementById('userName').textContent = `欢迎,${username}`;
      document.getElementById('content').style.display = 'block';
    };

    // 验证Token
    async function verifyToken(token) {
      try {
        const res = await fetch('/api/verifyToken', {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json',
            'Authorization': `Bearer ${token}`
          }
        });
        const data = await res.json();
        return data.code === 200;
      } catch (err) {
        return false;
      }
    }

    // 退出登录
    function logout() {
      // 清除存储
      localStorage.removeItem('token');
      sessionStorage.removeItem('token');
      removeCookie('username');
      // 通知后端
      fetch('/api/logout', {
        method: 'POST',
        headers: { 'Authorization': `Bearer ${localStorage.getItem('token') || sessionStorage.getItem('token')}` }
      });
      // 跳转登录页
      window.location.href = '/login.html';
    }

    // 工具函数:获取Cookie
    function getCookie(name) {
      const cookies = document.cookie.split('; ');
      for (const cookie of cookies) {
        const [key, value] = cookie.split('=');
        if (key === name) return decodeURIComponent(value);
      }
      return '';
    }

    // 工具函数:删除Cookie
    function removeCookie(name) {
      document.cookie = `${name}=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/`;
    }
  </script>
</body>
</html>

3. 后端核心逻辑(Node.js/Express 示例)

javascript 复制代码
const express = require('express');
const jwt = require('jsonwebtoken'); // 需安装:npm i jsonwebtoken
const app = express();
app.use(express.json());

// 模拟用户数据库
const users = [{ username: 'admin', password: '123456' }];
// JWT密钥(生产环境需用环境变量)
const JWT_SECRET = 'your-secret-key-123';

// 登录接口
app.post('/api/login', (req, res) => {
  const { username, password, remember } = req.body;
  const user = users.find(u => u.username === username && u.password === password);
  
  if (!user) {
    return res.json({ code: 401, msg: '账号或密码错误' });
  }

  // 生成JWT Token(根据"记住我"设置有效期)
  const expiresIn = remember ? '7d' : '1h'; // 7天或1小时
  const token = jwt.sign(
    { username: user.username, exp: Math.floor(Date.now() / 1000) + (remember ? 7*24*60*60 : 60*60) },
    JWT_SECRET
  );

  res.json({ code: 200, msg: '登录成功', token });
});

// 验证Token接口
app.post('/api/verifyToken', (req, res) => {
  const token = req.headers.authorization?.split(' ')[1];
  if (!token) {
    return res.json({ code: 401, msg: '未登录' });
  }

  try {
    // 验证Token
    jwt.verify(token, JWT_SECRET);
    res.json({ code: 200, msg: 'Token有效' });
  } catch (err) {
    res.json({ code: 401, msg: 'Token失效' });
  }
});

// 退出登录接口(前端清除存储,后端无需操作JWT,仅记录日志)
app.post('/api/logout', (req, res) => {
  res.json({ code: 200, msg: '退出成功' });
});

// 静态文件托管(前端页面)
app.use(express.static('public'));

// 启动服务
app.listen(3000, () => {
  console.log('服务运行在 http://localhost:3000/login.html');
});

四、安全规范(重中之重)

登录状态记录涉及用户隐私,必须遵守以下安全规则,避免数据泄露或攻击:

1. 核心安全原则

  • 禁止明文存储密码:前端仅传递密码,后端必须哈希存储(如bcrypt),示例中为简化用明文,生产环境需替换;
  • Token/密码必须HTTPS传输:HTTP协议下数据易被劫持,生产环境强制开启HTTPS;
  • HttpOnly Cookie:敏感凭证(如SessionID/Token)必须通过后端设置HttpOnly Cookie,禁止前端JS访问(防XSS);
  • Token必须有有效期:避免永久有效的凭证,即使泄露也能限制危害;
  • SameSite Cookie :设置SameSite=Lax/Strict,防止CSRF攻击;
  • Secure Cookie :仅在HTTPS下传输Cookie,设置Secure属性。

2. 常见攻击防护

攻击类型 防护措施
XSS 1. 凭证用HttpOnly Cookie;2. 输入输出转义;3. 开启CSP策略
CSRF 1. SameSite=Strict/Lax;2. 后端验证Referer/Origin;3. 使用CSRF Token
中间人攻击 1. 强制HTTPS;2. 配置HSTS头;3. 避免明文传输
令牌劫持 1. 缩短Token有效期;2. 敏感操作二次验证;3. 检测异常IP/设备

3. 生产环境优化

  • 密钥/敏感配置 :使用环境变量(如process.env.JWT_SECRET),避免硬编码;

  • Token刷新机制:实现无感刷新(Token快过期时自动请求新Token);

  • 多端登录管理:记录登录设备,支持踢下线;

  • 日志监控:记录登录/退出/Token验证日志,及时发现异常;

  • 密码加密 :后端用bcrypt/argon2哈希存储密码,示例:

    javascript 复制代码
    const bcrypt = require('bcrypt');
    // 加密密码
    const hashPassword = async (pwd) => await bcrypt.hash(pwd, 10);
    // 验证密码
    const verifyPassword = async (pwd, hash) => await bcrypt.compare(pwd, hash);

五、常见问题与解决方案

1. 记住我功能失效

  • 检查Cookie/LocalStorage的有效期设置是否正确;
  • 确认浏览器未禁用Cookie(隐私模式可能禁用LocalStorage);
  • 后端Token有效期需与前端存储有效期一致。

2. Token过期后无法自动刷新

  • 解决方案:实现Token刷新接口,前端在Token过期前(如剩余5分钟)请求新Token;

    javascript 复制代码
    // 刷新Token示例
    async function refreshToken() {
      const oldToken = localStorage.getItem('jwt_token');
      const res = await fetch('/api/refreshToken', {
        method: 'POST',
        headers: { 'Authorization': `Bearer ${oldToken}` }
      });
      const data = await res.json();
      if (data.code === 200) {
        localStorage.setItem('jwt_token', data.newToken);
      }
    }

3. 跨域请求时Cookie无法携带

  • 后端配置CORS允许凭证:

    javascript 复制代码
    app.use(cors({
      origin: 'http://localhost:8080', // 前端域名
      credentials: true // 允许携带Cookie
    }));
  • 前端请求时设置credentials: 'include'

    javascript 复制代码
    fetch('/api/login', {
      method: 'POST',
      credentials: 'include', // 携带Cookie
      // ...其他配置
    });

4. 退出登录后Token仍可使用

  • JWT是无状态的,无法主动失效,解决方案:
    1. 缩短Token有效期(如1小时);
    2. 后端维护黑名单(Redis存储失效的Token);
    3. 敏感操作需二次验证(如修改密码)。

六、总结

登录状态记录的核心是安全存储凭证 + 有效验证凭证,关键要点:

  1. 优先选择HttpOnly Cookie存储敏感凭证(防XSS),LocalStorage仅存储非敏感信息;
  2. 采用Token(JWT)实现无状态认证,适配分布式系统;
  3. 必须遵守安全规范(HTTPS、有效期、HttpOnly/SameSite);
  4. 前端负责存储/携带凭证,后端负责生成/验证凭证;
  5. 生产环境需优化密码存储、Token刷新、日志监控等细节。
相关推荐
(づど)2 小时前
一套齐全的环境设置:nvm\node\nrm\pnpm
前端·笔记
晷龙烬2 小时前
Vue 3 自定义指令:从“瑞士军刀”到“专属工具” !
前端·javascript·vue.js
MediaTea2 小时前
思考与练习(第四章 程序组成与输入输出)
java·linux·服务器·前端·javascript
BD_Marathon2 小时前
【JavaWeb】NPM_简介和相关配置
前端·npm·node.js
咸鱼加辣2 小时前
【前端框架】react
前端·react.js·前端框架
unicrom_深圳市由你创科技2 小时前
Vue 3 高效开发技巧总结
前端·javascript·vue.js
HIT_Weston2 小时前
66、【Ubuntu】【Gitlab】拉出内网 Web 服务:Gitlab 配置审视(十)
前端·ubuntu·gitlab
长空任鸟飞_阿康2 小时前
LangChain 技术栈全解析:从模型编排到 RAG 实战
前端·python·langchain
chilavert3182 小时前
技术演进中的开发沉思-258 Ajax:自定义事件
前端·ajax·okhttp