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. 监控安全风险

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

上一篇 uni-app开发踩坑记录:新手必看的常见问题与解决方案
下一篇 uni-app性能优化指南:从加载到渲染的全方位提升
感谢您的支持!
微信赞赏码 微信赞赏
支付宝赞赏码 支付宝赞赏