惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

W
WeLiveSecurity
T
Threatpost
C
CXSECURITY Database RSS Feed - CXSecurity.com
T
Threat Research - Cisco Blogs
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Know Your Adversary
Know Your Adversary
Scott Helme
Scott Helme
S
Schneier on Security
T
The Exploit Database - CXSecurity.com
Latest news
Latest news
雷峰网
雷峰网
T
Tor Project blog
T
Tenable Blog
Spread Privacy
Spread Privacy
博客园 - 叶小钗
D
DataBreaches.Net
美团技术团队
A
Arctic Wolf
Project Zero
Project Zero
L
Lohrmann on Cybersecurity
The GitHub Blog
The GitHub Blog
博客园 - 司徒正美
Security Latest
Security Latest
D
Docker
月光博客
月光博客
C
Cyber Attacks, Cyber Crime and Cyber Security
S
Secure Thoughts
T
Troy Hunt's Blog
U
Unit 42
WordPress大学
WordPress大学
I
Intezer
Forbes - Security
Forbes - Security
Microsoft Azure Blog
Microsoft Azure Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
SecWiki News
SecWiki News
罗磊的独立博客
The Last Watchdog
The Last Watchdog
人人都是产品经理
人人都是产品经理
Y
Y Combinator Blog
aimingoo的专栏
aimingoo的专栏
B
Blog
博客园 - 【当耐特】
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
Help Net Security
Help Net Security
S
Security @ Cisco Blogs
Microsoft Security Blog
Microsoft Security Blog
S
SegmentFault 最新的问题
The Cloudflare Blog
V
V2EX

吖远zzyの博客

记录一下豆包视频去水印的研究过程,以及fallback_api加密数据qAAB开头数据的解密。 | 吖远zzyの博客 解决华为开发者工具 DevEco Studio 登录跳转 localhost:10101 端口被阻止问题 | 吖远zzyの博客 微信小程序个人资质审核血泪史:踩坑,从反复被拒到神奇过审 | 吖远zzyの博客 如何实现公众号自动回复或做一个短视频无水印解析机器人? | 吖远zzyの博客 以后在手机上用 Termux 和 GitHub Actions 更新 Hexo 博客 | 吖远zzyの博客 必备!这款万能小程序能给抖音/豆包视频图集去除水印、还有有趣的测反应、玩成语模拟工具。 自制的一些免费API接口第二弹(获取QQ昵称、头像、抖音/豆包视频去水印等...) 告别原生导航栏:微信小程序自定义导航栏完美适配方案 在安卓手机上运行OpenClaw?当然可以,用Termux折腾OpenClaw安装QQ机器人的踩坑记录... | 吖远zzyの博客 个人开发工具之抖音直播录制工具:一款功能强大的Android直播录制应用 UniApp中Canvas绘图的易错点与踩坑指南 QQ频道机器人与UniApp开发:常见踩坑点与解决方案 UniApp中Canvas绘图不显示的常见问题与解决方案 Hexo博客实现随机文章功能的完整教程 QQ频道机器人Android客户端使用指南 | 吖远zzyの博客 安卓版QQ频道机器人APP客户端插件开发指南 | 吖远zzyの博客 2024年AI编程助手深度评测:哪款最适合你? uni-app地图定位踩坑记:地图功能和定位的那些坑 uni-app文件上传踩坑记:图片处理和上传全攻略 uni-app表单验证踩坑记:这些坑我替你踩过了 uni-app开发踩坑记录:新手必看的常见问题与解决方案 uni-app性能优化指南:从加载到渲染的全方位提升 uni-app动画效果实战:从基础到高级的动画实现指南 uni-app组件开发实战:从基础到进阶的最佳实践 uni-app网络请求与缓存策略:构建高效的数据层 uni-app状态管理进阶:Vuex最佳实践与性能优化 uni-app路由与页面跳转:最佳实践与踩坑指南
uni-app安全防护指南:构建可靠的跨端应用
吖远zzy · 2024-04-15 · via 吖远zzyの博客
  1. 1. 1. 数据加密
    1. 1.1. 1.1 加密工具类
    2. 1.2. 1.2 敏感数据存储
  2. 2. 2. 网络安全
    1. 2.1. 2.1 请求加密
    2. 2.2. 2.2 防重放攻击
  3. 3. 3. 代码保护
    1. 3.1. 3.1 代码混淆
    2. 3.2. 3.2 源码加密
  4. 4. 4. 数据防护
    1. 4.1. 4.1 防止SQL注入
    2. 4.2. 4.2 防止XSS攻击
  5. 5. 5. 最佳实践建议
  6. 6. 6. 安全监控
  7. 7. 7. 总结

uni-app安全防护指南:构建可靠的跨端应用 0 次阅读

安全防护是应用开发中不可忽视的环节,本文将详细介绍如何在uni-app中实现全方位的安全防护,助你打造安全可靠的应用。

1. 数据加密

1.1 加密工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
// utils/crypto.js
import CryptoJS from 'crypto-js'

class Crypto {
constructor(options = {}) {
this.key = options.key || 'default_key'
this.iv = options.iv || 'default_iv'
}

// AES加密
encrypt(data) {
const key = CryptoJS.enc.Utf8.parse(this.key)
const iv = CryptoJS.enc.Utf8.parse(this.iv)

const encrypted = CryptoJS.AES.encrypt(
JSON.stringify(data),
key,
{
iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
}
)

return encrypted.toString()
}

// AES解密
decrypt(data) {
const key = CryptoJS.enc.Utf8.parse(this.key)
const iv = CryptoJS.enc.Utf8.parse(this.iv)

const decrypted = CryptoJS.AES.decrypt(
data,
key,
{
iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
}
)

return JSON.parse(decrypted.toString(CryptoJS.enc.Utf8))
}

// RSA加密
rsaEncrypt(data, publicKey) {
const jsencrypt = new JSEncrypt()
jsencrypt.setPublicKey(publicKey)
return jsencrypt.encrypt(data)
}

// RSA解密
rsaDecrypt(data, privateKey) {
const jsencrypt = new JSEncrypt()
jsencrypt.setPrivateKey(privateKey)
return jsencrypt.decrypt(data)
}

// MD5加密
md5(data) {
return CryptoJS.MD5(data).toString()
}

// Base64编码
base64Encode(data) {
return CryptoJS.enc.Base64.stringify(
CryptoJS.enc.Utf8.parse(data)
)
}

// Base64解码
base64Decode(data) {
return CryptoJS.enc.Base64.parse(data).toString(
CryptoJS.enc.Utf8
)
}
}

export default new Crypto({
key: process.env.VUE_APP_CRYPTO_KEY,
iv: process.env.VUE_APP_CRYPTO_IV
})

1.2 敏感数据存储

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
// utils/secure-storage.js
import crypto from './crypto'

class SecureStorage {
constructor(options = {}) {
this.prefix = options.prefix || 'secure_'
this.expire = options.expire || 0
}

// 加密存储
set(key, value, expire = 0) {
const data = {
value,
time: Date.now(),
expire: expire || this.expire
}

const encrypted = crypto.encrypt(data)
uni.setStorageSync(
this.prefix + key,
encrypted
)
}

// 解密读取
get(key) {
const encrypted = uni.getStorageSync(this.prefix + key)
if (!encrypted) return null

try {
const { value, time, expire } = crypto.decrypt(encrypted)

// 判断是否过期
if (expire && Date.now() - time > expire * 1000) {
this.remove(key)
return null
}

return value
} catch (e) {
return null
}
}

// 移除数据
remove(key) {
uni.removeStorageSync(this.prefix + key)
}

// 清空数据
clear() {
const keys = uni.getStorageInfoSync().keys
keys.forEach(key => {
if (key.startsWith(this.prefix)) {
this.remove(key.slice(this.prefix.length))
}
})
}
}

export default new SecureStorage()

2. 网络安全

2.1 请求加密

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// utils/secure-request.js
import crypto from './crypto'

class SecureRequest {
constructor() {
this.publicKey = '' // RSA公钥
}

// 初始化
async init() {
// 获取服务器公钥
const { publicKey } = await uni.request({
url: '/api/public-key'
})
this.publicKey = publicKey
}

// 加密请求
async request(options) {
// 生成随机密钥
const randomKey = Math.random().toString(36).slice(-8)

// 使用RSA加密随机密钥
const encryptedKey = crypto.rsaEncrypt(
randomKey,
this.publicKey
)

// 使用随机密钥加密数据
const encryptedData = crypto.encrypt(
options.data,
randomKey
)

// 发送请求
return uni.request({
...options,
data: {
key: encryptedKey,
data: encryptedData
}
})
}
}

export default new SecureRequest()

2.2 防重放攻击

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
// utils/anti-replay.js
import crypto from './crypto'

class AntiReplay {
constructor() {
this.cache = new Map()
}

// 生成请求签名
sign(params) {
const timestamp = Date.now()
const nonce = Math.random().toString(36).slice(-8)

const data = {
...params,
timestamp,
nonce
}

// 按键名排序
const keys = Object.keys(data).sort()
const signStr = keys.map(key => `${key}=${data[key]}`).join('&')

return {
...data,
sign: crypto.md5(signStr)
}
}

// 验证签名
verify(params) {
const { timestamp, nonce, sign, ...rest } = params

// 检查时间戳是否过期
if (Date.now() - timestamp > 5 * 60 * 1000) {
return false
}

// 检查是否重放
const cacheKey = `${timestamp}:${nonce}`
if (this.cache.has(cacheKey)) {
return false
}

// 验证签名
const keys = Object.keys(rest).sort()
const signStr = keys.map(key => `${key}=${rest[key]}`).join('&')
if (crypto.md5(signStr) !== sign) {
return false
}

// 缓存请求标识
this.cache.set(cacheKey, true)
setTimeout(() => {
this.cache.delete(cacheKey)
}, 5 * 60 * 1000)

return true
}
}

export default new AntiReplay()

3. 代码保护

3.1 代码混淆

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// vue.config.js
const TerserPlugin = require('terser-webpack-plugin')

module.exports = {
configureWebpack: {
optimization: {
minimizer: [
new TerserPlugin({
terserOptions: {
compress: {
drop_console: true, // 移除console
pure_funcs: ['console.log'] // 移除指定函数
},
mangle: {
safari10: true // 兼容safari10
},
output: {
ascii_only: true, // 输出ASCII字符
comments: false // 移除注释
}
}
})
]
}
}
}

3.2 源码加密

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
// utils/code-protection.js
class CodeProtection {
constructor() {
this.isDebug = false
this.init()
}

// 初始化
init() {
// 检测调试模式
this.checkDebugger()
// 禁用控制台
this.disableConsole()
// 禁用右键菜单
this.disableContextMenu()
}

// 检测调试模式
checkDebugger() {
setInterval(() => {
const startTime = Date.now()
debugger
const endTime = Date.now()

if (endTime - startTime > 100) {
this.isDebug = true
// 检测到调试行为
this.handleDebug()
}
}, 1000)
}

// 禁用控制台
disableConsole() {
// #ifdef H5
try {
const noop = () => {}
const methods = [
'log',
'debug',
'info',
'warn',
'error',
'assert',
'dir',
'dirxml',
'trace',
'group',
'groupEnd'
]

methods.forEach(method => {
console[method] = noop
})
} catch (e) {}
// #endif
}

// 禁用右键菜单
disableContextMenu() {
// #ifdef H5
document.oncontextmenu = () => false
// #endif
}

// 处理调试行为
handleDebug() {
if (this.isDebug) {
// 退出应用
// #ifdef APP-PLUS
plus.runtime.quit()
// #endif

// 返回首页
// #ifdef H5 || MP
uni.reLaunch({
url: '/pages/index/index'
})
// #endif
}
}
}

export default new CodeProtection()

4. 数据防护

4.1 防止SQL注入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// utils/sql-escape.js
class SqlEscape {
// 转义SQL特殊字符
escape(str) {
if (typeof str !== 'string') {
return str
}

return str.replace(/[\0\n\r\b\t\\'"\x1a]/g, s => {
switch (s) {
case '\0':
return '\\0'
case '\n':
return '\\n'
case '\r':
return '\\r'
case '\b':
return '\\b'
case '\t':
return '\\t'
case '\x1a':
return '\\Z'
default:
return '\\' + s
}
})
}

// 验证SQL注入
validate(str) {
const pattern = /select|update|delete|exec|count|'|"|=|;|>|<|%/i
return !pattern.test(str)
}
}

export default new SqlEscape()

4.2 防止XSS攻击

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// utils/xss-filter.js
class XssFilter {
constructor() {
this.rules = {
whiteList: {
a: ['href', 'title', 'target'],
img: ['src', 'alt'],
p: [],
div: [],
span: [],
br: [],
strong: [],
em: []
},
stripIgnoreTag: true,
stripIgnoreTagBody: ['script']
}
}

// 过滤HTML
filter(html) {
if (typeof html !== 'string') {
return html
}

return html.replace(/<\/?[^>]*>/g, '').replace(/&[^;]+;/g, '')
}

// 转义HTML
escape(html) {
return html.replace(/[&<>"']/g, match => {
const escape = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#39;'
}
return escape[match]
})
}

// 验证XSS
validate(str) {
const pattern = /<script|javascript:|on\w+\s*=|style\s*=|href\s*=|alert\s*\(|confirm\s*\(|prompt\s*\(/i
return !pattern.test(str)
}
}

export default new XssFilter()

5. 最佳实践建议

  1. 使用HTTPS通信
  2. 实现请求签名
  3. 加密敏感数据
  4. 防止代码泄露
  5. 做好数据校验
  6. 定期安全审计
  7. 及时更新依赖
  8. 记录安全日志

6. 安全监控

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
// utils/security-monitor.js
class SecurityMonitor {
constructor() {
this.logs = []
this.maxLogs = 1000
}

// 记录安全日志
log(type, data) {
const log = {
type,
data,
time: Date.now(),
page: this.getCurrentPage()
}

this.logs.push(log)

// 超出限制清理旧日志
if (this.logs.length > this.maxLogs) {
this.logs = this.logs.slice(-this.maxLogs)
}

// 上报日志
this.report(log)
}

// 获取当前页面
getCurrentPage() {
const pages = getCurrentPages()
const page = pages[pages.length - 1]
return page ? page.route : ''
}

// 上报日志
report(log) {
uni.request({
url: '/api/security/log',
method: 'POST',
data: log
})
}

// 获取日志
getLogs(type) {
if (type) {
return this.logs.filter(log => log.type === type)
}
return this.logs
}

// 清理日志
clearLogs() {
this.logs = []
}
}

export default new SecurityMonitor()

7. 总结

  1. 实现数据加密
  2. 保护网络安全
  3. 防护代码泄露
  4. 过滤危险数据
  5. 监控安全风险

如果觉得文章对你有帮助,欢迎点赞、评论、分享,你的支持是我继续创作的动力!