問(wèn)題介紹
在前后端分離的模式下徒扶,如果前端希望從后端下載一個(gè)文件氛赐,通常的做法是后端通過(guò)一個(gè)接口返回給前端一個(gè)下載的地址,然后前端模擬瀏覽器的點(diǎn)擊事件
或者調(diào)用window.open
來(lái)實(shí)現(xiàn)文件的下載。
但是在通常情況下佛析,后端的接口都會(huì)需要權(quán)限驗(yàn)證栗精,比如要在header中添加Authorization的頭闯参,但是由于使用上述方法提到的文件下載方式并沒(méi)有使用ajax請(qǐng)求,所以我們并不能簡(jiǎn)單的在header中添加權(quán)限驗(yàn)證的頭悲立。
所以鹿寨,后端僅僅返回一個(gè)下載地址供前端下載的方式是不安全的,任何獲取到該url的人都能夠輕易的得到下載內(nèi)容薪夕。
解決方案
這里介紹兩種解決的方法脚草。
- 依賴于jwt的機(jī)制,使得文件下載的url需要在query中添加token參數(shù)進(jìn)行權(quán)限驗(yàn)證
- 利用 HTML5 File API原献。使用
createObjectURL
玩讳,revokeObjectURL
方法來(lái)創(chuàng)建和下載文件涩蜘,從而可以利用ajax來(lái)模擬實(shí)現(xiàn)文件下載。
基于jwt實(shí)現(xiàn)權(quán)限驗(yàn)證
例如下載地址為 /download/fileA
- 后端需要額外提供一個(gè)接口供用戶獲取下載用的token
- 客戶端使用ajax請(qǐng)求該接口獲取token
- 客戶端模擬打開(kāi)拼接了token的url:
/download/fileA?token=xxx
- 后端解析該token判斷用戶是否有權(quán)限下載該文件
// 服務(wù)器端
const jwt = require('jsonwebtoken');
...
router.get('/download/token', (req, res) => {
const secret = 'xxxxxx'
const token = jwt.sign({}, secret, { expiresIn: 2 }); // 2s內(nèi)有效
return res.json({ token });
});
// 客戶端
const download = async (url) => {
const { data: { token } } = await axios.get('/download/token');
let finalUrl = url;
if (url.includes('?')) {
finalUrl += `&token=${token}`;
} else {
finalUrl += `?token=${token}`;
}
window.open(finalUrl);
}
// 服務(wù)器端具體解析token下載文件的代碼就省略了熏纯。
// jwt的使用參考jsonwebtoken庫(kù)的使用
利用 HTML5 File API 實(shí)現(xiàn)權(quán)限驗(yàn)證
- 后端提供文件下載的接口與其他接口保證同樣的權(quán)限驗(yàn)證策略同诫,例如在header中驗(yàn)證Authorization頭
- 前端使用ajax請(qǐng)求文件下載的接口
- 前端利用window.URL.createObjectURL將請(qǐng)求得到的內(nèi)容轉(zhuǎn)化為objectUrl
- 前端模擬點(diǎn)擊,使用window.URL.revokeObjectURL(url)實(shí)現(xiàn)文件下載
// 客戶端
const download = async (url) => {
const response = await axios.get('/download/fileA', {headers: {Authorization: 'Token xxxxxx'}});
const blob = new Blob([response], { type: 'text/plain;charset=utf-8' });
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = [fileName];
a.click();
window.URL.revokeObjectURL(url);
}