200 行原生代码实现一个 JWT 登陆认证实例

前后端分离后,通过 HTTP header 来传递 token 就变得非常流行了,区别于 cookie-session 认证机制,jwt(json web token)是无状态的认证方式。

这里不借助任何框架,写一个 jwt 登陆认证实例。

功能介绍

本案例是基于实际项目简化而成的,从功能出发实现的一个实例

  • 假设「测试数据」是网站受保护的内容,这里用户未登录,访问失败

  • 当用户登陆成功后,生成 jwtToken,但是该用户不是网站管理员用户,而此功能不对普通用户开放,所以这里提示没有权限

  • 当真正的管理员用户登陆后,就能正确地请求数据了

前端部分

  • index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
<!DOCTYPE html>
<html lang="en">
<head>
<title>JWT 认证登陆</title>
</head>
<body>
<form id="form">
<label for="username"></label>
<input type="text" name="username" />
<label for="password"></label>
<input type="password" name="password" />
<button type="submit">登陆</button>
</form>
<br />
<button id="testData">测试数据</button>
<ul id="ul"></ul>

<script>
function handleSubmit() {
const form = document.getElementById('form')
form.addEventListener('submit', e => {
e.preventDefault()
postFormData(getInputData())
})
}

function getInputData() {
const data = document.getElementsByTagName('input')
let inputValue = {}
for (let item of data) {
inputValue[item.name] = item.value
}
return inputValue
}

function postFormData(formValue) {
fetch('http://localhost:3006/auth', {
method: 'POST',
body: JSON.stringify(formValue)
})
.then(res => {
res.json().then(data => {
const jwtToken = data.jwtToken
localStorage.setItem('jwtToken', `Bearer ${jwtToken}`)
alert('登陆成功')
})
})
.catch(err => {
console.log(err)
})
}

function testData() {
const testData = document.getElementById('testData')
testData.addEventListener('click', e => {
e.preventDefault()

getTodos()
})
}

function getTodos() {
const Token = localStorage.getItem('jwtToken')
fetch('http://localhost:3006/todos', {
method: 'GET',
headers: {
'Content-Type': 'application/json',
Authorization: Token
}
})
.then(res => {
res.json().then(data => {
const ul = document.getElementById('ul')
if (data[1]) {
data.map(item => {
return ul.insertAdjacentHTML(
'beforeend',
`<li>${item.todo}</li>`
)
})
}
})
})
.catch(err => {
console.log(err)
})
}

handleSubmit()
testData()
</script>
</body>
</html>

后端部分

  • 这里使用 node.js,新建 server.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
const http = require('http')
const url = require('url')
const jwt = require('jsonwebtoken')
const jwtSecret = 'JOGSKIDFSXIFWER'

// create http server
http
.createServer((req, res) => {
const pathname = url.parse(req.url).pathname

if (pathname !== '/favicon.ico') {
router(pathname, req, res)
}
})
.listen(3006)

// handle request and response
function router(pathname, req, res) {
if (pathname === '/auth' && req.method === 'POST') {
handlePostMethod(req, res)
} else if (pathname === '/todos' && req.method === 'GET') {
handleGetmethod(req, res)
} else if (pathname === '/todos' && req.method === 'OPTIONS') {
res.writeHead(200, {
'Content-Type': 'application/json; charset=utf8',
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'Content-Type,Authorization',
'Access-Control-Allow-Methods': 'POST, GET, OPTIONS, DELETE'
})
res.end('ok')
} else {
res.writeHead(416, { 'Content-Type': 'text/plain; charset=utf8' })
res.end('拒绝处理该请求')
}
}

function handlePostMethod(req, res) {
let str = ''
req.on('data', chunk => {
str += chunk
})

req.on('end', () => {
const formData = JSON.parse(str)
const token = jwt.sign({ username: formData.username }, jwtSecret)

res.writeHead(201, {
'Content-Type': 'application/json; charset=utf8',
'Access-Control-Allow-Origin': '*'
})
res.end(JSON.stringify({ jwtToken: token }))
})
}

function handleGetmethod(req, res) {
let jwtToken

if (req.headers.authorization) {
jwtToken = req.headers.authorization.split(' ')[1]
}

if (jwtToken) {
jwt.verify(jwtToken, jwtSecret, (err, data) => {
if (err) {
console.log(err)
} else {
if (data.username === 'xnng') {
res.writeHead(201, {
'Content-Type': 'application/json; charset=utf8',
'Access-Control-Allow-Origin': '*'
})
res.end(
JSON.stringify([{ id: 1, todo: '吃饭' }, { id: 2, todo: '睡觉' }])
)
} else {
res.writeHead(403, {
'Content-Type': 'text/plain ; charset=utf8',
'Access-Control-Allow-Origin': '*'
})
res.end('您没有权限访问')
}
}
})
} else {
res.writeHead(403, {
'Content-Type': 'text/plain ; charset=utf8',
'Access-Control-Allow-Origin': '*'
})
res.end('没有找到 token')
}
}

运行

  • 运行之前后端要安装 jwt 库
1
$ npm install jsonwebtoken
  • 运行
1
$ node server.js

jwt 登陆认证实现的步骤

用户登录 ——> 密码验证成功 ——> 服务端根据事先定义好的 Secret 签发 jwtToken ——> 客户端保存 jwtToken 到本地存储 ——> 客户端下一次发送请求时将此 jwtToken 带到 header 中 ——> 服务端从 header 中获取 jwtToken 并解密 ——> 如果 jwtToken 正确并且用户符合要求则放行

由于 Secret 是存储在服务端的,所以 token 无法被伪造,只是怕被盗取。前端获取了 token 后也可以用 jwt-decode 库解密 token,token 中只需要存储能够鉴别用户的信息而非私密信息。

0%