前端登录凭证

1. 为什么需要登录凭证?

  • web开发中,我们使用最多的协议是 http ,但是 http 是一个无状态的协议。

    • 无状态的协议?什么叫做无状态协议呢?
  • 举个例子:

  • 我们登录了一个网站www.yyds.com (当然这个网站不存在);

  • 登录的时候我们需要输入用户名和密码:

    比如用户名 fct,密码:fct666.;

  • 登录成功之后,我们要以 fct 的身份去访问其他的数据和资源,还是通过http请求去访问。

    • yyds的服务器会问:你谁呀?
    • fct 说:我是 fct 呀,刚刚登录过呀;
    • yyds:怎么证明你刚刚登录过呀?
    • fct 说:这........ http没有告诉你吗?
    • yydshttp 的每次请求对我来说都是一个独立的请求,和之前请求过什么没有关系。
  • 看到了吧?这就是 http 的无状态,也就是服务器不知道你上一步做了什么,我们必须得有一个办法可以证明用户之前已经登录过。

2. 常见登录凭证

  • cookie + session

  • Token 令牌

3.1 cookie概念

Cookie(复数形态 Cookies ),又称为“小甜饼”。类型为“ 小型文本文件 ",某些网站为了辨别用户身份而存储在用户本地终端( Client Side )上的数据。
浏览器会在特定的情况下携带上 cookie 来发送请求,我们可以通过cookie来获取一些信息;

Cookie总是保存在客户端中,按在客户端中的存储位置,Cookie可以分为内存Cookie硬盘Cookie

  • 内存Cookie由浏览器维护,保存在内存中,浏览器关闭时Cookie就会消失,其存在时间是短暂的;
  • 硬盘Cookie保存在硬盘中,有一个过期时间,用户手动清理或者过期时间到时,才会被清理;

如何判断一个 cookie内存cookie还是硬盘cookie呢?

  • 没有设置过期时间,默认情况下cookie是内存cookie,在关闭浏览器时会自动删除;
  • 有设置过期时间,并且过期时间不为0或者负数的cookie,是硬盘cookie,需要手动或者到期时,才会删除;

3.3.1 cookie的生命周期

默认情况下的cookie是内存cookie,也称之为会话cookie,也就是在浏览器关闭时会自动被删除;

我们可以通过设置 expires 或者 max-age 来设置过期的时间:

  • expires :设置的是 Date.toUTCString();设置格式是:expires=date-in-GMTString-format ;(到某个时刻cookie失效)
  • max-age :设置过期的秒钟;max-age=max-age-in-seconds (例如一年为60*60*24*365);(过去多久cookie失效)

3.3.2 cookie的作用域

访问哪些域名的哪个路径时会默认携带上 cookie,称为cookie的作用域。也就是允许cookie发送给哪些URL。

  • Domain :指定哪些主机(域名)可以接收 cookie。

    • 如果不指定,那么默认是 origin(即同域名请求会携带),不包括子域名;

      域名为 www.fct.com,子域名为www.music.fct.com

    • 如果指定 Domain,则包含子域名。例如,如果设置 Domain=mozila.org ,则 Cookie 也包含在子域名中(如 develope.mozilla.org )。

  • Path:指定主机下哪些路径可以接收cookie

    • 例如,设置 Path=/docs ,则以下地址都会匹配︰
      • /docs
      • /docs/Web/
      • /docs/Web/HTTP
// 1. 设置成键值对
// document.cookie = "key=value";

// 2. 不设置过期时间,默认是内存(会话)cookie
document.cookie = "name=fct";

// 3. 设置过期时间,5s后
document.cookie = "hobby=running;max-age=5;";
const express = require("express");
// 下载中间件 npm i cookie-parser
const cookieParser = require("cookie-parser");

const app = express();

// 使用cookie中间件
app.use(cookieParser());

app.get("/test", (req, res, next) => {
  // 设置cookie,过 10s 后 cookie 失效
  res.cookie("username", "fct666", { maxAge: 50 * 1000, httpOnly: true });
  res.send("服务器设置cookie");
});

app.get("/demo", (req, res, next) => {
  // 获取 cookie
  console.log(req.cookies);
  console.log(req.cookies.username); // fct666 

  res.send("获取cookie");
});

app.listen(8000, () => {
  console.log("服务器设置cookie的服务器启动成功!");
})

4. session

4.1 服务器端设置、获取

const express = require("express");
// 下载中间件 npm i express-session
const session = require("express-session");

const app = express();

app.use(session({
 // name,返回客户端的key的名称,默认为connect.sid,也可以自己设置。
 name: "session_id",
 // secret,一个 String类型的字符串,作为服务器端生成session的签名。
 secret: 'keyboard cat',
 // resave,即使session没有被修改,也保存session值,默认为true
 resave: false,
 // saveUninitialized:强制未初始化的session保存到数据库
 saveUninitialized: true
}));

// 设置 session
app.get("/login", (req, res, next) => {
 req.session.userinfo = "付常涛";
 req.session.user = { id: 123, name: "fct" };
 res.send('登录成功');
});

// 获取 session
app.get("/", (req, res, next) => {
 console.log(req.session.user);// { id: 123, name: 'fct' }
 if (req.session.userinfo) {
   res.send('你好,' + req.session.userinfo + '欢迎回来');
 } else {
   res.send('未登录');
 }
});

app.listen(8000, () => {
 console.log("设置 session 的服务器启动成功!");
})

5. session和cookie 的缺点

  • Cookie 会被附加在每个HTTP请求中所以无形中增加了流量(事实上某些请求是不需要的);
  • Cookie 是明文传递的,所以存在安全性的问题;
  • Cookie的大小限制是 4KB ,对于复杂的需求来说是不够的;
  • 对于浏览器外的其他客户端(比如 iOS、Android ),必须手动设置 cookie 和session ;
  • 对于分布式系统和服务器集群中如何可以保证其他系统也可以正确的解析session ?

6. Token

由于session和cookie的缺点,在目前前后端分离的开发过程中,多使用Token进行身份验证。

  • token可以翻译为令牌
  • 在验证了用户账号和密码正确的情况,给用户颁发一个令牌
  • 这个令牌作为后续用户访问一些接口或者资源的凭证;
  • 我们可以根据这个令牌凭证来判断用户是否有权限来访问;

Token的使用分为两部分:

  • 生成 token :登录的时候,颁发token ;
  • 验证 token :访问某些资源或者接口时,验证token ;

1. JWT实现Token机制

JWT(JSON WEB TOKEN)生成的 Token 由三部分组成:

1.1 header

  • alg:采用的加密算法,默认是 HMAC SHA256 ( HS256),采用同一个密钥进行加密和解密(对称加密);
  • typ:JWT,固定值,通常都写成JWT即可;
  • 会通过 base64Url 算法进行编码。

1.2 payload

  • 携带的数据,比如我们可以将用户的 id 和 name 放到 payload 中;
  • 默认也会携带 iat ( issued at ),令牌的签发时间;
  • 我们也可以设置过期时间:exp ( expiration time ) ;
  • 会通过 base64Url 算法进行编码。

1.3 signature

  • 设置一个secretKey,通过将前两个的结果合并后进行 HMACSHA256 的算法;
  • HMACSHA256(base64Url(header) + . + base64Url(payload), secretKey)
  • 但是如果secretKey暴露是一件非常危险的事情,因为之后就可以模拟颁发 token ,也可以解密 token ;

代码实现(使用默认HS256加密算法)对称加密:

const express = require("express");
// 引入 jsonwebtoken
const jwt = require('jsonwebtoken');

const app = express();
// 对称加密
const SECRET_KEY = "nAnafct";

// 登录,颁发 token令牌
app.get("/login", (req, res, next) => {
  const user = { id: 123, name: "fct" };
  const token = jwt.sign(user, SECRET_KEY, {
    expiresIn: 50 // 50s 后过期
  });
  res.end(token);
});

// 读取token,验证token
app.get("/home", (req, res, next) => {
  const authorization = req.headers.authorization;
  const token = authorization.replace("Bearer ", "");
  // 验证 token 是否有效
  try {
    const result = jwt.verify(token, SECRET_KEY);
    res.send(result);// {"id": 123,"name": "fct","iat": 1638958655,"exp": 1638958705}
  } catch (error) {
    res.send({
      status: 400,
      message: "用户认证失效"
    });
  }
});

app.listen(8000, () => {
  console.log("服务器设置cookie的服务器启动成功!");
})

2. 非对称加密

HS 256加密算法是对称加密算法。当密钥暴露时,每一个都可以颁发令牌,非常危险!因此我们可以使用非对称加密,RS 256。

  • 私钥:用于发布令牌;
  • 公钥:用于验证令牌;

使用OpenSSL产生私钥和公钥:

windows中使用git bash终端窗口。mac电脑自带终端即可。

> openssl # 进入openssl
# 生成私钥
OpenSSL> genrsa -out private.key 1024
# 生成对应的公钥
OpenSSL> rsa -in private.key -pubout -out public.key

使用非对称加密:

const express = require("express");
// 引入 jsonwebtoken
const jwt = require('jsonwebtoken');
const fs = require("fs");

const app = express();

// 读取私钥,公钥
const PRIVATE_KEY = fs.readFileSync("./keys/private.key");
const PUBLIC_KEY = fs.readFileSync("./keys/public.key");

// 登录,颁发 token令牌
app.get("/login", (req, res, next) => {
  const user = { id: 123, name: "fct" };
  const token = jwt.sign(user, PRIVATE_KEY, {
    expiresIn: 50, // 50s 后过期
    algorithm: "RS256", // 指定加密算法
  });
  res.end(token);
});

// 读取token,验证token
app.get("/home", (req, res, next) => {
  const authorization = req.headers.authorization;
  const token = authorization.replace("Bearer ", "");
  try {
    const result = jwt.verify(token, PUBLIC_KEY, {
      algorithms: ["RS256"]
    });
    res.send(result);
    /* {
        "id": 123,
        "name": "fct",
        "iat": 1638960914,
        "exp": 1638960964
      }*/
  } catch (error) {
    res.send({
      status: 400,
      message: "用户认证失效"
    });
  }
});

app.listen(8000, () => {
  console.log("token服务器启动成功!");
})
posted @ 2021-12-09 11:17  青柠i  阅读(304)  评论(0编辑  收藏  举报