· Nacho Coll · Guides · 7 min skaitymo
IPFS įkėlimo tokenai: saugus kliento pusės įkėlimas neatskleidžiant API raktų
Sužinokite, kaip pasirašyti įkėlimo tokenai leidžia saugiai įkelti į IPFS iš naršyklių ir mobiliųjų programėlių neatskleidžiant API rakto.

Kuriant šiuolaikines interneto programas dažnai reikia įkelti failus tiesiai iš naudotojų naršyklių į debesų saugyklą. Tačiau kalbant apie IPFS įkėlimo saugumą, kūrėjai susiduria su sudėtinga dilema: kaip leisti kliento pusės įkėlimą neatskleidžiant savo vertingų API raktų galimam piktnaudžiavimui?
Dauguma IPFS prisegimo paslaugų verčia jus rinktis nepatogų variantą: tvarkyti visus įkėlimus serverio pusėje (sukuriant kliūtis ir sudėtingumą) arba įterpti API raktą į kliento kodą (saugumo košmaras). IPFS.NINJA tai išsprendžia unikalia funkcija, kurios nesiūlo jokia kita prisegimo paslauga: pasirašyti įkėlimo tokenai.

Problema su tradiciniais IPFS API raktais
API rakto atskleidimo rizika
API raktų tiesioginis įterpimas į naršyklės JavaScript reiškia, kad bet kas gali peržiūrėti jūsų šaltinio kodą ir išgauti jūsų kredencialus. Tai gali sukelti:
- Neleistinus įkėlimus, naudojančius jūsų saugyklos kvotą
- Galimą jūsų prisegimo paslaugos paskyros piktnaudžiavimą
- Saugumo atitikties pažeidimus įmonių aplinkose
Serverio pusės kliūtys
Alternatyva --- visų įkėlimų nukreipimas per jūsų vidinę sistemą --- sukuria keletą problemų:
- Padidėjusios serverio pralaidumo išlaidos
- Didesnis vėlavimas naudotojams
- Sudėtingesni infrastruktūros reikalavimai
- Galimi vieninteliai gedimo taškai
Mobiliųjų programėlių saugumas
Mobiliosios programos susiduria su panašiais iššūkiais, kur programų paketuose saugomus API raktus galima išgauti atvirkštine inžinerija.
IPFS įkėlimo tokenų pristatymas
IPFS.NINJA pasirašyti įkėlimo tokenai suteikia saugų vidurio kelią:
- Serveris generuoja tokeną: jūsų vidinė sistema sukuria laiku ribotą, pasirašytą tokeną naudodama jūsų API raktą
- Klientas gauna tokeną: tokenas saugiai perduodamas jūsų priekinei programai
- Tiesioginis įkėlimas: klientai įkelia tiesiai į IPFS.NINJA naudodami pasirašytą tokeną
- Automatinis galiojimo pabaiga: tokenų galiojimas baigiasi po nustatytos trukmės
Įkėlimo tokeno saugumo supratimas
Pasirašyti įkėlimo tokenai naudoja kriptografinius parašus autentiškumui užtikrinti neatskleidžiant pagrindinio API rakto. Kiekvienas tokenas turi:
- Galiojimo laiko žymą: automatinis negaliojimas po nurodytos trukmės
- Naudojimo apribojimus: pasirenkamus failų skaičiaus ar bendro dydžio apribojimus
- Kriptografinį parašą: neleidžia klastoti ar falsifikuoti
- Išdavėjo patikrinimą: susieja su jūsų autentifikuota paskyra
Vidinės sistemos įgyvendinimas: Express.js pavyzdys
// server.js
const express = require('express');
const cors = require('cors');
const app = express();
app.use(express.json());
app.use(cors());
const IPFS_API_KEY = 'bws_1234567890abcdef1234567890abcdef12345678';
app.post('/api/generate-upload-token', async (req, res) => {
try {
const response = await fetch('https://api.ipfs.ninja/upload-tokens', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-Api-Key': IPFS_API_KEY },
body: JSON.stringify({ expiresIn: '1h', maxUploads: 10, maxSizeMB: 50 })
});
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
const tokenData = await response.json();
res.json({ success: true, uploadToken: tokenData.token, expiresAt: tokenData.expiresAt });
} catch (error) {
console.error('Token generation failed:', error);
res.status(500).json({ success: false, error: 'Failed to generate upload token' });
}
});
app.post('/api/verify-upload', async (req, res) => {
const { cid } = req.body;
try {
const response = await fetch(`https://api.ipfs.ninja/pins/${cid}`, { headers: { 'X-Api-Key': IPFS_API_KEY } });
const pinData = await response.json();
res.json({ success: true, verified: pinData.pinned, metadata: pinData });
} catch (error) { res.status(500).json({ success: false, error: 'Verification failed' }); }
});
app.listen(3000, () => { console.log('Server running on port 3000'); });Priekinės dalies įgyvendinimas: saugus kliento įkėlimas
<!DOCTYPE html>
<html>
<head>
<title>Secure IPFS Upload Demo</title>
<style>
body { font-family: Arial, sans-serif; max-width: 600px; margin: 50px auto; padding: 20px; }
.upload-area { border: 2px dashed #ccc; padding: 20px; text-align: center; margin: 20px 0; }
.upload-area.dragover { border-color: #007cba; background: #f0f8ff; }
button { background: #007cba; color: white; border: none; padding: 10px 20px; cursor: pointer; }
.status { margin: 10px 0; padding: 10px; border-radius: 4px; }
.success { background: #d4edda; color: #155724; }
.error { background: #f8d7da; color: #721c24; }
.info { background: #d1ecf1; color: #0c5460; }
</style>
</head>
<body>
<h1>Secure IPFS Upload with Signed Tokens</h1>
<div class="upload-area" id="uploadArea">
<p>Drag & drop files here or click to select</p>
<input type="file" id="fileInput" multiple style="display: none;">
<button onclick="document.getElementById('fileInput').click()">Select Files</button>
</div>
<div id="status"></div>
<div id="results"></div>
<script>
class SecureIPFSUploader {
constructor() { this.uploadToken = null; this.tokenExpiry = null; this.setupEventListeners(); }
setupEventListeners() {
const uploadArea = document.getElementById('uploadArea');
const fileInput = document.getElementById('fileInput');
uploadArea.addEventListener('dragover', (e) => { e.preventDefault(); uploadArea.classList.add('dragover'); });
uploadArea.addEventListener('dragleave', () => { uploadArea.classList.remove('dragover'); });
uploadArea.addEventListener('drop', (e) => { e.preventDefault(); uploadArea.classList.remove('dragover'); this.handleFiles(Array.from(e.dataTransfer.files)); });
fileInput.addEventListener('change', (e) => { this.handleFiles(Array.from(e.target.files)); });
}
async getUploadToken() {
if (this.uploadToken && this.tokenExpiry && new Date() < new Date(this.tokenExpiry)) return this.uploadToken;
try {
this.showStatus('Generating secure upload token...', 'info');
const response = await fetch('/api/generate-upload-token', { method: 'POST', headers: { 'Content-Type': 'application/json' } });
if (!response.ok) throw new Error(`Failed to generate token: ${response.statusText}`);
const data = await response.json();
if (!data.success) throw new Error(data.error || 'Token generation failed');
this.uploadToken = data.uploadToken; this.tokenExpiry = data.expiresAt; return this.uploadToken;
} catch (error) { this.showStatus(`Token generation failed: ${error.message}`, 'error'); throw error; }
}
async uploadFile(file) {
try {
const token = await this.getUploadToken();
const fileBase64 = await this.fileToBase64(file);
this.showStatus(`Uploading ${file.name} to IPFS...`, 'info');
const response = await fetch('https://api.ipfs.ninja/upload/new', {
method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Signed ${token}` },
body: JSON.stringify({ content: fileBase64, description: `File uploaded via secure token: ${file.name}`, metadata: { filename: file.name, fileType: file.type, uploadedAt: new Date().toISOString(), uploadMethod: 'signed-token' } })
});
if (!response.ok) { const errorText = await response.text(); throw new Error(`Upload failed: ${response.status} ${errorText}`); }
const result = await response.json();
return { success: true, filename: file.name, cid: result.cid, size: result.sizeMB, ipfsUri: result.uris.ipfs, httpUrl: result.uris.url };
} catch (error) { return { success: false, filename: file.name, error: error.message }; }
}
async handleFiles(files) {
if (files.length === 0) return; this.clearResults();
try {
const results = await Promise.all(files.map(file => this.uploadFile(file)));
this.displayResults(results);
const successful = results.filter(r => r.success).length;
if (successful === results.length) this.showStatus(`✅ Successfully uploaded ${successful} file(s) to IPFS!`, 'success');
else this.showStatus(`⚠️ Uploaded ${successful}/${results.length} files. Check results below.`, 'error');
} catch (error) { this.showStatus(`Upload failed: ${error.message}`, 'error'); }
}
fileToBase64(file) { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.readAsDataURL(file); reader.onload = () => resolve(reader.result.split(',')[1]); reader.onerror = error => reject(error); }); }
showStatus(message, type) { const s = document.getElementById('status'); s.className = `status ${type}`; s.textContent = message; }
displayResults(results) { document.getElementById('results').innerHTML = '<h3>Upload Results:</h3>' + results.map(r => `<div class="status ${r.success?'success':'error'}" style="margin:10px 0;"><strong>${r.filename}</strong><br>${r.success?`✅ CID: ${r.cid}<br>📊 Size: ${r.size} MB<br>🔗 URL: <a href="${r.httpUrl}" target="_blank">${r.httpUrl}</a>`:`❌ Error: ${r.error}`}</div>`).join(''); }
clearResults() { document.getElementById('results').innerHTML = ''; }
}
const uploader = new SecureIPFSUploader();
</script>
</body>
</html>Papildomi saugumo svarstymai
Tokeno apimties apribojimai
const restrictedToken = await fetch('https://api.ipfs.ninja/upload-tokens', {
method: 'POST',
headers: { 'X-Api-Key': IPFS_API_KEY },
body: JSON.stringify({ expiresIn: '30m', maxUploads: 5, maxSizeMB: 10, allowedMimeTypes: ['image/jpeg', 'image/png'], ipWhitelist: ['192.168.1.0/24'] })
});Turinio patikrinimas
app.post('/api/validate-upload', async (req, res) => {
const { cid } = req.body;
try {
const response = await fetch(`https://ipfs.ninja/ipfs/${cid}`);
const contentType = response.headers.get('content-type');
if (!isValidContentType(contentType)) { await deleteFromIPFS(cid); return res.status(400).json({ error: 'Invalid content type' }); }
res.json({ success: true, validated: true });
} catch (error) { res.status(500).json({ error: 'Validation failed' }); }
});Greičio ribojimas
const rateLimit = require('express-rate-limit');
const tokenLimiter = rateLimit({ windowMs: 15 * 60 * 1000, max: 10, message: 'Too many token requests, please try again later' });
app.use('/api/generate-upload-token', tokenLimiter);Pranašumai prieš tradicinius metodus
vs. serverio pusės tarpinis serveris
- Našumas: tiesioginis įkėlimas panaikina serverio pralaidumo naudojimą
- Masteliavimas: nėra serverio kliūčių didelio įkėlimo laikotarpiais
- Kaina: sumažintos pralaidumo ir apdorojimo išlaidos
vs. kliento pusės API raktas
- Saugumas: nėra API rakto išgavimo ar piktnaudžiavimo rizikos
- Atitiktis: atitinka saugumo audito reikalavimus
vs. kitos prisegimo paslaugos
IPFS.NINJA šiuo metu yra vienintelė pagrindinė prisegimo paslauga, siūlanti pasirašytus įkėlimo tokenus.
Daugiau informacijos apie palyginimą rasite mūsų išsamiame palyginimo vadove.
Gamybos diegimo patarimai
Aplinkos konfigūracija
const config = {
ipfsApiKey: process.env.IPFS_API_KEY, tokenExpiry: process.env.UPLOAD_TOKEN_EXPIRY || '1h',
maxFileSize: process.env.MAX_FILE_SIZE_MB || 50, allowedOrigins: process.env.ALLOWED_ORIGINS?.split(',') || ['localhost:3000']
};Stebėjimas ir registravimas
const winston = require('winston');
const logger = winston.createLogger({ level: 'info', format: winston.format.json(), transports: [new winston.transports.File({ filename: 'upload-security.log' })] });
logger.info('Upload token generated', { userId: req.user.id, clientIP: req.ip, userAgent: req.get('User-Agent'), expiresAt: tokenData.expiresAt });Klaidų apdorojimas
app.use((error, req, res, next) => {
logger.error('Upload token error', { error: error.message, stack: error.stack, userId: req.user?.id, endpoint: req.path });
res.status(500).json({ success: false, error: 'An internal error occurred. Please try again.' });
});Integracija su populiariais karkasais
React Hook
import { useState, useCallback } from 'react';
export function useSecureIPFSUpload() {
const [uploading, setUploading] = useState(false);
const [uploadToken, setUploadToken] = useState(null);
const getToken = useCallback(async () => {
if (uploadToken?.expiresAt && new Date() < new Date(uploadToken.expiresAt)) return uploadToken.token;
const response = await fetch('/api/generate-upload-token', { method: 'POST' });
const data = await response.json(); setUploadToken(data); return data.uploadToken;
}, [uploadToken]);
const uploadFile = useCallback(async (file) => {
setUploading(true); try { const token = await getToken(); } finally { setUploading(false); }
}, [getToken]);
return { uploadFile, uploading };
}Vue.js Composable
import { ref } from 'vue';
export function useSecureUpload() {
const uploading = ref(false); const uploadProgress = ref(0);
const uploadFile = async (file) => { uploading.value = true; };
return { uploading: readonly(uploading), uploadProgress: readonly(uploadProgress), uploadFile };
}Išvada
Pasirašyti įkėlimo tokenai išsprendžia kritinį saugumo iššūkį decentralizuotų programų kūrime. Suteikdami saugų būdą leisti tiesioginius kliento pusės įkėlimus į IPFS neatskleidžiant API raktų, jie atveria naujas architektūrines galimybes šiuolaikinėms interneto programoms.
Norėdami sužinoti daugiau apie IPFS pagrindus, žiūrėkite mūsų vadovą apie kas yra IPFS prisegimas arba naršykite mūsų pilną API vadovėlį. Kūrėjams, vertinantiems skirtingas galimybes, mūsų geriausių IPFS prisegimo paslaugų palyginimas suteikia išsamią informaciją.
Pasiruošę pradėti prisegimą? Sukurkite nemokamą paskyrą --- 500 failų, 1 GB saugykla, dedikuota vartų paslauga. Kredito kortelė nereikalinga.
