一种开放标准(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 jsonwebtoken

router验证

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-simplejwt

settings.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'),
]