node实现基于token的身份验证
最近研究了下基于token的身份验证,并将这种机制整合在个人项目中。现在很多网站的认证方式都从传统的seesion+cookie转向token校验。对比传统的校验方式,token确实有更好的扩展性与安全性。
传统的session+cookie身份验证
由于HTTP是无状态的,它并不记录用户的身份。用户将账号与密码发送给服务器后,后台通过校验,但是并没有记录状态,于是下一次用户的请求仍然需要校验身份。为了解决这一问题,需要在服务端生成一条包含用户身份的记录,也就是session,再将这条记录发送给用户并存储在用户本地,即cookie。接下来用户的请求都会带上这条cookie,若客户端的cookie与服务端的session能对应上,则说明用户身份验证通过。
token身份校验
流程大致如下:
- 第一次请求时,用户发送账号与密码
- 后台校验通过,则会生成一个有时效性的token,再将此token发送给用户
- 用户获得token后,将此token存储在本地,一般存储在localstorage或cookie
- 之后的每次请求都会将此token添加在请求头里,所有需要校验身份的接口都会被校验token,若token解析后的数据包含用户身份信息,则身份验证通过。
对比传统的校验方式,token校验有如下优势:
- 在基于token的认证,token通过请求头传输,而不是把认证信息存储在session或者cookie中。这意味着无状态。你可以从任意一种可以发送HTTP请求的终端向服务器发送请求。
- 可以避免CSRF攻击
- 当在应用中进行session的读,写或者删除操作时,会有一个文件操作发生在操作系统的temp文件夹下,至少在第一次时。假设有多台服务器并且session在第一台服务上创建。当你再次发送请求并且这个请求落在另一台服务器上,session信息并不存在并且会获得一个“未认证”的响应。我知道,你可以通过一个粘性session解决这个问题。然而,在基于token的认证中,这个问题很自然就被解决了。没有粘性session的问题,因为在每个发送到服务器的请求中这个请求的token都会被拦截。
下面介绍一下利用node+jwt(jwt教程)搭建简易的token身份校验
示例
当用户第一次登录时,提交账号与密码至服务器,服务器校验通过,则生成对应的token,代码如下:
constfs=require('fs'); constpath=require('path'); constjwt=require('jsonwebtoken'); //生成token的方法 functiongenerateToken(data){ letcreated=Math.floor(Date.now()/1000); letcert=fs.readFileSync(path.join(__dirname,'../config/pri.pem'));//私钥 lettoken=jwt.sign({ data, exp:created+3600*24 },cert,{algorithm:'RS256'}); returntoken; } //登录接口 router.post('/oa/login',async(ctx,next)=>{ letdata=ctx.request.body; let{name,password}=data; letsql='SELECTuidFROMt_userWHEREname=?andpassword=?andis_delete=0',value=[name,md5(password)]; awaitdb.query(sql,value).then(res=>{ if(res&&res.length>0){ letval=res[0]; letuid=val['uid']; lettoken=generateToken({uid}); ctx.body={ ...Tips[0],data:{token} } }else{ ctx.body=Tips[1006]; } }).catch(e=>{ ctx.body=Tips[1002]; }); });
用户通过校验将获取到的token存放在本地:
store.set('loginedtoken',token);//store为插件
之后客户端请求需要验证身份的接口,都会将token放在请求头里传递给服务端:
service.interceptors.request.use(config=>{ letparams=config.params||{}; letloginedtoken=store.get('loginedtoken'); lettime=Date.now(); let{headers}=config; headers={...headers,loginedtoken}; params={...params,_:time}; config={...config,params,headers}; returnconfig; },error=>{ Promise.reject(error); })
服务端对所有需要登录的接口均拦截token并校验合法性。
functionverifyToken(token){ letcert=fs.readFileSync(path.join(__dirname,'../config/pub.pem'));//公钥 try{ letresult=jwt.verify(token,cert,{algorithms:['RS256']})||{}; let{exp=0}=result,current=Math.floor(Date.now()/1000); if(current<=exp){ res=result.data||{}; } }catch(e){ } returnres; } app.use(async(ctx,next)=>{ let{url=''}=ctx; if(url.indexOf('/user/')>-1){//需要校验登录态 letheader=ctx.request.header; let{loginedtoken}=header; if(loginedtoken){ letresult=verifyToken(loginedtoken); let{uid}=result; if(uid){ ctx.state={uid}; awaitnext(); }else{ returnctx.body=Tips[1005]; } }else{ returnctx.body=Tips[1005]; } }else{ awaitnext(); } });
本示例使用的公钥与私钥可自己生成,操作如下:
- 打开命令行工具,输入openssl,打开openssl;
- 生成私钥:genrsa-outrsa_private_key.pem2048
- 生成公钥:rsa-inrsa_private_key.pem-pubout-outrsa_public_key.pem
点此查看node后台代码
点此查看前端代码
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。