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的做登录流程非常简单:
-
登录时,在后台使用JWT.sign函数,对想要存储的对象签名,生成一个token
-
客户端收到token,妥善存储
-
任何访问时,客户端通过http header,加入{"Authorization":"Bearer eyJhbGciOiJIUzI1NiI.."},其中base64过的内容就是之前生成的token
-
服务端得到token,调用jwt.verify函数,验证通过的话,获得到token内的对象
-
检查对象内容,如果是正确的,就说明已经认证,可以放行,否则提示登录
-
登出时,只要删除你存储的token即可