【商城实战】专栏重磅来袭!这是一份专为开发者与电商从业者打造的超详细指南。从项目基础搭建,运用 uniapp、Element Plus、SpringBoot 搭建商城框架,到用户、商品、订单等核心模块开发,再到性能优化、安全加固、多端适配,乃至运营推广策略,102 章内容层层递进。无论是想深入钻研技术细节,还是探寻商城运营之道,本专栏都能提供从 0 到 1 的系统讲解,助力你打造独具竞争力的电商平台,开启电商实战之旅。
在当今数字化时代,商城的安全至关重要。随着电商业务的飞速发展,越来越多的用户在商城中进行购物、支付等操作,商城系统中存储了大量的用户信息、商品数据和交易记录。一旦商城出现安全漏洞,不仅会导致用户数据泄露,给用户带来经济损失和隐私泄露风险,还会严重损害商城的声誉和信誉,导致用户流失,给商城运营者带来巨大的经济损失。因此,保障商城的安全是商城开发和运营过程中不可或缺的重要环节。而了解常见的安全漏洞及其防范措施,则是保障商城安全的基础,接下来将详细讲解 SQL 注入、XSS 攻击、CSRF 攻击等常见安全漏洞的原理。
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@PostMapping("/login")
public Result login(@RequestBody User user) {
String sql = "SELECT * FROM user WHERE username = '" + user.getUsername() + "' AND password = '" + user.getPassword() + "'";
// 这里使用了拼接字符串的方式构建SQL语句,未对参数进行任何过滤
List<User> userList = userService.list(new QueryWrapper<User>().last(sql));
if (userList != null && userList.size() > 0) {
return Result.success("登录成功");
} else {
return Result.error("用户名或密码错误");
}
}
}
当攻击者输入用户名admin’ OR ‘1’='1,密码任意时,实际执行的 SQL 语句就会变为SELECT * FROM user WHERE username = ‘admin’ OR ‘1’=‘1’ AND password = ‘任意密码’,从而绕过了登录验证,这将导致严重的安全问题,如用户数据泄露、数据被篡改等。
<template>
<el - container>
<el - header>
<el - input v - model="searchKeyword" placeholder="请输入搜索关键词"></el - input>
<el - button @click="search">搜索</el - button>
</el - header>
</el - container>
</template>
<script>
export default {
data() {
return {
searchKeyword: ''
};
},
methods: {
search() {
window.location.href = `http://example.com/search?keyword=${this.searchKeyword}`;
}
}
};
</script>
当攻击者在搜索框中输入,并点击搜索按钮后,浏览器会跳转到http://example.com/search?keyword=%3Cscript%3Ealert(‘XSS’)%3C/script%3E,此时服务器返回的页面中包含了恶意脚本,浏览器会执行该脚本,弹出警告框。
@RestController
@RequestMapping("/message")
public class MessageController {
@Autowired
private MessageService messageService;
@PostMapping("/save")
public Result save(@RequestBody Message message) {
messageService.save(message);
return Result.success("留言成功");
}
}
前端 Uniapp 代码如下:
<template>
<view>
<input v - model="messageContent" placeholder="请输入留言内容"></input>
<button @click="saveMessage">提交留言</button>
<view v - for="(message, index) in messageList" :key="index">
{{message.content}}
</view>
</view>
</template>
<script>
export default {
data() {
return {
messageContent: '',
messageList: []
};
},
methods: {
saveMessage() {
uni.request({
url: 'http://example.com/message/save',
method: 'POST',
data: {
content: this.messageContent
},
success: (res) => {
if (res.statusCode === 200) {
this.getMessageList();
}
}
});
},
getMessageList() {
uni.request({
url: 'http://example.com/message/list',
method: 'GET',
success: (res) => {
if (res.statusCode === 200) {
this.messageList = res.data;
}
}
});
}
},
onLoad() {
this.getMessageList();
}
};
</script>
如果攻击者在留言内容中输入,提交后,其他用户查看留言板时,这段恶意脚本就会在他们的浏览器中执行。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF - 8">
<meta name="viewport" content="width=device - width, initial - scale = 1.0">
<title>DOM Based XSS</title>
</head>
<body>
<div id="content"></div>
<script>
const urlParams = new URLSearchParams(window.location.search);
const content = urlParams.get('content');
document.getElementById('content').innerHTML = content;
</script>
</body>
</html>
当攻击者构造的 URL 为http://example.com/page?content=%3Cscript%3Ealert(‘XSS’)%3C/script%3E时,页面加载后,恶意脚本就会被执行,弹出警告框。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF - 8">
<meta name="viewport" content="width=device - width, initial - scale = 1.0">
<title>恶意网站</title>
</head>
<body>
<h1>欢迎访问</h1>
<p>点击以下链接领取大奖</p>
<a href="http://bank.com/transfer?amount=1000&toAccount=攻击者账号">点击领取</a>
</body>
</html>
当用户在银行网站登录后,未退出登录状态,又访问了这个恶意网站并点击了 “点击领取” 链接,浏览器会自动携带银行网站的 Cookie 向http://bank.com/transfer?amount=1000&toAccount=攻击者账号发送请求,银行网站的服务器会认为这是合法用户的操作,从而执行转账操作,将用户账户中的 1000 元转到攻击者的账号,给用户造成经济损失。
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.example.demo.entity.User;
import com.example.demo.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
public class UserController {
@Autowired
private UserMapper userMapper;
@GetMapping("/users")
public List<User> getUsers(@RequestParam String username) {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("username", username);
return userMapper.selectList(queryWrapper);
}
}
在上述代码中,通过QueryWrapper类的eq方法来构建查询条件,eq方法的第一个参数是数据库表中的字段名,第二个参数是要查询的值。这里的username是从前端传递过来的参数,QueryWrapper会自动对其进行安全处理,避免了 SQL 注入风险。
mybatis - plus:
global - config:
db - config:
id - type: auto
field - strategy: NOT_EMPTY
configuration:
map - underscore - to - camel - case: true
use - column - label: true
cache - enabled: false
call - setters - on - nulls: true
log - impl: org.apache.ibatis.logging.stdout.StdOutImpl
其中,mybatis - plus是 MyBatis - Plus 的配置前缀;global - config表示全局配置;db - config用于配置数据库相关的参数,如id - type: auto表示主键生成策略为自动生成,field - strategy: NOT_EMPTY表示字段策略为非空时才进行更新或插入操作;configuration用于配置 MyBatis 的一些通用属性,如map - underscore - to - camel - case: true表示开启下划线转驼峰命名规则,use - column - label: true表示使用列标签,cache - enabled: false表示禁用二级缓存,call - setters - on - nulls: true表示当查询结果为null时调用对象的 setter 方法,log - impl: org.apache.ibatis.logging.stdout.StdOutImpl表示使用标准输出日志实现。
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.mapper.UserMapper">
<select id="getUserByUsernameAndPassword" resultType="com.example.demo.entity.User">
SELECT * FROM users WHERE username = #{username} AND password = #{password}
select>
mapper>
在上述代码中,#{username}和#{password}就是预编译占位符,MyBatis 在解析 SQL 语句时,会将#{}内的参数替换为?,然后设置参数的值。例如,最终执行的 SQL 语句可能是SELECT * FROM users WHERE username =? AND password =?,而具体的username和password值会在执行时通过参数传递的方式传入。
<template>
<view>
<input v - model="comment" @input="validateComment" placeholder="请输入评论"></input>
<button @click="submitComment">提交评论</button>
</view>
</template>
<script>
export default {
data() {
return {
comment: ''
};
},
methods: {
validateComment() {
const pattern = /[<>"'&\/]/g;
this.comment = this.comment.replace(pattern, '');
},
submitComment() {
// 提交评论的逻辑
}
}
};
</script>
在 Element plus 中,同样可以对输入框进行类似的处理。假设在一个表单中有一个昵称输入框:
<template>
<el - form ref="nicknameForm" :model="nicknameForm">
<el - form - item label="昵称">
<el - input v - model="nicknameForm.nickname" @input="validateNickname"></el - input>
</el - form - item>
<el - button type="primary" @click="submitNickname">提交</el - button>
</el - form>
</template>
<script>
export default {
data() {
return {
nicknameForm: {
nickname: ''
}
};
},
methods: {
validateNickname() {
const pattern = /[<>"'&\/]/g;
this.nicknameForm.nickname = this.nicknameForm.nickname.replace(pattern, '');
},
submitNickname() {
// 提交昵称的逻辑
}
}
};
</script>
function escapeHTML(str) {
return str
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
在 Uniapp 中展示用户评论时,可以这样调用该函数:
<template>
<view>
<view v - for="(comment, index) in commentList" :key="index">
{{escapeHTML(comment)}}
</view>
</view>
</template>
<script>
export default {
data() {
return {
commentList: []
};
},
methods: {
escapeHTML(str) {
return str
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
},
onLoad() {
// 获取评论列表的逻辑,假设获取到的评论列表为commentList
this.commentList = [''];
}
};
</script>
在 Element plus 中展示用户昵称时,也可以类似地使用该转义函数:
<template>
<el - table :data="userList">
<el - table - column prop="nickname" label="昵称" :formatter="formatNickname"></el - table - column>
</el - table>
</template>
<script>
export default {
data() {
return {
userList: [
{ nickname: '' }
]
};
},
methods: {
escapeHTML(str) {
return str
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
},
formatNickname(row, column) {
return this.escapeHTML(row.nickname);
}
}
};
</script>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF - 8">
<meta name="viewport" content="width=device - width, initial - scale = 1.0">
<title>避免innerHTML</title>
</head>
<body>
<div id="content"></div>
<script>
const userInput = '';
document.getElementById('content').innerHTML = userInput;
</script>
</body>
</html>
在上述代码中,当使用innerHTML将userInput插入到div元素中时,浏览器会将解析为 JavaScript 代码并执行,从而导致 XSS 攻击。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF - 8">
<meta name="viewport" content="width=device - width, initial - scale = 1.0">
<title>使用textContent</title>
</head>
<body>
<div id="content"></div>
<script>
const userInput = '';
const contentDiv = document.getElementById('content');
contentDiv.textContent = userInput;
</script>
</body>
</html>
在这个例子中,使用textContent将userInput插入到div元素中,userInput会被当作纯文本处理,不会被解析为 JavaScript 代码,而是直接显示在页面上,避免了 XSS 攻击。
再例如,使用setAttribute方法来设置元素的属性值:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF - 8">
<meta name="viewport" content="width=device - width, initial - scale = 1.0">
<title>使用setAttribute</title>
</head>
<body>
<a id="link" href="#"></a>
<script>
const userInput = 'javascript:alert("XSS")';
const link = document.getElementById('link');
if (!/^javascript:/.test(userInput)) {
link.setAttribute('href', userInput);
}
</script>
</body>
</html>
在上述代码中,先对userInput进行检查,确保其不是以javascript:开头(防止恶意的 JavaScript 链接),然后使用setAttribute方法设置a标签的href属性。这样可以避免直接将恶意的 JavaScript 代码作为链接属性值,从而保证了页面的安全性。
在商城系统的开发与维护过程中,防范安全漏洞是保障系统稳定运行、保护用户数据安全的关键环节。针对常见的 SQL 注入、XSS 攻击和 CSRF 攻击等安全漏洞,我们分别从后端和前端采取了一系列有效的防范措施。在后端,通过使用 MyBatis - Plus 的条件构造器、开启 SQL 注入防护配置以及使用预编译 SQL 语句等方式,能够有效防止 SQL 注入攻击,确保数据库操作的安全性。在前端,通过输入验证与过滤、输出编码以及使用安全的 DOM API 等手段,能够防范 XSS 攻击,避免恶意脚本在用户浏览器中执行。这些防范措施并非孤立存在,而是需要前后端协同配合,形成一个完整的安全防护体系。只有前后端共同发力,才能全面有效地降低安全风险,为商城系统的安全运行提供坚实保障。
随着技术的不断发展和网络环境的日益复杂,商城安全技术也需要不断演进和完善。未来,我们需要持续关注安全漏洞的新形式和新变化,及时调整和优化防范策略。例如,随着人工智能和大数据技术在商城系统中的应用越来越广泛,可能会出现新的安全威胁,如基于人工智能算法的攻击、数据隐私泄露等。因此,我们需要加强对这些新兴技术安全问题的研究,探索相应的防范技术和措施。同时,加强安全监控与应急响应也是未来商城安全的重要方向。通过建立实时的安全监控系统,能够及时发现潜在的安全漏洞和攻击行为,并迅速采取应急响应措施,降低安全事件造成的损失。此外,定期进行安全审计和漏洞扫描,及时修复发现的安全问题,也是保障商城系统安全的重要手段。