news 2026/10/17 14:29:20

Django登录功能实战:从认证原理到安全加固的完整指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Django登录功能实战:从认证原理到安全加固的完整指南

1. 登录功能到底在解决什么问题

登录这件事,乍一看就是“输入账号密码,点按钮,进首页”,简单到很多人觉得没什么可讲的。但真到项目里落地,你会发现它牵扯的东西远比想象中多:用户表怎么设计、密码怎么存、会话怎么维持、状态怎么在多次请求之间保持、未登录用户怎么拦截、登录之后怎么记住身份、退出之后怎么让凭证失效。这一整套链路,才是登录功能真正的全貌。

我用 Django 做过不少带用户体系的项目,从内部管理后台到面向普通用户的内容站点都有。每次重新搭登录模块,我都会先把需求拆清楚,而不是上来就写视图。因为登录功能的实现方式,直接决定了后续权限控制、用户体验和安全性。比如你是要做纯服务端的表单登录,还是前后端分离下的接口登录,这两条路的技术选型完全不同,踩的坑也不一样。

这篇文章面向的是已经了解 Django 基础、能跑起一个项目、但对用户认证体系还没有系统认知的开发者。我会从整体设计思路讲起,把 Django 自带的认证机制拆开揉碎,再一步步带你实现一个完整可用的登录功能,包括表单登录、接口登录、状态保持、登录拦截、退出登录,以及那些文档里不会写、只有实际做过才会遇到的坑。看完之后,你应该能独立给自己的项目接上一套靠谱的登录体系,并且知道每个环节为什么这么设计。

2. 登录功能的整体设计与技术选型

2.1 为什么优先用 Django 自带认证体系

很多人一上来就想自己写用户表、自己写密码校验、自己写会话管理。我的建议是:除非你有非常特殊的业务需求,否则不要重复造轮子。Django 自带的django.contrib.auth这套认证体系,已经帮你解决了登录功能里最麻烦、最容易出错的部分。

它到底帮你做了什么?我列几个关键点你就明白了。第一,它提供了User模型,字段设计经过大量项目验证,用户名、密码、邮箱、是否激活、是否超级用户这些基础字段都有。第二,它内置了密码哈希机制,默认用 PBKDF2 算法加盐存储,你永远不需要自己处理明文密码。第三,它提供了authenticate和login两个核心函数,前者负责校验凭证,后者负责把用户身份写进会话。第四,它自带会话框架,登录状态通过 session 维持,浏览器只需要存一个 sessionid。

自己实现这套东西,最容易翻车的地方就是密码存储和会话管理。我见过有人把密码直接明文存数据库的,也见过用简单 MD5 不加盐的,这些在真实项目里都是灾难。Django 默认的密码哈希方案会随着版本升级不断更新,你几乎不用操心算法过时的问题。

提示:Django 的密码哈希是单向的,数据库里存的是哈希值,任何人都无法从哈希反推出原始密码。这也是为什么“找回密码”功能只能重置而不能找回。

2.2 表单登录与接口登录的选型对比

登录功能的实现形态,主要分两种:一种是传统的服务端渲染表单登录,用户提交表单,服务端处理完直接重定向到目标页面;另一种是前后端分离下的接口登录,前端通过 AJAX 或 fetch 提交 JSON 数据,服务端返回 token 或设置 cookie。

这两种方式没有绝对优劣,关键看你的项目架构。如果你的项目是 Django 模板渲染为主,那表单登录最省事,Django 的LoginView几乎开箱即用。如果你的前端是 Vue、React 这类独立应用,那就得走接口登录,这时候会话维持方式就要重新考虑。

我把两种方案的差异整理成一张表,方便你对照选择。

对比维度表单登录接口登录
适用架构服务端渲染前后端分离
状态维持Session + CookieSession 或 Token
前端工作量低中到高
跨域处理基本不涉及需要配置 CORS
移动端适配差好
安全性控制依赖框架默认需自行加固

选型的时候还有一个容易被忽略的点:如果你的项目既要服务网页端,又要给移动端提供接口,那最好从一开始就统一走接口登录,避免两套逻辑并存带来的维护成本。我吃过这个亏,早期图省事用了表单登录,后来加移动端时不得不把认证逻辑重写一遍。

2.3 会话保持的核心原理

登录功能能“记住”你,靠的是会话机制。理解这一点,很多问题就迎刃而解了。整个流程是这样的:用户提交用户名和密码,服务端用authenticate校验,校验通过后调用login函数,Django 会在服务端生成一条 session 记录,同时把 session 的唯一标识(sessionid)通过 Set-Cookie 写进浏览器。之后浏览器每次请求都会带上这个 cookie,Django 的会话中间件根据 sessionid 找到对应的 session 数据,从中取出用户 ID,再加载出用户对象。

这里有个关键点:session 数据存在服务端,浏览器只存一个 ID。这意味着即使有人拿到了 sessionid,只要服务端把这条 session 删掉,凭证就立即失效。这也是为什么“退出登录”本质上就是删除服务端的 session 记录。

会话的存储位置默认是数据库,Django 会在django_session表里存。你也可以换成缓存、文件或签名 cookie。生产环境里,如果并发量高,我一般会把 session 存到 Redis 里,读写速度快很多,也方便做分布式部署时的会话共享。

3. 核心细节解析与实操要点

3.1 用户模型的选择与扩展时机

Django 默认的User模型够用,但很多项目后期会想加字段,比如手机号、头像、昵称。这时候就面临一个经典问题:是直接用默认 User,还是自定义用户模型?

我的经验是:项目一开始就自定义用户模型,哪怕暂时不加任何字段。原因是 Django 官方明确建议,如果有可能需要自定义用户模型,最好在项目初期就做,因为项目一旦跑起来、数据库里有了数据,再换用户模型会非常痛苦,涉及数据迁移和外键关系重建。

自定义的方式有两种。一种是继承AbstractUser,保留默认字段,额外加自己的字段。另一种是继承AbstractBaseUser,完全自己定义字段,灵活但工作量大。绝大多数场景用第一种就够了。

# users/models.py from django.contrib.auth.models import AbstractUser from django.db import models class CustomUser(AbstractUser): phone = models.CharField(max_length=20, blank=True, verbose_name="手机号") avatar = models.URLField(blank=True, verbose_name="头像地址") class Meta: db_table = "custom_user" verbose_name = "用户"

定义完之后,必须在settings.py里声明AUTH_USER_MODEL = "users.CustomUser",否则 Django 还是用默认的 User。这一步千万不能忘,忘了的话迁移会出各种奇怪的问题。

注意:自定义用户模型必须在第一次执行 migrate 之前完成配置。如果已经迁移过,再改这个配置,需要清空数据库重新迁移,生产环境基本不可行。

3.2 密码校验背后的哈希逻辑

前面提到密码是哈希存储的,这里展开说一下细节,因为理解它对你排查登录问题很有帮助。Django 默认使用 PBKDF2 算法,配合 SHA256 和随机盐,迭代次数在 settings 里可以配置。每次用户设置密码时,Django 会生成一个随机盐,把盐和密码一起哈希,最终存成类似pbkdf2_sha256$260000$salt$hash这样的字符串。

这个字符串里包含了算法名、迭代次数、盐和哈希值。校验的时候,Django 会解析出算法和盐,用同样的方式重新计算,再比对结果。所以即使两个用户密码完全一样,数据库里存的哈希也完全不同,因为盐不一样。

这里有个实用技巧:Django 提供了check_password和set_password两个方法,任何时候都不要自己去比对密码。

from django.contrib.auth.hashers import check_password, make_password # 设置密码 user.set_password("new_password") user.save() # 校验密码 if check_password("input_password", user.password): print("密码正确")

迭代次数这个参数,随着硬件性能提升,Django 每个大版本都会调高默认值。你可以在 settings 里手动指定PASSWORD_HASHERS来调整,但一般不建议动,除非你有明确的性能或安全需求。

3.3 登录视图的请求处理流程

一个完整的登录视图,处理流程可以拆成几个阶段。第一阶段是 GET 请求,返回登录页面或登录表单。第二阶段是 POST 请求,接收用户提交的凭证。第三阶段是校验,调用authenticate。第四阶段是判断结果,成功则调用login并重定向,失败则返回错误提示。

这里有个细节很多人会忽略:authenticate返回的是用户对象或 None,但它不会自动登录。登录必须显式调用login(request, user)。这两个函数分开设计,是为了让你有机会在登录前做额外判断,比如检查用户是否被禁用、是否完成了邮箱验证。

from django.contrib.auth import authenticate, login from django.shortcuts import render, redirect def login_view(request): if request.method == "POST": username = request.POST.get("username") password = request.POST.get("password") user = authenticate(request, username=username, password=password) if user is not None: login(request, user) return redirect("home") else: return render(request, "login.html", {"error": "用户名或密码错误"}) return render(request, "login.html")

这段代码看起来简单,但每一行都有讲究。authenticate的第一个参数是 request,这是必须的,因为认证后端可能需要访问请求上下文。login函数会自动处理 session 的创建和 cookie 的设置,你不需要手动操作。

3.4 登录拦截与访问控制

登录功能做完,紧接着的需求就是“未登录用户不能访问某些页面”。Django 提供了login_required装饰器,用法很直接。

from django.contrib.auth.decorators import login_required @login_required def profile(request): return render(request, "profile.html")

未登录用户访问时,会被重定向到settings.LOGIN_URL指定的地址,默认是/accounts/login/。你可以在 settings 里改成自己的登录路由。重定向时会带上next参数,记录用户原本想访问的页面,登录成功后可以跳回去。

对于类视图,Django 提供了LoginRequiredMixin,用法类似。对于接口,就不能用重定向了,因为前端拿到 302 会懵。接口场景下应该返回 401 状态码,让前端自己决定跳转逻辑。这时候需要自己写一个装饰器或中间件来判断。

from django.http import JsonResponse def api_login_required(view_func): def wrapper(request, *args, **kwargs): if not request.user.is_authenticated: return JsonResponse({"code": 401, "msg": "未登录"}, status=401) return view_func(request, *args, **kwargs) return wrapper

is_authenticated这个属性,在 Django 里对已登录用户返回 True,对匿名用户返回 False。注意它在新版本里是属性而不是方法,不要写成is_authenticated(),这是很多人从老教程里带过来的习惯性错误。

4. 完整实操过程与核心环节实现

4.1 项目初始化与配置

假设我们从零开始,先建一个项目和一个应用。命令行操作如下。

django-admin startproject mysite cd mysite python manage.py startapp accounts

然后在settings.py里注册应用,配置数据库、模板目录、静态文件这些基础项。重点是要把accounts加进INSTALLED_APPS,并且配置好AUTH_USER_MODEL。

INSTALLED_APPS = [ "django.contrib.admin", "django.contrib.auth", "django.contrib.contenttypes", "django.contrib.sessions", "django.contrib.messages", "django.contrib.staticfiles", "accounts", ] AUTH_USER_MODEL = "accounts.CustomUser" LOGIN_URL = "/accounts/login/" LOGIN_REDIRECT_URL = "/" LOGOUT_REDIRECT_URL = "/accounts/login/"

LOGIN_URL是未登录跳转的地址,LOGIN_REDIRECT_URL是登录成功后默认跳转的地址,LOGOUT_REDIRECT_URL是退出后跳转的地址。这三个配置项建议一开始就设好,省得后面到处改。

4.2 用户模型与数据库迁移

在accounts/models.py里定义用户模型,前面已经给过示例。定义完之后执行迁移。

python manage.py makemigrations accounts python manage.py migrate

迁移完成后,数据库里会多出custom_user表和django_session表。前者存用户,后者存会话。你可以用python manage.py createsuperuser创建一个超级用户,方便测试登录。

提示:如果迁移时报错说AUTH_USER_MODEL引用的模型不存在,检查一下应用是否已注册、模型类名是否拼写正确、app_label是否对得上。

4.3 登录表单的设计与校验

Django 提供了AuthenticationForm,专门用于登录场景,它内置了用户名密码字段和基础校验。你可以直接用它,也可以继承后自定义。

from django import forms from django.contrib.auth.forms import AuthenticationForm class LoginForm(AuthenticationForm): username = forms.CharField( label="用户名", widget=forms.TextInput(attrs={"class": "form-control", "placeholder": "请输入用户名"}) ) password = forms.CharField( label="密码", widget=forms.PasswordInput(attrs={"class": "form-control", "placeholder": "请输入密码"}) )

用表单的好处是,校验逻辑集中在表单里,视图只负责流程控制。AuthenticationForm的clean方法会自动调用authenticate,校验失败会抛出ValidationError,你只需要在视图里判断form.is_valid()即可。

4.4 基于类的登录视图实现

Django 自带的LoginView已经封装好了整个流程,你只需要指定模板和表单类。

from django.contrib.auth.views import LoginView from .forms import LoginForm class MyLoginView(LoginView): template_name = "accounts/login.html" authentication_form = LoginForm redirect_authenticated_user = True

redirect_authenticated_user设为 True 时,已登录用户访问登录页会自动跳转到首页,避免重复登录。这个细节体验很好,建议开启。

对应的模板文件login.html大致长这样。

<form method="post"> {% csrf_token %} {{ form.as_p }} {% if form.errors %} <p class="error">用户名或密码错误,请重试</p> {% endif %} <button type="submit">登录</button> </form>

{% csrf_token %}这个模板标签千万不能漏,否则 POST 请求会被 Django 的 CSRF 中间件拦截,返回 403。这是新手最常踩的坑之一,报错信息是“CSRF verification failed”,看到这个先检查表单里有没有加这个标签。

4.5 接口登录的实现与返回结构

如果是前后端分离项目,登录接口需要返回 JSON。这里我用 Django 原生的JsonResponse演示,不依赖第三方库。

import json from django.http import JsonResponse from django.contrib.auth import authenticate, login from django.views.decorators.csrf import csrf_exempt @csrf_exempt def api_login(request): if request.method != "POST": return JsonResponse({"code": 405, "msg": "方法不允许"}, status=405) try: data = json.loads(request.body) except json.JSONDecodeError: return JsonResponse({"code": 400, "msg": "参数格式错误"}, status=400) username = data.get("username", "").strip() password = data.get("password", "") if not username or not password: return JsonResponse({"code": 400, "msg": "用户名和密码不能为空"}, status=400) user = authenticate(request, username=username, password=password) if user is None: return JsonResponse({"code": 401, "msg": "用户名或密码错误"}, status=401) if not user.is_active: return JsonResponse({"code": 403, "msg": "账号已被禁用"}, status=403) login(request, user) return JsonResponse({ "code": 0, "msg": "登录成功", "data": {"username": user.username, "user_id": user.id} })

这段代码里有几个值得说的点。第一,csrf_exempt装饰器是为了绕过 CSRF 校验,因为接口调用通常不带 CSRF token。但这样做会降低安全性,生产环境更推荐用 token 认证或配置 CORS 白名单。第二,参数校验要放在最前面,空用户名或空密码直接返回,不要浪费一次数据库查询。第三,is_active的判断要单独做,因为authenticate对禁用用户也可能返回 None,具体行为取决于认证后端。

4.6 退出登录的实现

退出登录就是调用logout函数,它会清除当前会话。

from django.contrib.auth import logout from django.shortcuts import redirect def logout_view(request): logout(request) return redirect("login")

logout会做两件事:清空当前 request 的 user 对象,删除服务端的 session 记录。注意它不会删除浏览器里的 cookie,但 cookie 里的 sessionid 已经失效了,下次请求会被当成匿名用户。

接口场景下,退出接口同样返回 JSON 即可。如果你的项目用了 token 认证,退出时还需要把 token 加入黑名单或从存储中删除,这部分逻辑要单独处理。

5. 常见问题与排查技巧实录

5.1 登录后立刻又变成未登录状态

这是最经典的问题,表现是登录接口返回成功,但下一个请求request.user还是匿名用户。原因通常有三个。

第一个是 cookie 没写进去。检查响应头里有没有Set-Cookie,如果没有,可能是SESSION_COOKIE_SECURE设成了 True 但你在用 HTTP 访问,浏览器会拒绝写入。开发环境把它设为 False。

第二个是跨域导致 cookie 丢失。前后端分离时,前端域名和后端域名不同,浏览器默认不发送跨域 cookie。需要后端设置CORS_ALLOW_CREDENTIALS = True,前端请求时带上withCredentials: true,同时后端不能把Access-Control-Allow-Origin设成*,必须指定具体域名。

第三个是 session 存储配置有问题。如果你把 session 存到了缓存,但缓存服务没启动或连不上,session 就写不进去。检查一下缓存配置和日志。

5.2 CSRF 校验失败的排查思路

CSRF 失败通常报 403,页面提示“CSRF verification failed”。排查顺序是这样的:先看表单里有没有{% csrf_token %},再看请求头里有没有X-CSRFToken,然后看 cookie 里有没有csrftoken。三者缺一不可。

接口场景下,如果前端是独立域名,还需要后端配置CSRF_TRUSTED_ORIGINS,把前端域名加进去。Django 4.0 之后这个配置是必须的,之前版本不强制。

注意:不要为了省事直接全局禁用 CSRF 中间件。CSRF 防护是防止跨站请求伪造的重要手段,禁用后你的登录接口可能被恶意站点利用。

5.3 密码正确却提示错误

这种情况一般是用户对象的状态问题。authenticate默认的ModelBackend会检查is_active,如果用户被标记为未激活,即使密码正确也会返回 None。检查一下用户表里的is_active字段。

还有一种可能是密码哈希算法不匹配。如果你手动改过PASSWORD_HASHERS,或者从别的系统迁移了用户数据,哈希格式对不上就会校验失败。这种情况下需要重新设置密码。

5.4 常见问题速查表

问题现象可能原因排查方向
登录成功但状态丢失cookie 未写入检查 Set-Cookie 和 Secure 配置
403 CSRF 失败缺少 token检查表单标签和请求头
密码正确提示错误用户未激活检查 is_active 字段
跨域请求失败CORS 未配置检查 CORS 和凭证设置
退出后仍显示登录浏览器缓存检查 session 是否真删除
登录接口 405请求方法不对确认使用 POST

5.5 几个我踩过的坑

第一个坑是next参数的处理。用login_required跳转到登录页时,URL 会带?next=/target/。如果你在登录成功后直接redirect("home"),用户就跳不回原本想去的页面了。正确做法是从request.GET或request.POST里取next,校验它是不是站内地址,再跳转。校验很重要,否则会有开放重定向漏洞。

第二个坑是 session 过期时间。默认是两周,对于敏感系统可能太长。可以在 settings 里设SESSION_COOKIE_AGE,单位是秒。还可以设SESSION_EXPIRE_AT_BROWSER_CLOSE = True,让浏览器关闭时 session 失效。

第三个坑是并发登录。默认情况下,同一个账号可以在多个设备同时登录,各自有独立的 session。如果你需要限制单点登录,得自己实现逻辑,比如在用户表里存一个当前 session key,登录时比对,不一致就踢掉旧会话。

第四个坑是登录日志。很多项目上线后才想起来要记录登录行为,但这时候用户表已经跑了一段时间。建议一开始就加一个登录日志表,记录用户、时间、IP、结果。排查异常登录时非常有用。

6. 安全加固与体验优化建议

6.1 登录频率限制

不做频率限制的登录接口,等于给暴力破解开了绿灯。我一般会在登录视图里加一层计数,同一个 IP 或同一个用户名在短时间内失败次数超过阈值,就锁定一段时间。

实现方式可以用缓存计数器。每次登录失败,给对应的 key 加一,设置过期时间。登录成功则清空计数。判断时如果计数超过阈值,直接返回“尝试次数过多,请稍后再试”。

from django.core.cache import cache def check_login_attempts(username): key = f"login_fail_{username}" count = cache.get(key, 0) return count >= 5 def record_login_failure(username): key = f"login_fail_{username}" count = cache.get(key, 0) cache.set(key, count + 1, timeout=300)

阈值和锁定时间根据业务调整。面向普通用户的站点,5 次失败锁 5 分钟比较合适。内部系统可以更严格。

6.2 记住我与自动登录

“记住我”功能本质上是延长 session 有效期。勾选时把SESSION_COOKIE_AGE设长一些,不勾选则用默认值。实现上可以在登录时根据表单里的复选框动态设置request.session.set_expiry()。

if request.POST.get("remember_me"): request.session.set_expiry(60 * 60 * 24 * 30) # 30 天 else: request.session.set_expiry(0) # 浏览器关闭即失效

set_expiry(0)表示会话在浏览器关闭时过期,这是最安全的默认行为。记住我则给一个较长的固定期限。

6.3 登录页面的体验细节

登录页是用户进入系统的第一道门,体验好坏直接影响留存。几个我实践下来觉得值得做的点:错误提示要具体但不要泄露信息,统一说“用户名或密码错误”而不是“该用户不存在”,避免被用来枚举账号。输入框要支持回车提交,这是基本预期。密码框要有显示切换按钮,减少输错概率。登录按钮在提交后要禁用并显示加载状态,防止重复提交。

还有一点是自动聚焦。页面加载后光标自动落在用户名输入框,用户可以直接开始输入,少点一次鼠标。这些细节单个看起来不起眼,叠加起来体验差距很明显。

6.4 多端登录的会话管理

如果项目同时有网页端和移动端,会话管理要分开考虑。网页端用 cookie 维持 session 没问题,移动端用 cookie 比较别扭,更适合用 token。这时候可以两套并存:网页端走 session,移动端走 token,后端通过请求头判断用哪种认证方式。

Django 的认证后端是可插拔的,你可以写一个自定义后端,同时支持 session 和 token。这样视图层不用关心认证方式,request.user始终可用。

7. 我个人在实际项目中的几点体会

登录功能看起来是每个项目都会做的基础模块,但真正做好并不容易。我最大的体会是:不要等到项目后期才认真对待认证体系。用户模型、会话存储、登录拦截这些决策,越早定下来越好,后期改动的成本是指数级增长的。

另一个体会是关于安全。很多开发者觉得“我这个项目没什么敏感数据,不用搞那么严”。但登录接口是攻击面最大的入口之一,频率限制、密码强度校验、CSRF 防护这些基础措施,成本很低但收益很高,没有理由不做。

最后分享一个实用习惯:每次搭完登录模块,我都会用几个边界场景自测一遍。比如密码错误、用户不存在、用户被禁用、重复提交、未登录访问受保护页面、登录后访问登录页、退出后再访问受保护页面。这几个场景覆盖了绝大多数线上问题,提前测一遍能省下大量排查时间。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/17 14:28:20

psplash源码解析:从帧缓冲到FIFO,玩转嵌入式Linux启动画面

简介&#xff1a;面向ARM嵌入式Linux的psplash开源源码包&#xff0c;适用于嵌入式设备&#xff08;树莓派、开发板&#xff09;系统启动或升级场景&#xff0c;帮助开发者在启动阶段实现带进度条和动画的自定义图形界面&#xff0c;提升用户体验。资源共79个文件&#xff0c;压…

作者头像 李华
网站建设 2026/10/17 14:27:35

SpringBoot整合ElasticSearch实战:从倒排索引到Mapping设计

先说结论&#xff1a;SpringBoot 整合 ElasticSearch 这事本身不难&#xff0c;难的是你把 ES 当成数据库用、还是当成搜索引擎用。我见过太多人一上来就写查询代码&#xff0c;结果 mapping 设计一塌糊涂&#xff0c;索引重建了三次&#xff0c;最后被线上数据教做人。这篇内容…

作者头像 李华
网站建设 2026/10/17 14:27:04

流式翻译延迟控制与分段策略:OpenClaw实战解析

做实时字幕和会议同传的同学&#xff0c;应该都体会过那种难受&#xff1a;说话人嘴都快闭上了&#xff0c;翻译结果才慢悠悠弹出来。更尴尬的是&#xff0c;弹出来的还是一句缺头少尾的“半成品”&#xff0c;用户当场就会质疑这东西到底能不能用。我接触 OpenClaw 之前&#…

作者头像 李华
网站建设 2026/10/17 14:26:59

C++模板元编程:编译期计算与零开销抽象实战指南

1. 这不是“写代码”&#xff0c;是在编译期“运行代码”&#xff1a;C元编程的本质破冰 很多人第一次听说“C template metaprogramming”时&#xff0c;下意识反应是&#xff1a;“哦&#xff0c;就是写模板呗&#xff1f;不就是泛型函数和类吗&#xff1f;”——这恰恰踩进了…

作者头像 李华
网站建设 2026/10/17 14:25:50

Charles抓包HTTPS显示unknown?证书与SSL Pinning排查指南

搞移动端开发的人&#xff0c;几乎都绕不过抓包这一步。前阵一个朋友发消息问我&#xff1a;用 Charles 抓手机包的时候&#xff0c;为什么有的包能正常抓到&#xff0c;有的却显示 unknown&#xff1f;他说自己对着同一个 App 翻来覆去刷&#xff0c;大部分接口都能看到 JSON …

作者头像 李华