使用JWT做登录验证

536 阅读3分钟

JWT是一个比较新颖的跨域认证方法,比起Cookie来说可以跨域,比起session来说,是一个无状态的方案因此更适合需要可伸缩的环境。

使用JWT比较简单,对服务端来说,第一步就是认证。在客户端需要登录的时候,验证登录成功就发一个token给客户端;第二步是当客户端通过HTTP Header携带token来访问时,使用JWT验证token的有效性,如果有效,就通行否则阻塞访问。代码看起来是这样的:

var jwt = require('jsonwebtoken')
var SECRET_KEY ='sharedkey'
var express = require('express');
var app = express();  
var port = 4001
app.get('/visit',(req,res)=>{  
  var token = req.headers.authorization || ''    
  try {
      var splitToken = token.split(' ')[1]
      var decoded = jwt.verify(splitToken, SECRET_KEY)  
  } catch (e) {}
  if(decoded && decoded.login =='reco')
    res.send('pass')
  else
    res.send('block')
})
app.get('/normal',(req,res)=>{      
      res.send('pass')
  })

app.get('/login/:username/:password',(req,res)=>{
  if(req.params.username == 'reco' && req.params.password == 'rita')
    var token = jwt.sign({ login:req.params.username},SECRET_KEY)
    res.send(token)    
})
app.listen({ port }, async() =>{    
  await testflow()  
  process.exit();
});
async function testflow(){
    var prefix = `http://localhost:${port}/`
    var fetch = require('node-fetch')
    var result = await fetch(prefix+'normal')
    var assert = require('assert')
    assert.equal(await result.text(),'pass')
    var result = await fetch(prefix+'visit')
    assert.equal(await result.text(),'block')
    var result = await fetch(prefix+'login/reco/rita')
    var bearer = await result.text()    
    var result = await fetch(prefix+'visit',{headers: {'Authorization': 'Bearer '+ bearer}})
    assert.equal(await result.text(),'pass')
}

就是说,jwt提供了两个函数,分别用于生产token和验证token。代码使用了express作为web访问框架,提供了两个端点,分别是visit端点用来访问验证,还有login端点,用来登录。为了展示基本概念,代码简单至极,甚至就是简陋,但是胜在表意即可。

只要拿到token,任何客户端都可以访问此服务端,不像是cookie那样,需要受domain的限制。客户端只要在http header内携带token,类似这样:

Authorization:Bearer TOKEN

jwt token的样子像是这样:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJsb2dpbiI6InJlY28iLCJpYXQiOjE1OTIxODY1OTZ9.c2cCAYQGz2WIVZNRoqejkfX_hPAgyn77_Nyr1ygMxJk

这个token,用"."作为分隔符,分为3个部分,其中第二段就是我们的被签名的数据。默认情况下,token并不加密。因此,可以使用(base64工具)[www.base64decode.org/],解析处其中的内容:

{"login":"reco","iat":1592186596}

第一段数据是 header,第三段为Signature。具体格式,请看(官方文档)[jwt.io/introductio…

其中的iat,表示的iat (Issued At):签发时间。使用的格式是Unix timestamp,可以使用(Unix timestampe)[www.unixtimestamp.com/index.php]转… JWT 签发了,在到期之前就会始终有效,除非服务器部署额外的逻辑。可以利用iat部署此逻辑。

默认情况下,利用base64工具可以解析token,因此JWT 不应该使用 HTTP 协议明码传输,要使用 HTTPS 协议传输。

JWT也是可以加密的。生成原始 Token 以后,可以用密钥再加密一次。验证的时候在解密一次。 一个简单的客户端Vanilla代码验收了登录退出和携带JWT访问受限资源的方法:

<script>
    var bearer = ''
    async function f1(){
        var result = await fetch('/normal')
        document.getElementById('result').textContent = await result.text()
    }
    async function f2(){
        var result = await fetch('/visit',{headers: {'Authorization': 'Bearer '+ bearer}})        
        document.getElementById('result').textContent = await result.text()
    }
    async function f3(){        
        var result = await fetch('/login/reco/rita')
        document.getElementById('result').textContent = await result.text()
        bearer = document.getElementById('result').textContent
    }
    async function f4(){
        bearer = ''
        document.getElementById('result').textContent = 'logout'
    }
</script>
<button onclick="f1()">normal</button>
<button onclick="f2()">visit</button>
<button onclick="f3()">login</button>
<button onclick="f4()">logout</button>
<span id="result">ready</span>

总结下,使用JWT的做登录流程非常简单:

  1. 登录时,在后台使用JWT.sign函数,对想要存储的对象签名,生成一个token

  2. 客户端收到token,妥善存储

  3. 任何访问时,客户端通过http header,加入{"Authorization":"Bearer eyJhbGciOiJIUzI1NiI.."},其中base64过的内容就是之前生成的token

  4. 服务端得到token,调用jwt.verify函数,验证通过的话,获得到token内的对象

  5. 检查对象内容,如果是正确的,就说明已经认证,可以放行,否则提示登录

  6. 登出时,只要删除你存储的token即可