如何在Django中实现用户与服务提供者的独立登录系统
实现普通用户与服务提供者的双模型登录权限控制
背景
我已经有一套完整的普通用户认证系统,包含User模型、JWT登录接口,前端用react-auth-kit做登录校验。现在新增了独立的Service Provider(服务提供者)模型,需要让服务提供者能登录并访问普通用户无法进入的专属组件(比如Dashboard.jsx),该怎么实现双模型的登录权限控制?
后端改造(Django部分)
1. 完善Provider模型,支持密码哈希与校验
当前Provider模型未继承Django用户基类,无法自动处理密码安全逻辑,修改如下:
from django.contrib.auth.models import AbstractBaseUser, BaseUserManager, PermissionsMixin from django.utils import timezone from django.db import models class ProviderManager(BaseUserManager): def create_user(self, name, email, password=None, **extra_fields): if not email: raise ValueError('必须提供邮箱') email = self.normalize_email(email) user = self.model(name=name, email=email, **extra_fields) user.set_password(password) # 自动哈希密码 user.save(using=self._db) return user class Provider(AbstractBaseUser, PermissionsMixin): name = models.CharField(null=False, max_length=20, unique=True) email = models.CharField(max_length=255, unique=True) date_joined = models.DateTimeField(default=timezone.now) USERNAME_FIELD = 'email' # 用邮箱作为登录标识 REQUIRED_FIELDS = ['name'] objects = ProviderManager() def __str__(self): return self.name
注:原User模型中的
password字段可以删除,因为AbstractUser已经内置了密码相关的安全处理逻辑。
2. 改造登录视图,支持双模型登录并返回用户类型
修改原LoginView,同时兼容普通用户和服务提供者的登录请求,在JWT中加入用户类型标识:
from rest_framework.decorators import api_view from rest_framework.response import Response from rest_framework.exceptions import AuthenticationFailed import jwt import datetime from .models import User, Provider @api_view(['POST']) def LoginView(request): email = request.data.get('email') password = request.data.get('password') # 优先匹配普通用户 user = User.objects.filter(email=email).first() if user and user.check_password(password): user_type = 'user' user_id = user.id else: # 匹配服务提供者 provider = Provider.objects.filter(email=email).first() if provider and provider.check_password(password): user_type = 'provider' user_id = provider.id else: raise AuthenticationFailed('用户不存在或密码错误') # 生成带用户类型的JWT payload = { 'id': user_id, 'user_type': user_type, 'exp': datetime.datetime.utcnow() + datetime.timedelta(minutes=60), 'iat': datetime.datetime.utcnow() } token = jwt.encode(payload, 'secret', algorithm='HS256') response = Response() # 将JWT存入HttpOnly Cookie,提升安全性 response.set_cookie(key='jwt', value=token, httponly=True) response.data = { 'jwt': token, 'user_type': user_type } return response
3. 编写权限校验类,保护服务提供者专属接口
创建专属权限类,用于限制只有服务提供者能访问的API:
from rest_framework.permissions import BasePermission import jwt from .models import User, Provider class IsProvider(BasePermission): def has_permission(self, request, view): token = request.COOKIES.get('jwt') if not token: return False try: payload = jwt.decode(token, 'secret', algorithms=['HS256']) return payload.get('user_type') == 'provider' except jwt.ExpiredSignatureError: return False except jwt.InvalidTokenError: return False # 使用示例:服务提供者专属接口 @api_view(['GET']) @permission_classes([IsProvider]) def provider_dashboard_api(request): return Response({'message': '欢迎访问服务提供者后台'})
前端改造(React部分)
1. 登录组件保存用户类型到auth状态
修改Login.jsx的登录成功逻辑,将用户类型存入react-auth-kit的认证状态:
import { useSignIn } from 'react-auth-kit'; import { useNavigate } from 'react-router-dom'; import axios from 'axios'; const Login = () => { const signIn = useSignIn(); const navigate = useNavigate(); // ...表单逻辑省略 const handleLogin = () => { axios.post("/api/login", { email: values.email, password: values.password, }, { withCredentials: true, headers: { "Content-Type": "application/json" }, }) .then((res) => { // 保存认证状态与用户类型 signIn({ token: res.data.jwt, expiresIn: 3600, // 与后端JWT过期时间一致(60分钟) tokenType: "Bearer", authState: { userType: res.data.user_type } }); // 根据用户类型跳转对应页面 res.data.user_type === 'provider' ? navigate('/dashboard') : navigate('/profile'); }) .catch((err) => { console.error(err.response.data); }); } return (/* 表单渲染 */); }
2. 编写区分用户类型的私有路由组件
分别创建普通用户和服务提供者的专属路由组件:
import { useIsAuthenticated, useAuthUser } from 'react-auth-kit'; import { Navigate } from 'react-router-dom'; // 普通用户专用路由 const UserPrivateRoute = ({ Component }) => { const isAuthenticated = useIsAuthenticated(); const authUser = useAuthUser(); if (!isAuthenticated()) return <Navigate to="/login" />; if (authUser().userType !== 'user') return <Navigate to="/" />; return <Component />; }; // 服务提供者专用路由 const ProviderPrivateRoute = ({ Component }) => { const isAuthenticated = useIsAuthenticated(); const authUser = useAuthUser(); if (!isAuthenticated()) return <Navigate to="/login" />; if (authUser().userType !== 'provider') return <Navigate to="/" />; return <Component />; };
3. 更新路由配置
修改Router中的路由规则,替换原有私有路由并添加服务提供者专属路由:
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom'; import NavBar from './NavBar'; import Footer from './Footer'; import Home from './Home'; import Login from './Login'; import Signup from './Signup'; import UserProfile from './UserProfile'; import Dashboard from './Dashboard'; import { UserPrivateRoute, ProviderPrivateRoute } from './PrivateRoutes'; function App() { return ( <Router> <NavBar /> <Routes> <Route path="/" element={<Home />} /> <Route path="/login" element={<Login />} /> <Route path="/signup" element={<Signup />} /> {/* 普通用户专属页面 */} <Route path="/profile" element={<UserPrivateRoute Component={UserProfile} />} /> {/* 服务提供者专属页面 */} <Route path="/dashboard" element={<ProviderPrivateRoute Component={Dashboard} />} /> </Routes> <Footer /> </Router> ); }
内容的提问来源于stack exchange,提问作者TF Ryzen
相关产品推荐
相关产品推荐

