likes
comments
collection
share

token 和 cookie 还在傻傻分不清?

作者站长头像
站长
· 阅读数 9

token 概念和作用

Token是一种用于身份验证和授权的令牌。在Web应用程序中,当用户进行登录或授权时,服务器会生成一个Token并将其发送给客户端。客户端在后续的请求中将Token作为身份凭证携带,以证明自己的身份。

Token可以是一个字符串,通常是经过加密和签名的,以确保其安全性和完整性。服务器收到Token后,会对其进行解析和验证,以验证用户的身份并授权对特定资源的访问权限。

Token的使用具有以下特点:

  • 无状态:服务器不需要在数据库中存储会话信息,所有必要的信息都包含在Token中。
  • 可扩展性:Token可以存储更多的用户信息,甚至可以包含自定义的数据。
  • 安全性:Token可以使用加密算法进行签名,以确保数据的完整性和安全性。
  • 跨域支持:Token可以在跨域请求中通过在请求头中添加Authorization字段进行传递。

Token在前后端分离的架构中广泛应用,特别是在RESTful API的身份验证中常见。它比传统的基于Cookie的会话管理更灵活,并且适用于各种不同的客户端,例如Web、移动应用和第三方接入等。

cookie 和 token 的关系

Cookie和Token是两种不同的概念,但它们在身份验证和授权方面可以有关联。

Cookie是服务器在HTTP响应中通过Set-Cookie标头发送给客户端的一小段数据。客户端浏览器将Cookie保存在本地,然后在每次对该服务器的后续请求中将Cookie作为HTTP请求的一部分发送回服务器。Cookie通常用于在客户端和服务器之间维护会话状态,以及存储用户相关的信息。

Token是一种用于身份验证和授权的令牌。它是一个包含用户身份信息的字符串,通常是服务器生成并返回给客户端。客户端在后续的请求中将Token作为身份凭证发送给服务器,服务器通过验证Token的有效性来确认用户的身份和权限。

Cookie和Token可以结合使用来实现身份验证和授权机制。服务器可以将Token存储在Cookie中,然后发送给客户端保存。客户端在后续的请求中将Token作为Cookie发送给服务器。服务器通过验证Token的有效性来判断用户的身份和权限。这种方式称为基于Cookie的身份验证。另外,也可以将Token直接存储在请求的标头中,而不是在Cookie中进行传输,这种方式称为基于Token的身份验证。

需要注意的是,Token相对于Cookie来说更加灵活和安全,可以实现跨域身份验证,以及客户端和服务器的完全分离。而Cookie则受到一些限制,如跨域访问限制,以及容易受到XSS和CSRF攻击等。因此,在实现身份验证和授权机制时,可以选择使用Token替代或辅助Cookie。

token 一般在客户端存在哪儿

Token一般在客户端存在以下几个地方:

  • Cookie:Token可以存储在客户端的Cookie中。服务器在响应请求时,可以将Token作为一个Cookie发送给客户端,客户端在后续的请求中会自动将Token包含在请求的Cookie中发送给服务器。
  • Local Storage/Session Storage:Token也可以存储在客户端的Local Storage或Session Storage中。这些是HTML5提供的客户端存储机制,可以在浏览器中长期保存数据。
  • Web Storage API:除了Local Storage和Session Storage,Token也可以使用Web Storage API中的其他存储机制,比如IndexedDB、WebSQL等。
  • 请求头:Token也可以包含在客户端发送的请求头中,一般是在Authorization头中携带Token。

需要注意的是,无论将Token存储在哪个地方,都需要采取相应的安全措施,如HTTPS传输、加密存储等,以保护Token的安全性。

存放在 cookie 就安全了吗?

存放在Cookie中相对来说是比较常见的做法,但是并不是最安全的方式。存放在Cookie中的Token可能存在以下安全风险:

  • 跨站脚本攻击(XSS) :如果网站存在XSS漏洞,攻击者可以通过注入恶意脚本来获取用户的Cookie信息,包括Token。攻击者可以利用Token冒充用户进行恶意操作。
  • 跨站请求伪造(CSRF) :攻击者可以利用CSRF漏洞,诱使用户在已经登录的情况下访问恶意网站,该网站可能利用用户的Token发起伪造的请求,从而执行未经授权的操作。
  • 不可控的访问权限:将Token存放在Cookie中,意味着浏览器在每次请求中都会自动携带该Token。如果用户在使用公共计算机或共享设备时忘记退出登录,那么其他人可以通过使用同一个浏览器来访问用户的账户。

为了增加Token的安全性,可以采取以下措施:

  • 使用HttpOnly标识:将Cookie设置为HttpOnly,可以防止XSS攻击者通过脚本访问Cookie。
  • 使用Secure标识:将Cookie设置为Secure,只能在通过HTTPS协议传输时发送给服务器,避免明文传输。
  • 设置Token的过期时间:可以设置Token的过期时间,使得Token在一定时间后失效,减少被滥用的风险。
  • 使用其他存储方式:考虑将Token存储在其他地方,如Local Storage或Session Storage,并采取加密等额外的安全措施保护Token的安全性。

token 身份验证代码实现

服务端使用 JWT 进行 token 签名和下发

可以参考使用这个库 node-jsonwebtoken

后端代码示例 (Node.js / Express),代码简单实现如下:

const express = require('express');
const jwt = require('jsonwebtoken');
const app = express();

const secretKey = 'mysecretkey';

app.use(express.json());

app.post('/api/login', (req, res) => {
  // 从请求中获取用户名和密码
  const { username, password } = req.body;

  // 验证用户名和密码
  if (username === 'admin' && password === 'password') {
    // 用户名和密码验证成功,生成Token并返回给前端
    const token = jwt.sign({ username }, secretKey, { expiresIn: '1h' });
    res.json({ token });
  } else {
    // 用户名和密码验证失败,返回错误信息给前端
    res.status(401).json({ error: 'Authentication failed' });
  }
});

app.get('/api/protected', verifyToken, (req, res) => {
  // Token验证成功,可以访问受保护的路由
  res.json({ message: 'Protected API endpoint' });
});

function verifyToken(req, res, next) {
  const token = req.headers.authorization;

  if (!token) {
    return res.status(401).json({ error: 'Missing token' });
  }

  // 验证Token
  jwt.verify(token, secretKey, (err, decoded) => {
    if (err) {
      return res.status(401).json({ error: 'Invalid token' });
    }

    // Token验证通过,将解码后的数据存储在请求中,以便后续使用
    req.user = decoded;
    next();
  });
}

app.listen(3000, () => {
  console.log('Server is running on port 3000');
});

在上述后端代码中,我们使用了jsonwebtoken库来生成和验证Token。在登录路由/api/login中,验证用户名和密码成功后,生成一个Token并返回给前端。在受保护的路由/api/protected中,我们使用verifyToken中间件来验证请求中的Token,只有通过验证的请求才能访问该路由。

当然实际开发中, 可以使用中间件来进行 jwt 的验证, 下发方式也因人而异, 可以放在 cookie 中, 也可以作为 response 返回均可, 上述代码仅作参考;

前端代码实现示范如下

前端获取到了Token后将其存储在Cookie中,并在后续请求中自动发送给后端,可以通过以下方式实现前端代码:

import React, { useState, useEffect } from 'react';

function App() {
  const [token, setToken] = useState('');

  useEffect(() => {
    // 检查本地是否有保存的Token
    const savedToken = localStorage.getItem('token');
    if (savedToken) {
      setToken(savedToken);
    }
  }, []);

  const handleLogin = async () => {
    // 发送请求到后端进行登录验证
    const response = await fetch('http://example.com/api/login', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({ username: 'admin', password: 'password' }),
    });

    if (response.ok) {
      // 登录成功,获取Token并保存到前端
      const data = await response.json();
      setToken(data.token);
      // 保存Token到本地
      localStorage.setItem('token', data.token);
    }
  };

  const handleLogout = () => {
    // 清除保存的Token
    setToken('');
    // 清除本地保存的Token
    localStorage.removeItem('token');
  };

  return (
    <div>
      {token ? (
        <div>
          <p>Token: {token}</p>
          <button onClick={handleLogout}>Logout</button>
        </div>
      ) : (
        <button onClick={handleLogin}>Login</button>
      )}
    </div>
  );
}

export default App;