一种开放标准(RFC 7519),用于在网络应用中安全地传输信息。
原理
-
用户登录,服务器验证身份
-
验证通过后,根据用户的某些信息(如ID)生成一个token,其中包含签发日期、过期日期、签名算法、利用加密算法生成的密钥等,返回给客户端
-
客户端存储token。每次访问时,带着token访问服务端。若未过期则通过,若过期,请求刷新或重新登录验证
-
token仅保证网络传输过程中不被解密和篡改
-
一般有一个access token和一个refresh token,access有效期短(通常几小时),refresh较长(通常一周,防止用户频繁登录)。当access过期后进行验证,会先使用refresh请求新的access token
前端
以Vue为例。验证分前后双端,前端在router中验证(是否允许用户进入某个页面),用axios交给后端验证(是否允许执行后端某个业务)。安全起见,两个都设置一下比较好。如果某个操作前后端都存在,以后端为准。
安装
jsonwebtoken是用来前端拆出token中信息的
npm install axios jsonwebtokenrouter验证
import { createRouter, createWebHistory } from 'vue-router';
import store from '../store'; // 假设你使用了Vuex。根据你存token的地方来变化,也可能在localStorage里
const routes = [
{
path: '/login',
name: 'Login',
component: () => import('../views/Login.vue'),
},
{
path: '/profile',
name: 'Profile',
component: () => import('../views/Profile.vue'),
meta: { needLogin: true }, // 需要登录才能访问
},
// 其他路由...
];
const router = createRouter({
history: createWebHistory(),
routes,
});
router.beforeEach((to, from, next) => {
const isLogin = !!store.state.token; // 检查是否有token
const needLogin = to.matched.some(record => record.meta.needLogin);
if (needLogin) {
if (isLogin) {
next(); // 已登录,允许访问
} else {
next('/login'); // 未登录,跳转到登录页面
}
} else {
next(); // 不需要登录,直接访问
}
});
export default router;axios拦截器
// src/axios.js
import axios from 'axios';
import store from './store'; // 假设你使用了Vuex
const instance = axios.create({
baseURL: 'https://api.yourservice.com',
});
// 请求拦截器
instance.interceptors.request.use(config => {
const token = store.state.token; // 从Vuex中获取token
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
}, error => {
return Promise.reject(error);
});
// 响应拦截器
instance.interceptors.response.use(response => {
return response;
}, error => {
if (error.response.status === 401) {
store.dispatch('removeToken'); // 清除token
router.push('/login'); // 跳转到登录页面
}
return Promise.reject(error);
});
export default instance;登录组件样例
<template>
<div>
<form @submit.prevent="login">
<input v-model="username" type="text" placeholder="Username" />
<input v-model="password" type="password" placeholder="Password" />
<button type="submit">Login</button>
</form>
</div>
</template>
<script setup>
import { ref } from 'vue';
import axios from '../axios';
import { useRouter } from 'vue-router';
const username = ref('');
const password = ref('');
const router = useRouter();
const login = async () => {
try {
const response = await axios.post('auth/login/', {
username: username.value,
password: password.value,
});
const token = response.data.token;
localStorage.setItem('token', token); // 将 token 存储到 localStorage
router.push('/profile'); // 跳转到受保护页面
} catch (error) {
alert('Invalid credentials');
console.error(error);
}
};
</script>后端
Django为例,使用rest_framework
pip install django djangorestframework djangorestframework-simplejwtsettings.py配置:
# settings.py
INSTALLED_APPS = [
...
'rest_framework',
'rest_framework_simplejwt.token_blacklist',
'your_app_name', # 替换为你的应用名
]
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework_simplejwt.authentication.JWTAuthentication',
),
}登录view:
# views.py
from rest_framework_simplejwt.tokens import RefreshToken
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from django.contrib.auth import authenticate
class LoginView(APIView):
def post(self, request, *args, **kwargs):
username = request.data.get('username')
password = request.data.get('password')
user = authenticate(username=username, password=password)
if user is not None:
refresh = RefreshToken.for_user(user)
return Response({
'token': str(refresh.access_token),
'refresh': str(refresh),
})
else:
return Response({'error': 'Invalid credentials'}, status=status.HTTP_401_UNAUTHORIZED)url.py:
# urls.py
from django.urls import path
from .views import LoginView
urlpatterns = [
path('auth/login/', LoginView.as_view(), name='login'),
]