Django中表单设为readonly时如何防止数据篡改?
解决Django只读表单字段被篡改提交的问题
核心问题很明确:前端的readonly只是UI层面的限制,任何人都能通过浏览器调试工具篡改字段值并提交,所以必须在后端做强制校验,前端的限制只是辅助提升用户体验。
一、后端强制校验(必须实现)
核心思路是:保留用户第一次提交的合法原始数据,第二次提交时直接使用原始数据,完全忽略前端传过来的只读字段值。
1. 存储原始数据到Session
第一次提交表单(步骤1)时,验证通过后把用户的合法数据存入Session:
def step1_submit(request): if request.method == 'POST': form = ProductForm(request.POST, request.FILES) if form.is_valid(): # 将验证后的干净数据存入Session request.session['product_raw_data'] = form.cleaned_data return redirect('step2_page') # GET请求返回步骤1的表单页面 return render(request, 'step1.html', {'form': ProductForm()})
2. 渲染步骤2的只读表单
从Session取出原始数据,初始化表单并设置只读属性:
def step2_page(request): raw_data = request.session.get('product_raw_data') if not raw_data: # 没有原始数据,直接跳回步骤1 return redirect('step1_submit') # 用原始数据填充表单 form = ProductForm(initial=raw_data) # 设置指定字段为只读 form.fields['name'].widget.attrs['readonly'] = True # 其他需要只读的字段同理 # 注意:这里要传表单对象,不是字符串!你之前的代码写错了 return render(request, 'mypage.html', {'form_one_item': form})
3. 处理第二次提交时忽略篡改值
当用户提交步骤2的表单时,直接使用Session里的原始数据,完全不信任前端传来的只读字段:
def final_submit(request): raw_data = request.session.get('product_raw_data') if not raw_data: return redirect('step1_submit') # 处理业务逻辑,比如保存到数据库 # 只读字段直接用raw_data里的值,不要用request.POST product = Product.objects.create( name=raw_data['name'], # 如果有其他非只读字段,可以从request.POST获取 # price=request.POST.get('price') ) # 清理Session里的临时数据 del request.session['product_raw_data'] return redirect('success_page')
二、前端增强限制(可选,提升体验)
虽然后端已经保证安全,前端可以做一些限制减少恶意篡改的可能性:
- 给只读字段添加
onfocus="this.blur()",阻止字段获得焦点:
{% for field in form_one_item %} {% if field.name == 'name' %} <input type="{{ field.field.widget.input_type }}" name="{{ field.name }}" value="{{ field.value }}" readonly onfocus="this.blur()"> {% else %} {{ field }} {% endif %} {% endfor %}
- 用JS重置篡改后的字段值(虽然能被绕过,但增加门槛):
const readonlyInput = document.querySelector('input[name="name"]'); const originalValue = readonlyInput.value; readonlyInput.addEventListener('input', () => { readonlyInput.value = originalValue; });
三、修复你当前代码的错误
你现在的代码里把表单对象作为字符串'form_one_item'传给模板了,这会导致模板无法正确渲染表单,必须传变量本身:
# 错误写法: return render(request, 'mypage.html', {'form_one_item':'form_one_item'}) # 正确写法: return render(request, 'mypage.html', {'form_one_item': form_one_item})
内容的提问来源于stack exchange,提问作者Federico Gentile
相关产品推荐
相关产品推荐

