Flutter与Django基于JWT令牌的登录存token无法保持认证问题
问题描述
我希望在Flutter端完成登录操作,但目前在Django服务器和Flutter客户端均无法保持认证状态。
我需要通过存储JWT令牌让用户在Flutter端保持认证状态,同时也希望Django服务器端也能维持用户的认证状态。
Django端代码(Views.py)
class LoginAPIView(APIView): def post(self, request): username = request.data.get('username') phonenumber = request.data.get('phonenumber') password = request.data.get('password') try: user = UserProfile.objects.get( username=username, phone_number=phonenumber) except UserProfile.DoesNotExist: # Handle the case where the user with the given username doesn't exist return Response({"message": "A user or phone number does not found."}, status=status.HTTP_400_BAD_REQUEST) print(user) if user.password == password: if user is not None: # if user.check_password(password): # Password is correct, log in the user print(user) login(request, user) # Generate JWT token payload = { 'user_id': user.id, 'exp': datetime.datetime.utcnow() + datetime.timedelta(minutes=60), 'iat': datetime.datetime.utcnow() } token = jwt.encode(payload, 'secret', algorithm='HS256') uname = username # Set the token as a cookie # print(token)"jwt": token, return Response({"message": "Successfully Login", "jwt": token, "uname": uname}, status=status.HTTP_200_OK) # response = Response( # {"message": "Successfully Login", "uname": uname}, status=status.HTTP_200_OK) # response.set_cookie(key='jwt', value=token, httponly=True) return response else: # Password is incorrect raise AuthenticationFailed( {"message": "Password Incorrect"}, code=status.HTTP_401_UNAUTHORIZED)
Flutter端代码
onPressed: () async { var username = usernameController.text; final phonenumber = phoneNumberController.text; final password = passwordController.text; const apiUrl = 'http://127.0.0.1:8000/Login/'; AuthService authService = AuthService(); var token; final response = await http.post( Uri.parse(apiUrl), headers: {'Content-Type': 'application/json'}, body: jsonEncode({ 'Authorization': 'Bearer $token', 'username': username, 'phonenumber': phonenumber, 'password': password, }), ); if (response.statusCode == 200) { // Handle successful response final Map<String, dynamic> responseData = json.decode(response.body); final String token = responseData['jwt']; print(token); setState(() { message = responseData['message']; uname = responseData['uname']; }); const storage = FlutterSecureStorage(); // await storage.write(key: 'jwt', value: token);
问题修复方案
Django端问题及修复
- 密码验证逻辑错误:直接对比
user.password == password不成立,Django默认对密码做哈希存储,必须用user.check_password(password)验证密码。 - 代码缩进错误:生成JWT的代码块缩进层级错误,会导致非登录成功场景也执行JWT生成逻辑,需将其移入密码验证通过的分支内。
- 服务器端认证状态维持:
login(request, user)已经会创建session维持后端状态,若需同时支持JWT验证,可将JWT存入HttpOnly Cookie,提升安全性;另外JWT密钥不要硬编码,建议从环境变量读取。
修复后的Views.py代码:
import os from datetime import datetime, timedelta from django.contrib.auth import login from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import status from rest_framework.exceptions import AuthenticationFailed from .models import UserProfile import jwt class LoginAPIView(APIView): def post(self, request): username = request.data.get('username') phonenumber = request.data.get('phonenumber') password = request.data.get('password') try: user = UserProfile.objects.get( username=username, phone_number=phonenumber) except UserProfile.DoesNotExist: return Response({"message": "用户或手机号不存在"}, status=status.HTTP_400_BAD_REQUEST) # 正确验证哈希密码 if user.check_password(password): # 登录用户,维持后端session认证状态 login(request, user) # 生成JWT令牌 payload = { 'user_id': user.id, 'exp': datetime.utcnow() + timedelta(minutes=60), 'iat': datetime.utcnow() } # 从环境变量读取密钥,避免硬编码 jwt_secret = os.environ.get('JWT_SECRET_KEY', 'default_secret') token = jwt.encode(payload, jwt_secret, algorithm='HS256') # 返回响应并将JWT存入HttpOnly Cookie response = Response( {"message": "登录成功", "jwt": token, "uname": username}, status=status.HTTP_200_OK ) response.set_cookie( key='jwt', value=token, httponly=True, secure=True, samesite='Lax' ) return response else: raise AuthenticationFailed({"message": "密码错误"}, code=status.HTTP_401_UNAUTHORIZED)
Flutter端问题及修复
- 登录请求冗余字段:登录时还未获取到token,无需在请求body中携带
Authorization字段,该字段应在后续认证请求的headers中使用。 - 未实际存储JWT:需取消
await storage.write(key: 'jwt', value: token)的注释,将token存入安全存储。 - 后续请求需携带JWT:每次向后端发起认证请求时,从安全存储中取出token,放在请求headers的
Authorization: Bearer <token>中。
修复后的Flutter代码片段:
onPressed: () async { var username = usernameController.text; final phonenumber = phoneNumberController.text; final password = passwordController.text; const apiUrl = 'http://127.0.0.1:8000/Login/'; final response = await http.post( Uri.parse(apiUrl), headers: {'Content-Type': 'application/json'}, body: jsonEncode({ 'username': username, 'phonenumber': phonenumber, 'password': password, }), ); if (response.statusCode == 200) { final Map<String, dynamic> responseData = json.decode(response.body); final String token = responseData['jwt']; print(token); setState(() { message = responseData['message']; uname = responseData['uname']; }); // 将JWT存入安全存储 const storage = FlutterSecureStorage(); await storage.write(key: 'jwt', value: token); // 登录成功后跳转至认证页面 Navigator.pushReplacementNamed(context, '/home'); } else { // 处理登录失败逻辑 final errorData = json.decode(response.body); setState(() { message = errorData['message']; }); } }
后端JWT验证补充(可选)
若要让Django统一通过JWT验证请求,可使用djangorestframework-simplejwt扩展,配置后后端可同时支持session和JWT两种认证方式:
- 安装依赖:
pip install djangorestframework-simplejwt - 在
settings.py中配置:
INSTALLED_APPS = [ ... 'rest_framework_simplejwt', ] REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': ( 'rest_framework_simplejwt.authentication.JWTAuthentication', 'rest_framework.authentication.SessionAuthentication', ), }
内容的提问来源于stack exchange,提问作者Shahan
相关产品推荐
相关产品推荐

