nodejs使用session进行登录认证

基于Token 认证和session 认证的比较

前言:本文解决的问题

  • 基于session 认证的不足
  • 基于 token 认证的过程
  • session VS tooken

1.传统基于Cookie 认证的过程

长期以来,基于Session的认证(Session based authentication)一直处于主流地位。由于http协议是无状态的,借助cookie,客户端登陆成功后,服务端就能识别其后续请求,而不需要每次都登陆。它是有状态的(statefull),也就是服务端和客户端都需要保存生成的session*,也就是说在服务端需要在数据库中追踪session是否alive,客户端要把session写入cookie中。基本过程如下:

  • 客户端登陆,一般输入用户名和密码
  • 服务端如果验证通过,就会生成session,并把它存入数据库中
  • 客户端在浏览器上会产生cookie,并把session写入
  • 客户端后续有新的请求,都会在请求后携带sessIon,发给服务端
  • 如果客户端登陆出去(log out),该生成的session就会在客户端和服务端都被销毁
    如下图:


    image

基于Session 认证的不足
正如图片所示,服务端需要保存每个用户的session,这对于很多访问用户的情景来说,服务端的负担很重,需要大量的的资源来存储session;另一方面不能很好解决跨域资源共享问题Cross-Origin Resource Sharing (CORS)。最重要的是cookie的使用引入了很多不安全因素,招致了很多专门针对cookie的攻击。

2. 基于token的认证

近年来,基于token的认证开始成为主流,不管是单个网页,还是web app,还是Internet of Things 。该认证方式是无状态的(stateless),客户端登陆成功后,服务端会生成一个token并把它返还给客户端,由于是无状态的,服务端不再保存该Token**。

问题来了,当客户端再发送请求时,服务端如何判断它曾经已经登陆了?类似Sesssion based authentication,客户端每次发送请求时也会携带Token。由于这里的Token是服务端用自己的密钥签名的,当它受到客户但的Token时,只需要再用自己的密钥去验证,就可以判断这个Token是不是刚自己签发的。这里面的核心就是用签名和验证,从而减轻了服务端的负担,无需再存储session,尤其是对于大型的分布式应用,减负很多。以下是具体的过程:

  • 客户端用自己的机密信息登陆,如用户名和密码
  • 服务端验证,验证通过,生成Token返还给客户端(一般用哈希算法,再加个随机数)。
  • 客户端把Token写入local storage(本地内存),后续请求都携带该Token
  • 服务端收到请求时验证Token,如果验证通过,则允许用户访问相应资源


    token based authentication

问题

由于这里Token是后续用来登陆的唯一认证手段,如果用户关闭了网页,被其它别有用心的应用窃取到Token,拿它再来登陆就危险了。因此这里需要服务端给Tooken设置过期时间(expired time),不能太长,太长不安全;太短用户体验差。另外,当用户登出时,服务端要把当前Token设为黑名单(back list),防止被冒用。

3. 基于token的优点

  • 无状态,stateless

服务端无需保存生成的Token,它需要做的就是签发Token,并验证它。由于Token中是含有需要验证的所有信息(签名算法、用户信息、签名),这就让服务端负载减轻了很多

  • 交叉域和交叉域共享Cross-domain / CORS

Cookie和CORS在不同的域效果不好。而使用基于Token可以使API应用到不同的服务和域中。

  • 可以在JWT中存储数据 Store Data in the JWT

我们知道,现通用的Token based Authentication 就是JOSN Web Token(jwt)。JWT包含头部(header),载荷(claim set), 和签名(signature)。可以在载荷中存放预定义的元数据,只要是JOSON格式就可以了。

  • 不需要CSRF(cross-site-request-forgery)防护

由于不需要依赖Cookie,自然不用担心Cookie被截获,用户信息被伪造登陆问题。

代码补充

由于某系逻辑不需要故进行了删减。详细可参考注释信息。

4. 使用nodejs实现session

4.1. 获取cookie并写入session

//www.js
const http = require("http")
const PORT = 3000

const querystring = require('querystring')
const serverHandle = require("../app")
//创建服务,处理请求
const server = http.createServer(serverHandle)

server.listen(PORT, () => {
  console.log('listening on PORT' + PORT)
})
//app.js
//...
let SESSION_DATA ={}
//JavaScript对象是存储在内存的堆中的,只要该进程不被重启,
//或使用某种淘汰算法淘汰其中的某些值,那么这个对象中的值和对象将长时间存在。


//当某一个请求进来后,下面的函数被激活,每个链接都会获取当前请求的cookie和sessionID
//如果cookie中没有sessionID,则生成一个sessionID并在SESSION_DATA中添加
//因此当浏览器在首次请求时,服务器仍然会返回一个sessionID。
const handleServer = (req,res)=>{
/...
//获取cookie
req.cookie = {}
  const cookieStr = req.headers.cookie || ''
  cookieStr.split(';').forEach(item => {
    if (!item) {
      return
    }
    const arr = item.split('=')
    const key = arr[0].trim()
    const val = arr[1].trim()
    req.cookie[key] = val
  });

//解析session
  let userId = req.cookie.userid //判断请求cookie中是否有被添加userid(sessionID)
  let needSetCookie = false //是否需要set-cookie
  if (userId) {//cookie中有userid(sessionID)(之前set-cookie过)
    if (!SESSION_DATA[userId]) {//服务器内存中不存在该session(可能过期)
      SESSION_DATA[userId] = {}//使用该sessionID初始化一个新的session
    }
  }
  else {//客户端未被添加过sessionID
    needSetCookie = true
    userId = (Date.now() + (Math.random() * 100).toFixed(0)) + ''//生成sessionID
    SESSION_DATA[userId] = {}//初始化session
  }
//设置req.session 指向全局的SESSION_DATA中的某一条session(也就是刚才生成的或者cookie中sessionID相同的的那个session)
  req.session = SESSION_DATA[userId]
}
//......

4.2. 登录,向session中添加信息。

//controller中用于处理登录的方法
const { PostLogin } = require('../controller/userController')

const { SuccessMoudle, ErrorMoudle } = require('../moudle/resMoudle')
//设置过期时间
const setCookieExpries = () => {
  const d = new Date()
  d.setTime(d.getTime() + (24 * 60 * 60 * 1000))
  return d.toGMTString()
}

const handleUserRouter = (req, res) => {
  const method = req.method
  const url = req.url
  const path = url.split('?')[0]
  //使用GET模拟登录,不安全。
  if (method === 'GET' && path === '/api/user/login') {
    const { username, password } = req.query
    const result = PostLogin(username, password)

    return result.then(isSussful => {
      //登录成功
      if (isSussful) {
      //向req.session.username中添加信息,由于req.session 之前设置为指向SESSION_DATA中的对应session,
      //修改或向req.session对象中添加属性会对SESSION_DATA[sessionID]起作用。
      //所以修改req.session会引起全局的SESSION_DATA变化
        req.session.username = username
        return new SuccessMoudle()
      } else {
        return new ErrorMoudle('登录失败')
      }
    })

  }
//app.js
///...
 const userData = handleUserRouter(req, res)
    console.log(SESSION_DATA)
    if (userData) {
      if (needSetCookie) {
//在处理完每个请求后都应该向cookie中添加信息
        res.setHeader('Set-Cookie', `userid=${userId};path=/;httpOnly;expires=${setCookieExpries()}`)
       
      }
      userData.then(result => {
        res.end(
          JSON.stringify(result)
        )
      }
      )
      return
    }

4.3. 验证登录状态

  //验证登录状态
  if (method === 'GET' && req.path === '/api/user/login-test') {
    if (req.session.username) {//判断SESSION_DATA[sessionID]中是否存在username属性,之前登录成功时添加过属性
      return Promise.resolve(new SuccessMoudle(req.session))
    } else {
      return Promise.resolve(new ErrorMoudle("尚未登陆"))
    }
  }
}

module.exports = handleUserRouter
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,682评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,277评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,083评论 0 355
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,763评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,785评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,624评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,358评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,261评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,722评论 1 315
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,900评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,030评论 1 350
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,737评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,360评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,941评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,057评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,237评论 3 371
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,976评论 2 355

推荐阅读更多精彩内容