使用 token 验证用户登录
简介
日常开发中经常需要对用户的登录状态进行记录和验证,让用户登录后能保持一段时间的登录状态,让用户能访问自己的用户信息等。
这篇文章主要讲解使用 token 的方式如何做到用户登录验证,文章案例的后端使用了nodejs的express,前端使用了fetch请求。
服务端
依赖
npm i express mongoose bcrypt jsonwebtoken
用户模型
定义一个mongoose的用户模型user model,在MongoDB中存用户数据。
const mongoose = require('mongoose')
const bcrypt = require('bcrypt')
mongoose.connect('mongodb://localhost/manage', {
useNewUrlParser: true,
useUnifiedTopology: true,
useCreateIndex: true
})
const requiredString = {
type: String,
required: true
}
const userSchema = mongoose.Schema({
username: {
unique: true,
...requiredString
},
password: {
set(value) {
return bcrypt.hashSync(value, 10)
},
...requiredString
}
})
module.exports = mongoose.model('User', userSchema)
Express 结构
const express = require('express')
const bcrypt = require('bcrypt')
const jwt = require('jsonwebtoken')
const User = require('./models/user')
const app = express()
const port = 8000
const secret = 'dflglrsgiv879grejh'
app.use(express.json())
app.listen(port, () => {
console.log(`http://localhost:${port}`)
})
CORS 跨域
为了方便我们就做一个简单的方法解决跨域问题,实际开发中会用到cors这个express的中间件。
对所有OPTINOS请求进行处理,OPTIONS是预检请求
app.options('*', (req, res, next) => {
res.setHeader('Access-Control-Allow-Origin', '*')
res.setHeader('Access-Control-Allow-Headers', 'Content-Type,Authorization')
next()
})
const cors = (req, res, next) => {
res.setHeader('Access-Control-Allow-Origin', '*')
next()
}
用户注册
/register路由往数据库中存放用户信息。前端发送POST请求,传入用户名和密码。
app.post('/register', cors, async (req, res) => {
const { username, password } = req.body
try {
const user = await User.create({ username, password })
res.json({
ok: true,
data: user
})
} catch (err) {
res.json({
ok: false,
message: '该用户已存在'
})
}
})
用户登录
/login路由进行用户登录。前端发送POST请求,传入用户名和密码。
用户登录需要做的操作:
- 判断这个用户是否存在
- 判断用户输入的密码是否正确
- 颁发 token,这里把
token返回给前端自己进行处理
app.post('/login', cors, async (req, res) => {
const { username, password } = req.body
const user = await User.findOne({ username })
if (!user) {
return res.json({
ok: false,
message: '用户名不存在'
})
}
const isPasswordValid = bcrypt.compareSync(
password,
user.password
)
if (!isPasswordValid) {
return res.json({
ok: false,
message: '密码错误'
})
}
const token = jwt.sign(
{ id: user._id },
secret
)
res.json({
ok: true,
data: { user, token }
})
})
验证用户登录
比如:/profile这个路由需要用户验证才能获得数据。前端发送GET请求,添加Authorization请求头放入前面用户登录时返回的token。
auth中间件
将用户验证抽离成一个express中间件,这样在之后需要对用户登录状态进行验证的路由添加auth这个中间件就行了。
const auth = async (req, res, next) => {
const { authorization } = req.headers
if (authorization) {
const token = authorization.split(' ').pop()
const { id } = jwt.verify(token, secret)
try {
req.user = await User.findById(id)
next()
} catch (error) {
res.json({
ok: false,
message: 'token无效'
})
}
} else {
res.json({
ok: false,
message: '请设置token'
})
}
}
/profile路由
在处理前传了两个中间件,cors用于跨域处理,auth做了用户验证。
app.get('/profile', cors, auth, async (req, res) => {
res.json({
ok: true,
data: req.user
})
})
客户端
这里使用fetch方法来发送请求,同样的你可以使用XMLHttpRequest、Axios等都可以。
后端 API
const serverURL = 'http://localhost:8000'
const registerURL = `${serverURL}/register`
const loginURL = `${serverURL}/register`
const profileURL = `${serverURL}/register`
注册用户
向registerURL后端的登录API发送POST请求,传了json格式用户名和密码。
fetch(registerURL, {
method: 'POST',
body: JSON.stringify({
username: 'user',
password: '1234'
}),
headers: new Headers({
'Content-Type': 'application/json'
})
})
.then(res => res.json())
.then(json => console.log(json))
登录用户
向loginURL后端的登录API发送POST请求,传了json格式用户名和密码。
fetch(loginURL, {
method: 'POST',
body: JSON.stringify({
username: 'user',
password: '1234'
}),
headers: new Headers({
'Content-Type': 'application/json'
})
})
.then(res => res.json())
.then(json => console.log(json))
用户信息
后端的对/profile做了用户登录验证,我们就需要按照要求在请求头中添加Authorization字段来传token给后端去做验证。
Authorization请求头的type类型Bearer在OAuth 2.0中进行了相应的说明。
const token = ''
fetch(profileURL, {
method: 'GET',
headers: new Headers({
'Authorization': `Bearer ${token}`
})
})
.then(res => res.json())
.then(json => console.log(json))
补充:服务端直接设置 Cookie
通常情况后端都不会把token交给前端来处理,让前端自己去存放和删除。下面就使用简单的例子说明通过后端来设置cookie并且从cookie中做用户登录验证。
服务端
CORS 跨域
在跨域的情况下,前端的请求不会自动带上cookie传递给后端,那么前端需要设置Credentials字段做相应的处理。
这时后端同样需要添加额外的相应头Access-Control-Allow-Credentials为true,而且Access-Control-Allow-Origin不允许再设为*所有域,这里假设前端运行在3000端口上。
app.options('*', (req, res, next) => {
res.setHeader('Access-Control-Allow-Origin', 'http://localhost:3000')
res.setHeader('Access-Control-Allow-Credentials', true)
res.setHeader('Access-Control-Allow-Headers', 'Content-Type,Authorization')
next()
})
const cors = (req, res, next) => {
res.setHeader('Access-Control-Allow-Credentials', true)
res.setHeader('Access-Control-Allow-Origin', 'http://localhost:3000')
next()
}
用户登录
这时用户登录返回到前端的结果中就不要有token字段了,我们直接把token通过响应头Set-Cookie设置到cookie中。
在express中提供了Respone.cookie()这个方法去设置cookie,这样就变得十分方便。
你仍然可以使用res.setHeader('Set-Cookie', 'user=dssfrgsrgh; path=/; HttpOnly')这样的方式在express中设置响应头去设置Cookie。
app.post('/login', cors, async (req, res) => {
const token = jwt.sign(
{ id: user._id },
secret
)
res.cookie('user', token, {
httpOnly: true
})
res.json({
ok: true,
data: user
})
})
用户信息
我们就不再需要从Authorization请求头中拿token了,现在就要从cookie中获得token。
const auth = async (req, res, next) => {
const { cookie } = req.headers
if (cookie) {
let matchReg = /user=(.*?);/
if (cookie.split(';').length === 1) {
matchReg = /user=(.*)/
}
const matchResult = cookie.match(matchReg)
if (matchResult) {
const token = matchResult[1]
const { id } = jwt.verify(token, secret)
try {
req.user = await User.findById(id)
next()
} catch (err) {
res.json({
ok: false,
message: 'token无效'
})
}
} else {
res.json({
ok: false,
message: '请先登录'
})
}
} else {
res.json({
ok: false,
message: '没有Cookie'
})
}
}
app.get('/profile', cors, auth, async (req, res) => {
res.json({
ok: true,
data: req.user
})
})
客户端
用户登录
现在前端的登录请求不会拿到token,也就不再需要对token进行存放、删除等操作了。
发送POST请求,跨域携带cookie。
fetch方法在跨源请求中携带cookie需要设置credentials字段。
另外,对于axios就是withCredentials字段。
fetch(loginURL, {
method: 'POST',
body: JSON.stringify({
username: 'user',
password: '1234'
}),
credentials: 'include'
})
需要授权的请求
发送GET请求,跨域携带cookie。
fetch(profileURL, { credentials: 'include' })
版权声明:
Cody's Blog文章皆为站长Cody原创内容,转载请注明出处。
包括商业转载在内,注明下方要求的文章出处信息即可,无需联系站长授权。
请尊重他人劳动成果,用爱发电十分不易,谢谢!
请注明出处:
本文出自:Cody's Blog
本文永久链接:https://okcody.com/posts/backend/2