美文网首页
cookie和session、jwt

cookie和session、jwt

作者: 三省吾身_9862 | 来源:发表于2022-03-03 11:53 被阅读0次

domain 属性

domain标识指定了哪些主机可以访问该Cookie的域名。

cookie的domain 可以访问cookie的接口域名
.baidu.com baidu.com、map.baidu.com、*.baidu.com
baidu.com baidu.com
重点:cookie中的domain限制,限制的是接口域名,不是浏览器访问域名;
图一 图二

上图一中:我们能看到baidu.com下,有哪些cookie;
上图二中:证明,当我们浏览器访问127.0.0.1网站,但是这个网站 调用https://www.baidu.com/sugrec接口时候,会带上domain为baidu.com的cookie;而这些cookie却不是127.0.0.1网站的。

  • 注意:测试的时候,因为这是跨域请求,所以axios要配置允许跨域携带cookie:
_axios.defaults.withCredentials = true

这就是CSRF攻击;假如我们登录了银行网站;然后又点击了一个钓鱼网站,这个钓鱼网站去请求银行网站转账接口,就会带上登录成功的cookie去请求。所以cookie不被推荐使用,它是浏览器设置的一个bug。

  • 导致这个问题有两个原因:
  1. 浏览器发送http请求会自动携带cookie
  2. A网站 访问B域名接口,会带上B域名的cookie;也就是A网站能通过http请求拿到B网站的cookie(直接js拿是拿不到)。
  • 解决方法:
    第一种方法:给cookie设置属性SameSite=Strict;
Set-Cookie: CookieName=CookieValue; SameSite=Strict;

第二种方法:很多网站开始使用 token,存放到localStorage。这样不同网站域名下,只能访问自己网站域名下的localStorage;http请求也不会自动携带localStorage的值给服务器;

SameSite 属性

  1. Strict最为严格,完全禁止第三方 Cookie,跨站点时,任何情况下都不会发送 Cookie。换言之,只有浏览器域名和接口域名一致,才会带上 Cookie。
Set-Cookie: CookieName=CookieValue; SameSite=Strict;
  1. Lax规则稍稍放宽,跨站点时,大多数情况也是不发送第三方 Cookie,但是导航到目标网址的 Get 请求除外。
Set-Cookie: CookieName=CookieValue; SameSite=Lax;

导航到目标网址的 GET 请求,只包括三种情况:链接,预加载请求,GET 表单。详见下表。

请求类型 示例 正常情况 Lax
链接 <a href="..."></a> 发送 Cookie 发送 Cookie
预加载 <link rel="prerender" href="..."/> 发送 Cookie 发送 Cookie
GET 表单 <form method="GET" action="..."> 发送 Cookie 发送 Cookie
POST 表单 <form method="POST" action="..."> 发送 Cookie 不发送
iframe <iframe src="..."></iframe> 发送 Cookie 不发送
AJAX $.get("...") 发送 Cookie 不发送
Image <img src="..."> 发送 Cookie 不发送

设置了Strict或Lax以后,基本就杜绝了 CSRF 攻击。当然,前提是用户浏览器支持 SameSite 属性。

  1. None
    Chrome 计划将Lax变为默认设置。这时,网站可以选择显式关闭SameSite属性,将其设为None。不过,前提是必须同时设置Secure属性(Cookie 只能通过 HTTPS 协议发送),否则无效。
    下面的设置无效。
Set-Cookie: widget_session=abc123; SameSite=None

下面的设置有效。

Set-Cookie: widget_session=abc123; SameSite=None; Secure

HttpOnly

设置HttpOnly=true的cookie不能被js获取到,无法用document.cookie打出cookie的内容

Secure

Secure属性是说如果一个cookie被设置了Secure=true,那么这个cookie只能用https协议发送给服务器,用http协议是不发送的

expries

expries是指cookie的过期时间
值为session时,意思是和seesion同样的时间失效,属于一个默认值,将会在你关闭浏览器之后失效。


session

session是一种概念,实现session有很多框架,不同的实现方式;总体来说,session这个概念就是一个map类型;下面是我自己来简单实现,代码中解释

var app = require('express')() 

// 这个就是session
const session = {}

// 登录成功,为浏览器设置cookie,并且把cookie作为session的key存起来,值才是真正的数据,为了安全,不发送给浏览器
app.use('/api/login', (req, res) => {
   // 登录成功
  const key = 'yijkadf23as'
  res.setHeader('set-cookie', `session=${key}; httponly=true;`)
  session[key] = {
    username: 'wkp',
    age: 30
  }
})

// 获取用户信息;带上cookie,作为key在session对象中找出,真正的数据
app.get('/api/user', (req, res) => {
  const cookies = req.get('cookie').split(';')
  let cookieMap = {}
  cookies.forEach(item => {
    let arr = item.split('=')
    cookieMap[item[0]] = item[1]
  })
  console.log(cookieMap) // {session: 'yijkadf23as'}
  // 拿到 key
  const key = cookieMap.session;
  // 拿到用户信息
  const user = session[key]
  console.log(user) // { username: 'wkp', age: 30 }
})

上面代码就实现了简单的session设计:

  1. 登录成功,生成一个随机数作为cookie发送给浏览器,同时把这个随机数作为key,用户数据位置 value;保存在session对象中,为了保证用户信息安全性,我们只发送一个cookie给浏览器,不发送真正的用户信息。
  2. 下次访问接口,浏览器http请求默认会带上cookie。拿到cookie,得到上一步生成的随机数,作为key在session对象中找出用户数据value;

那么,显而易见session不一定要用cookie配合实现,可以token等其他方法

express框架实现session

  • 第一种方式:express-session ,结合了session和cookie
const express = require('express')
const session = require('express-session')

const app = express()
// 包装请求体
app.use(require('express-body'));

// 使用第三方已经封装好的一套session框架;
app.use(session({
  secret: 'keyboard cat',
  resave: false,
  saveUninitialized: true,
}))

app.use(express.static('./www'))

app.post('/api/login', (req, res) => {
  const {username, pwd} = req.body;
  req.session.user = req.body
  req.session.isLogin = true

  res.send({status: 0, msg: '登录成功!'})
})

app.get('/api/user', (req, res) => {
  if (req.session.isLogin) {
    res.send({status: 0, data: req.session.user})
  } else {
    res.send({status: 1, msg: '登录过期'})
  }
})

app.listen(4000, () => console.log('http://127.0.0.1:3000'))

jwt: 适合跨域登录认证

  • jwt原理:
  1. 服务端把用户信息 加密后,形成字符串,发送给浏览器;
  2. 浏览器保存到localStorage或sessionStorage;每次请求在请求头加上Authorization:token(服务加密的那个字符串);
  3. 服务器收到请求,拿到请求头字段Authorization,进行解密,还原用户信息。
  • token字符串,有三部分组成:
// 头部.用户信息.签名
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb25uZWN0LnNpZCI6InM6bDNrX1JGYVE2Qzg0Vmt1b3hLdlRLckNoT3JQUXFMaHkuRC90N0JOK1pKd1hDS29LOHZsWCtMWHErUHpVOGh5ZVZCa2o2QXE2c2FaOCIsInVzZXJuYW1lIjoid2twIiwicHdkIjoiMTIzNDU2IiwiaWF0IjoxNjQ2MzYxNjYyLCJleHAiOjE2NDYzNjE2OTJ9.XotDm9iMwXSZHElk16htZ9kgSk4PmFk6TK3I4SGsyWo
  • 第一种方式:jsonwebtoken + express-jwt
    服务端 server.js
const express = require('express')
const jwt = require('jsonwebtoken') // 加密
const expressJWT = require('express-jwt') // 解密

const app = express()
app.use(express.static('./www'))

// 包装请求体
app.use(require('express-body'));

const secretKey = 'wkp ^_^';
/**
 * 配置了expressJWT中间件,req上面会有个user属性,后面接口可以通过req.user拿到
 * unless,path :不需要权限的接口
 */
app.use(expressJWT({secret: secretKey, algorithms: ['HS256']}).unless({path: ['/', '/api/login']}))

app.post('/api/login', (req, res) => {
  const {username, pwd} = req.body;
  // 加密用户信息
  const token = jwt.sign(req.body, secretKey, {expiresIn: '30s'})
  res.send({status: 0, msg: '登录成功!', token})
})

app.get('/api/user', (req, res) => {
  // 这个req.user是 expressJWT 中间件生成挂载到req的
  res.send({status: 0, data: req.user})
})

// 错误处理中心
app.use((err, req, res, next) => {
  // token解析失败错误
  if (err.name === 'UnauthorizedError') {
    res.send({status: 1, msg: 'token过期'})
  } else {
    res.send({status: 1, msg: '未知错误'})
  }
})
app.listen(4000, () => console.log('http://127.0.0.1:3000'))

浏览器端 index.html

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

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <script src="https://cdn.bootcdn.net/ajax/libs/axios/0.26.0/axios.min.js"></script>
</head>

<body>
  <button id="J_login">登录</button>
  <button id="J_get">获取</button>

  <script>
    document.getElementById('J_get').onclick = () => {
      axios({
        url: '/api/user',
        headers: {
          Authorization: `Bearer ${localStorage.token}`
        }
      }).then(res => {
        console.log(res.data.data)
      })
    }
    document.getElementById('J_login').onclick = () => {
      axios.post('/api/login', { username: 'wkp', pwd: '123456' }).then(res => {
        localStorage.token = res.data.token;
      })
    }
  </script>
</body>

</html>

相关文章

网友评论

      本文标题:cookie和session、jwt

      本文链接:https://www.haomeiwen.com/subject/fpwyrrtx.html