Vue 3 全栈教程:225.输入验证与过滤
概述
输入验证与过滤是Web应用安全的重要组成部分,它可以防止恶意输入、确保数据完整性,并提供良好的用户体验。本集将深入探讨Vue 3应用中的输入验证与过滤机制,包括客户端验证、服务器端验证、常见的验证规则以及具体的实现方案。
输入验证的重要性
1. 防止恶意输入
恶意输入是Web应用安全的主要威胁之一,如SQL注入、XSS攻击、命令注入等。通过输入验证,可以过滤掉恶意字符和恶意代码,防止这些攻击。
2. 确保数据完整性
输入验证可以确保用户输入的数据符合预期格式和范围,如确保邮箱格式正确、密码强度足够、年龄在合理范围内等。
3. 提供良好的用户体验
实时的客户端验证可以在用户输入过程中提供即时反馈,帮助用户正确填写表单,减少提交错误,提高用户体验。
4. 减轻服务器负担
客户端验证可以在数据提交到服务器之前过滤掉大部分无效数据,减少服务器的处理负担和网络流量。
输入验证的类型
1. 客户端验证
客户端验证是在浏览器端进行的验证,通常使用JavaScript实现。它可以提供即时反馈,但不能完全依赖,因为攻击者可以绕过客户端验证。
2. 服务器端验证
服务器端验证是在服务器端进行的验证,是输入验证的最后一道防线。无论客户端是否进行了验证,服务器端都必须进行验证,以确保数据的安全性和完整性。
3. 数据库验证
数据库验证是在数据库层面进行的验证,如使用约束(如NOT NULL、UNIQUE、CHECK等)来确保数据的完整性。
Vue 3中的输入验证实现
1. 原生表单验证
HTML5提供了原生的表单验证功能,如required、type、pattern、min、max等属性。Vue 3可以直接使用这些原生验证功能。
<template>
<div>
<h2>原生表单验证示例</h2>
<form @submit.prevent="submitForm">
<div>
<label>用户名:</label>
<input
v-model="form.username"
type="text"
required
minlength="3"
maxlength="20"
pattern="^[a-zA-Z0-9_]+$"
title="用户名只允许字母、数字和下划线"
>
</div>
<div>
<label>邮箱:</label>
<input
v-model="form.email"
type="email"
required
>
</div>
<div>
<label>密码:</label>
<input
v-model="form.password"
type="password"
required
minlength="6"
>
</div>
<div>
<label>年龄:</label>
<input
v-model.number="form.age"
type="number"
required
min="18"
max="100"
>
</div>
<button type="submit">提交</button>
</form>
<div v-if="submittedData">
<h3>提交的数据:</h3>
<pre>{{ submittedData }}</pre>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
const form = ref({
username: '',
email: '',
password: '',
age: null
})
const submittedData = ref(null)
const submitForm = (event) => {
const formElement = event.target
// 检查表单是否通过原生验证
if (formElement.checkValidity()) {
submittedData.value = { ...form.value }
console.log('表单提交成功', submittedData.value)
} else {
// 触发浏览器的原生验证提示
formElement.reportValidity()
}
}
</script>2. 使用VeeValidate进行表单验证
VeeValidate是一个功能强大的Vue表单验证库,支持多种验证规则和自定义验证。
安装VeeValidate:
npm install vee-validate@next yup基本使用:
<template>
<div>
<h2>VeeValidate表单验证示例</h2>
<Form @submit="submitForm" v-slot="{ errors }">
<div>
<label>用户名:</label>
<Field
name="username"
v-model="form.username"
type="text"
placeholder="请输入用户名"
/>
<span v-if="errors.username" class="error">{{ errors.username }}</span>
</div>
<div>
<label>邮箱:</label>
<Field
name="email"
v-model="form.email"
type="email"
placeholder="请输入邮箱"
/>
<span v-if="errors.email" class="error">{{ errors.email }}</span>
</div>
<div>
<label>密码:</label>
<Field
name="password"
v-model="form.password"
type="password"
placeholder="请输入密码"
/>
<span v-if="errors.password" class="error">{{ errors.password }}</span>
</div>
<div>
<label>确认密码:</label>
<Field
name="confirmPassword"
v-model="form.confirmPassword"
type="password"
placeholder="请确认密码"
/>
<span v-if="errors.confirmPassword" class="error">{{ errors.confirmPassword }}</span>
</div>
<button type="submit">提交</button>
</Form>
<div v-if="submittedData">
<h3>提交的数据:</h3>
<pre>{{ submittedData }}</pre>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { Form, Field, ErrorMessage } from 'vee-validate'
import * as yup from 'yup'
// 定义验证规则
const validationSchema = yup.object({
username: yup
.string()
.required('用户名不能为空')
.min(3, '用户名长度不能少于3个字符')
.max(20, '用户名长度不能超过20个字符')
.matches(/^[a-zA-Z0-9_]+$/, '用户名只允许字母、数字和下划线'),
email: yup
.string()
.required('邮箱不能为空')
.email('邮箱格式不正确'),
password: yup
.string()
.required('密码不能为空')
.min(6, '密码长度不能少于6个字符')
.matches(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).+$/, '密码必须包含大小写字母和数字'),
confirmPassword: yup
.string()
.required('请确认密码')
.oneOf([yup.ref('password')], '两次输入的密码不一致')
})
const form = ref({
username: '',
email: '',
password: '',
confirmPassword: ''
})
const submittedData = ref(null)
const submitForm = async (values) => {
try {
// 验证表单数据
const validatedData = await validationSchema.validate(values, { abortEarly: false })
submittedData.value = validatedData
console.log('表单提交成功', validatedData)
} catch (error) {
console.error('表单验证失败', error)
}
}
</script>
<style scoped>
.error {
color: red;
font-size: 12px;
margin-top: 4px;
display: block;
}
</style>3. 自定义验证规则
在某些情况下,我们需要自定义验证规则来满足特定的业务需求。VeeValidate允许我们轻松地定义和使用自定义验证规则。
<template>
<div>
<h2>自定义验证规则示例</h2>
<Form @submit="submitForm" v-slot="{ errors }">
<div>
<label>手机号:</label>
<Field
name="phone"
v-model="form.phone"
type="text"
placeholder="请输入手机号"
/>
<span v-if="errors.phone" class="error">{{ errors.phone }}</span>
</div>
<div>
<label>身份证号:</label>
<Field
name="idCard"
v-model="form.idCard"
type="text"
placeholder="请输入身份证号"
/>
<span v-if="errors.idCard" class="error">{{ errors.idCard }}</span>
</div>
<button type="submit">提交</button>
</Form>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { Form, Field, defineRule } from 'vee-validate'
import * as yup from 'yup'
// 定义自定义验证规则:手机号验证
const validatePhone = (value) => {
const phoneRegex = /^1[3-9]\d{9}$/
return phoneRegex.test(value) || '请输入正确的手机号'
}
// 定义自定义验证规则:身份证号验证
const validateIdCard = (value) => {
const idCardRegex = /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/
return idCardRegex.test(value) || '请输入正确的身份证号'
}
// 注册自定义验证规则
defineRule('phone', validatePhone)
defineRule('idCard', validateIdCard)
// 使用yup定义验证规则
const validationSchema = yup.object({
phone: yup
.string()
.required('手机号不能为空')
.test('phone', '请输入正确的手机号', (value) => {
return /^1[3-9]\d{9}$/.test(value)
}),
idCard: yup
.string()
.required('身份证号不能为空')
.test('idCard', '请输入正确的身份证号', (value) => {
return /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/.test(value)
})
})
const form = ref({
phone: '',
idCard: ''
})
const submitForm = async (values) => {
try {
const validatedData = await validationSchema.validate(values, { abortEarly: false })
console.log('表单提交成功', validatedData)
} catch (error) {
console.error('表单验证失败', error)
}
}
</script>
<style scoped>
.error {
color: red;
font-size: 12px;
margin-top: 4px;
display: block;
}
</style>4. 实时验证
实时验证可以在用户输入过程中提供即时反馈,帮助用户正确填写表单。VeeValidate支持实时验证,可以通过设置validateOnInput属性来实现。
<template>
<div>
<h2>实时验证示例</h2>
<Form @submit="submitForm" v-slot="{ errors }" :validate-on-input="true">
<div>
<label>用户名:</label>
<Field
name="username"
v-model="form.username"
type="text"
placeholder="请输入用户名"
/>
<span v-if="errors.username" class="error">{{ errors.username }}</span>
</div>
<div>
<label>邮箱:</label>
<Field
name="email"
v-model="form.email"
type="email"
placeholder="请输入邮箱"
/>
<span v-if="errors.email" class="error">{{ errors.email }}</span>
</div>
<button type="submit">提交</button>
</Form>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { Form, Field } from 'vee-validate'
import * as yup from 'yup'
const validationSchema = yup.object({
username: yup
.string()
.required('用户名不能为空')
.min(3, '用户名长度不能少于3个字符'),
email: yup
.string()
.required('邮箱不能为空')
.email('邮箱格式不正确')
})
const form = ref({
username: '',
email: ''
})
const submitForm = async (values) => {
try {
const validatedData = await validationSchema.validate(values, { abortEarly: false })
console.log('表单提交成功', validatedData)
} catch (error) {
console.error('表单验证失败', error)
}
}
</script>
<style scoped>
.error {
color: red;
font-size: 12px;
margin-top: 4px;
display: block;
}
</style>输入过滤
输入过滤是指在用户输入数据时,自动过滤掉无效字符或转换数据格式,如自动去除空格、转换为大写、过滤掉HTML标签等。
1. 使用计算属性进行输入过滤
<template>
<div>
<h2>计算属性输入过滤示例</h2>
<div>
<label>输入:</label>
<input v-model="inputValue" type="text" placeholder="请输入内容">
</div>
<div>
<label>过滤后:</label>
<input v-model="filteredValue" type="text" readonly>
</div>
<div>
<label>大写转换:</label>
<input v-model="uppercaseValue" type="text" readonly>
</div>
<div>
<label>去除HTML标签:</label>
<input v-model="noHtmlValue" type="text" readonly>
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
const inputValue = ref('')
// 过滤掉空格
const filteredValue = computed(() => {
return inputValue.value.replace(/\s+/g, '')
})
// 转换为大写
const uppercaseValue = computed(() => {
return inputValue.value.toUpperCase()
})
// 过滤掉HTML标签
const noHtmlValue = computed(() => {
return inputValue.value.replace(/<[^>]*>/g, '')
})
</script>2. 使用watch进行输入过滤
<template>
<div>
<h2>Watch输入过滤示例</h2>
<div>
<label>手机号:</label>
<input v-model="phone" type="text" placeholder="请输入手机号">
</div>
<div>
<label>只允许数字:</label>
<input v-model="onlyNumbers" type="text" placeholder="请输入数字">
</div>
</div>
</template>
<script setup>
import { ref, watch } from 'vue'
const phone = ref('')
const onlyNumbers = ref('')
// 手机号格式化:自动添加分隔符
watch(phone, (newValue) => {
// 只保留数字
const cleaned = newValue.replace(/\D/g, '')
// 格式化手机号:138 8888 8888
const formatted = cleaned.replace(/(\d{3})(\d{4})(\d{4})/, '$1 $2 $3')
// 防止无限循环
if (formatted !== phone.value) {
phone.value = formatted
}
})
// 只允许输入数字
watch(onlyNumbers, (newValue) => {
const cleaned = newValue.replace(/\D/g, '')
if (cleaned !== onlyNumbers.value) {
onlyNumbers.value = cleaned
}
})
</script>3. 使用自定义指令进行输入过滤
<template>
<div>
<h2>自定义指令输入过滤示例</h2>
<div>
<label>只允许字母:</label>
<input v-model="onlyLetters" type="text" v-only-letters placeholder="请输入字母">
</div>
<div>
<label>只允许数字:</label>
<input v-model="onlyDigits" type="text" v-only-digits placeholder="请输入数字">
</div>
<div>
<label>去除空格:</label>
<input v-model="noSpaces" type="text" v-trim placeholder="请输入内容">
</div>
</div>
</template>
<script setup>
import { ref, appContext } from 'vue'
const onlyLetters = ref('')
const onlyDigits = ref('')
const noSpaces = ref('')
// 获取app实例
const app = appContext.app
// 自定义指令:只允许字母
app.directive('only-letters', {
mounted(el) {
el.addEventListener('input', (e) => {
const value = e.target.value
e.target.value = value.replace(/[^a-zA-Z]/g, '')
// 触发input事件,更新v-model
el.dispatchEvent(new Event('input'))
})
}
})
// 自定义指令:只允许数字
app.directive('only-digits', {
mounted(el) {
el.addEventListener('input', (e) => {
const value = e.target.value
e.target.value = value.replace(/\D/g, '')
el.dispatchEvent(new Event('input'))
})
}
})
// 自定义指令:去除空格
app.directive('trim', {
mounted(el) {
el.addEventListener('input', (e) => {
const value = e.target.value
e.target.value = value.trim()
el.dispatchEvent(new Event('input'))
})
}
})
</script>常见的验证规则
1. 格式验证
- 邮箱验证:确保邮箱格式正确,如`/^[^\s@]+@[^\s@]+.[^\s@]+$/
- 手机号验证:确保手机号格式正确,如`/^1[3-9]\d{9}$/
- URL验证:确保URL格式正确,如`/^https?://[\w-]+(.[\w-]+)+([\w-.,@?^=%&:/
+#]*[\w-@?^=%&/+#])?/ - 身份证号验证:确保身份证号格式正确,如`/(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/
2. 长度验证
- 最小长度:确保输入内容的长度不小于指定值
- 最大长度:确保输入内容的长度不大于指定值
- 固定长度:确保输入内容的长度等于指定值
3. 范围验证
- 数值范围:确保数值在指定范围内,如年龄在18-100之间
- 日期范围:确保日期在指定范围内,如开始日期小于结束日期
4. 复杂度验证
- 密码复杂度:确保密码包含大小写字母、数字和特殊字符
- 字符串复杂度:确保字符串包含多种字符类型
5. 唯一性验证
- 用户名唯一性:确保用户名尚未被注册
- 邮箱唯一性:确保邮箱尚未被注册
输入验证的最佳实践
- 始终进行服务器端验证:客户端验证可以提供良好的用户体验,但不能完全依赖,服务器端验证是最后一道防线。
- 使用成熟的验证库:如VeeValidate、Yup、Joi等,避免自行实现复杂的验证逻辑。
- 提供清晰的错误信息:错误信息应该清晰、具体,帮助用户理解错误原因和如何修复。
- 实时验证:对于简单的验证规则,使用实时验证提供即时反馈;对于复杂的验证规则(如唯一性验证),可以在表单提交时进行验证。
- 输入过滤:在用户输入过程中自动过滤掉无效字符,减少用户的输入错误。
- 防止XSS攻击:对用户输入的HTML内容进行过滤或转义,防止XSS攻击。
- 防止SQL注入:使用参数化查询或ORM框架,避免直接拼接SQL语句。
- 使用HTTPS:确保表单提交的数据通过HTTPS传输,防止数据被窃取。
- 限制输入长度:在前端和后端都限制输入长度,防止缓冲区溢出攻击。
- 定期更新验证规则:根据业务需求和安全威胁的变化,定期更新验证规则。
总结
输入验证与过滤是Web应用安全的重要组成部分,它可以防止恶意输入、确保数据完整性,并提供良好的用户体验。Vue 3应用中可以使用多种方式实现输入验证与过滤,如原生表单验证、VeeValidate库、计算属性、watch、自定义指令等。
在实际应用中,应根据业务需求和安全要求选择合适的验证方式,并遵循输入验证的最佳实践,确保应用的安全性和数据完整性。