此文记录jwt鉴权(react express jsonwebtoken)思路流程
整个项目可以在https://github.com/furfur-jiang/DevConnector处查看,欢迎start
jwt鉴权(react express jsonwebtoken)思路流程
后端四步
1. 配置一个随机内容,全局使用
config/default.json
{
"jwtSecret": "mysecrettoken",
}
2. 登录接口,返回token
routes/api/auth.js
const express = require('express')
const router = express.Router()
const auth = require('../../middleware/auth')
const jwt = require('jsonwebtoken')
const User = require('../../models/User')
const { body, validationResult } = require('express-validator')
const config = require('config')
const bcrypt = require('bcryptjs')
router.post(
'/',
[
//检验输入内容
body('email', '请输入正确邮箱').isEmail(),
body('password', '请输入密码').exists(),
],
async (req, res) => {
const errors = validationResult(req)
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() })
}
const { email, password } = req.body
try {
let user = await User.findOne({ email: email })
if (!user) {
return res.status(400).json({ error: [{ msg: 'Invalid Credentials 没有登录资格' }] })
}
const isMatch = await bcrypt.compare(password, user.password)
if (!isMatch) {
return res.status(400).json({ error: [{ msg: 'Invalid Credentials 没有登录资格' }] })
}
const payload = {
user: {
id: user.id,
},
}
jwt.sign(
payload,
config.get('jwtSecret'), //从config文件获取内容
{ expiresIn: 360000 },
(err, token) => {
if (err) throw err
res.json({ token })
},
)
//return jwt
} catch (err) {
console.error(err.message)
res.status(500).send('Server error')
}
},
)
// 加auth中间件,接受携带token请求,返回用户内容
router.get('/', auth, async (req, res) => {
try {
const user = await User.findById(req.user.id).select('-password')
res.json(user)
} catch (err) {
console.error(err.message)
res.status(500).send('Server Error')
}
})
module.exports = router
3. 后端添加鉴权中间件,解析后返回解码后的内容,便于后续中间件使用
middleware/auth.js
const jwt = require('jsonwebtoken')
const config = require('config')
module.exports = function (req, res, next) {
//从头部获取token
const token = req.header('x-auth-token')
//判断是否有token
if (!token) {
return res.status(401).json({ msg: '没有访问权限' })
}
try {
//解析token
const decoded = jwt.verify(token, config.get('jwtSecret'))
req.user = decoded.user //赋予解码后的user属性,便于后续使用
next()
} catch (err) {
res.status(401).json({ msg: '令牌无效' })
}
}
4. 需要权限的接口,加上auth中间件即可
/**
* @route GET api/profile/me
* @desc Get current users profile
* @access Private
*/
router.get('/me', auth, async (req, res) => {
try {
const profile = await Profile.findOne({
user: req.user.id,
}).populate('user', ['name', 'avatar'])
if (!profile) {
return res.status(400).json({ msg: '此用户没有配置文件' })
}
res.json(profile)
} catch (err) {
console.error(err.message)
res.status(500).send('Server Error')
}
})
前端五步
1. 前端登录后,存储token、用户信息、登录态 到state和localstorge
src/actions/auth.js
// load user
export const loadUser = () => async (dispatch) => {
if (localStorage.token) {
setAuthToken(localStorage.token)//加x-auth-token头部,函数定义在下面
}
try {
const res = await api.get('/auth')
dispatch({
type: USER_LOADED,
payload: res.data,
})
} catch (error) {
dispatch({
type: AUTH_ERROR,
})
}
}
//Login user
export const login = (email, password) => async (dispatch) => {
const body = JSON.stringify({ email, password })
try {
const res = await api.post('/auth', body)
dispatch({
type: LOGIN_SUCCESS,
payload: res.data,
})
dispatch(loadUser())
} catch (err) {
const errors = err.response.data.errors
if (errors) {
errors.forEach((error) => dispatch(setAlert(error.msg, 'danger')))
}
dispatch({
type: LOGIN_FAIL,
})
}
}
2. 将token设置到请求头
src/utils/setAuthToken.js
import api from './api';
const setAuthToken = token => {
if (token) {
api.defaults.headers.common['x-auth-token'] = token
localStorage.setItem('token', token);
}else{
delete api.defaults.headers.common['x-auth-token']
localStorage.removeItem('token');
}
}
export default setAuthToken
3. 定义对应reducer
src/reducers/auth.js
function auth(state = initialState, action) {
const { type, payload } = action
switch (type) {
case USER_LOADED:
return {
...state,
isAuthenticated: true,
loading: false,
user:payload
}
case REGISTER_SUCCESS:
case LOGIN_SUCCESS:
localStorage.setItem('token', payload.token)
return {
...state,
...payload,
isAuthenticated: true,
loading: false,
}
case AUTH_ERROR:
case REGISTER_FAIL:
case LOGIN_FAIL:
case LOGOUT:
case ACCOUNT_DELETE:
localStorage.removeItem('token')
return { ...state, token:null, isAuthenticated: false, loading: false }
default:
return state
}
}
export default auth
4. 封装私有路由
src/components/routing/PrivateRoute.js
import PropTypes from 'prop-types';
import React from 'react';
import { connect } from 'react-redux';
import { Navigate } from 'react-router-dom';
import Spinner from '../layout/Spinner';
const PrivateRoute = ({
component: Component,
auth: { isAuthenticated, loading }
}) => {
if (loading) return <Spinner />;
if (isAuthenticated) return <Component />;
return <Navigate to="/login" />;
};
PrivateRoute.propTypes = {
auth: PropTypes.object.isRequired,
}
const mapStateToProps = (state) => ({
auth: state.auth,
})
export default connect(mapStateToProps)(PrivateRoute)
5. 使用私有路径,私有路由就会判断是否已授权
<Provider store={store}>
<Router>
<Routes>
<Route
path="/posts/:id"
element={<PrivateRoute component={Post} />}
</Routes>
</Router>
</Provider>
附依赖版本
后端
“dependencies”: {
“bcryptjs”: “^2.4.3”,
“config”: “^3.3.7”,
“express”: “^4.17.2”,
“express-validator”: “^6.14.0”,
“gravatar”: “^1.8.2”,
“jsonwebtoken”: “^8.5.1”,
“mongoose”: “^6.1.8”,
“request”: “^2.88.2”
}
前端
“dependencies”: {
“@testing-library/jest-dom”: “^5.16.2”,
“@testing-library/react”: “^12.1.2”,
“@testing-library/user-event”: “^13.5.0”,
“axios”: “^0.25.0”,
“moments”: “0.0.2”,
“react”: “^17.0.2”,
“react-dom”: “^17.0.2”,
“react-moment”: “^1.1.1”,
“react-redux”: “^7.2.6”,
“react-router-dom”: “^6.2.1”,
“react-scripts”: “5.0.0”,
“redux”: “^4.1.2”,
“redux-devtools-extension”: “^2.13.9”,
“redux-thunk”: “^2.4.1”,
“uuid”: “^8.3.2”,
“web-vitals”: “^2.1.4”
}