uniapp做后端对接时,大多数开发者只关注接口能不能通、数据能不能回来,但安全配置往往在项目上线后才暴露出问题。真正的问题不是“要不要做安全”,而是很多团队在开发阶段根本没有把安全配置纳入到统一的请求层设计里。uniapp本身是跨端框架,编译到小程序、H5、App时底层网络能力有差异,如果不在请求层做统一封装和安全策略,后期每个端都要单独修补,成本极高。
先说最常见的误区:把后端接口地址直接写死在页面里,或者把token明文存在本地存储。uniapp提供的uni.request虽然可以发请求,但直接散落在业务代码里,后续要加签名、加密、token刷新、防重放这些安全机制时,几乎无法维护。正确做法是在项目里建立一个统一的请求模块,所有业务请求都走这个模块,安全配置集中在拦截器层处理。
统一请求层是安全配置的基础在uniapp项目里,建议在utils目录下新建request.js,封装uni.request。这个封装不只是包一层Promise,还要处理几个关键安全点:请求头注入、token自动附加、响应拦截、错误码统一处理。下面是一个基础但可用的封装结构:
const BASE_URL = 'https://api.example.com'
const TOKEN_KEY = 'access_token'
function getToken() {
return uni.getStorageSync(TOKEN_KEY) || ''
}
function request(options) {
return new Promise((resolve, reject) => {
const token = getToken()
const header = Object.assign({
'Content-Type': 'application/json',
'X-Request-Timestamp': Date.now().toString()
}, options.header || {})
if (token) {
header['Authorization'] = 'Bearer ' + token
}
uni.request({
url: BASE_URL + options.url,
method: options.method || 'GET',
data: options.data || {},
header,
timeout: 10000,
success(res) {
if (res.statusCode === 401) {
// 统一处理token失效
uni.removeStorageSync(TOKEN_KEY)
uni.navigateTo({ url: '/pages/login/login' })
reject(new Error('登录已过期'))
return
}
if (res.statusCode >= 200 && res.statusCode < 300) {
resolve(res.data)
} else {
reject(res.data)
}
},
fail(err) {
reject(err)
}
})
})
}
export default request
这个封装解决了请求入口分散的问题,但安全配置远不止这些。token存储就是一个需要仔细权衡的点。uni.setStorageSync在小程序和H5端底层是localStorage或小程序缓存,App端是原生存储。这些存储方式都容易被逆向或通过开发者工具查看。对于高敏感场景,建议token只存内存,刷新时用refresh token走独立的存储通道,并且refresh token要设置较短的有效期和单次使用限制。
请求签名与防篡改很多uniapp项目对接后端时,接口完全裸奔,只要拿到token就能无限调用。后端如果只验证token有效性,攻击者截获请求后可以篡改参数重放。解决这个问题需要在请求层加入签名机制。签名方案不一定要很复杂,但要覆盖几个要素:时间戳、随机数、请求体摘要、密钥。具体做法是在request封装里,对每次请求生成一个nonce随机字符串,计算请求参数的哈希,然后用预先协商好的密钥做HMAC签名,放到请求头里。
import CryptoJS from 'crypto-js'
function generateSignature(method, url, data, timestamp, nonce) {
const secret = 'your-secret-key' // 建议通过安全方式下发
const bodyStr = method === 'GET' ? '' : JSON.stringify(data || {})
const signStr = method + url + bodyStr + timestamp + nonce
return CryptoJS.HmacSHA256(signStr, secret).toString()
}
这里有个关键点:密钥不要写死在代码里。uniapp编译后的代码可以被反编译,尤其是App端。建议密钥通过后端接口动态下发,并且定期轮换。小程序端虽然有一定保护,但开发者工具可以抓包,写死的密钥等于没有。另外,签名里的时间戳要配合后端做时间窗口校验,一般允许5分钟内的偏差,超过就拒绝请求,这样即使请求被截获,过期后也无法重放。
HTTPS与证书校验uniapp各端对HTTPS的支持情况不同。H5端浏览器会处理证书校验,但App端使用原生网络栈时,默认行为可能允许不安全的证书。在生产环境必须强制HTTPS,并且在App端配置SSL证书校验。如果使用uniapp的云打包,需要在manifest.json里配置网络相关参数。对于Android端,还要注意不要因为调试方便而开启明文流量,很多开发者为了测试http接口,在AndroidManifest里加了usesCleartextTraffic,上线时忘记移除,导致整个App的流量都可能被中间人劫持。
另一个容易被忽略的是证书固定。App端可以配置证书指纹,在请求层校验服务端证书是否匹配,防止中间人用伪造证书拦截流量。虽然uniapp没有直接提供证书固定API,但可以通过原生插件或自定义基座实现。对于金融、支付类应用,这一步不能省。
敏感数据加密传输有些业务数据即使走了HTTPS,也需要在应用层再做一次加密。比如用户密码、身份证号、银行卡号这类高敏感字段。HTTPS保护的是传输通道,但如果后端日志记录不当或者数据被代理工具在客户端侧截获,应用层加密能提供额外一层保护。在uniapp的请求封装里,可以对特定字段做AES加密,密钥通过非对称加密协商,或者使用后端下发的临时会话密钥。
function encryptSensitiveData(data, sessionKey) {
const jsonStr = JSON.stringify(data)
const encrypted = CryptoJS.AES.encrypt(jsonStr, sessionKey, {
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7
})
return encrypted.toString()
}
注意,AES密钥不要直接硬编码,最好每次登录后由后端返回一个会话密钥,前端用这个密钥加密敏感字段。这样即使某个会话的密钥泄露,影响范围也有限。对于非对称加密,前端可以内置RSA公钥,用公钥加密敏感数据,后端用私钥解密。但RSA加密有长度限制,适合加密小字段,不适合大块数据。
Token管理与自动刷新uniapp项目里token管理是个老大难问题。很多开发者把token有效期设得很长,甚至永不过期,这是很危险的做法。合理的方案是access token短时有效,比如15到30分钟,refresh token有效期稍长,比如7天。当access token过期时,请求层自动用refresh token去刷新,拿到新token后重放原请求。这个逻辑必须放在统一请求层里,不能让每个页面自己处理。
实现自动刷新时要注意并发问题。如果同时有多个请求返回401,不能每个都触发刷新,否则会重复刷新导致refresh token被消耗掉。可以在请求层加一个刷新锁,当刷新进行中时,其他请求先挂起,等刷新完成后用新token重放。
let isRefreshing = false
let pendingQueue = []
function handle401(options) {
if (isRefreshing) {
return new Promise((resolve, reject) => {
pendingQueue.push({ options, resolve, reject })
})
}
isRefreshing = true
return refreshToken().then(newToken => {
uni.setStorageSync(TOKEN_KEY, newToken)
pendingQueue.forEach(item => {
item.options.header['Authorization'] = 'Bearer ' + newToken
request(item.options).then(item.resolve).catch(item.reject)
})
pendingQueue = []
return newToken
}).finally(() => {
isRefreshing = false
})
}
refresh token本身也要做好保护。建议refresh token只存内存,不持久化存储。App端可以利用原生安全存储,比如iOS的Keychain,Android的Keystore。小程序端受限于平台能力,只能做相对妥协的方案。如果refresh token泄露,攻击者可以持续获取新的access token,所以refresh token要支持吊销,并且每次刷新后旧token立即失效。
请求参数校验与防注入前端做参数校验不只是为了用户体验,也是安全的第一道防线。uniapp项目里,用户输入的数据可能包含SQL注入、XSS攻击载荷。虽然最终安全校验必须在后端完成,但前端过滤掉明显的恶意输入能减少后端压力。在请求封装里,可以对所有字符串参数做基础过滤,移除危险字符。对于富文本输入,要使用专门的过滤库,不能简单用正则替换。
另外,uniapp的H5端容易受到XSS攻击。如果页面里有v-html渲染用户输入的内容,必须对内容做白名单过滤。小程序端相对安全,因为小程序环境隔离了DOM操作,但也要注意不要在页面里使用eval或Function构造动态代码。App端如果使用了webview组件加载外部页面,要限制跳转域名,防止加载恶意页面。
环境隔离与配置管理开发环境、测试环境、生产环境的接口地址和密钥往往不同。很多uniapp项目通过手动注释切换环境,这样很容易出错,测试环境的配置可能被打包到生产版本里。建议在项目里建立config目录,通过环境变量或构建参数区分环境。在编译时注入不同的配置,而不是在代码里写死判断逻辑。
// config/env.js
const envConfig = {
development: {
BASE_URL: 'https://dev-api.example.com',
ENABLE_LOG: true
},
production: {
BASE_URL: 'https://api.example.com',
ENABLE_LOG: false
}
}
const currentEnv = process.env.NODE_ENV || 'development'
export default envConfig[currentEnv]
生产环境的密钥、证书指纹等敏感配置不要放在代码仓库里。可以通过CI/CD流程在构建时注入,或者通过后端接口动态获取。如果必须放在前端代码里,至少要混淆处理,并且定期更换。很多安全事件都是因为开发者把生产密钥提交到了代码仓库,然后仓库被公开访问导致泄露。
日志与监控安全配置不只是防御,还包括发现问题的能力。uniapp项目里应该接入错误监控和请求日志。当请求异常、签名失败、token刷新失败时,要上报到监控平台。这些日志本身也要脱敏,不能记录完整token、密码等敏感信息。监控数据能帮助发现异常请求模式,比如某个用户短时间内大量请求失败,可能是token被盗用。
在实际项目中,建议把安全相关的日志单独分类,设置告警阈值。比如同一设备在短时间内尝试多个不同账号登录,或者请求签名错误率突然升高,这些都可能是攻击信号。uniapp各端的上报方式不同,可以封装统一的日志模块,在请求层拦截异常并上报。
总结来说,uniapp对接后端的安全配置不是某一个点的加固,而是从请求层设计、token管理、签名机制、传输加密、环境隔离到监控告警的完整体系。很多安全问题的根源是开发阶段图省事,把安全配置放在最后补。正确的做法是在项目初始化时就建立统一请求层,把安全策略固化到框架里,后续业务开发只需要关注业务逻辑,安全配置自动生效。这样才能在跨端场景下保持一致的防护能力,避免因为某个端的疏忽导致整体防线被突破。
