You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 07:33:20