阿里云主机折上折
  • 微信号
Current Site:Index > Safety configuration precautions

Safety configuration precautions

Author:Chuan Chen 阅读数:36451人阅读 分类: ECharts

Security Configuration Considerations

ECharts, as a powerful data visualization library, requires special attention to security configurations in practical applications. Security risks such as data leaks, XSS attacks, and CSRF attacks may arise due to improper configurations.

Data Source Security Validation

Data retrieved from the backend must undergo strict validation to prevent malicious data injection. It is recommended to perform type checks and range limitations on the data:

// Unsafe data handling
function unsafeRender(data) {
  myChart.setOption({
    series: [{
      data: data // Directly using unvalidated data
    }]
  });
}

// Safe data handling
function safeRender(data) {
  if (!Array.isArray(data)) {
    throw new Error('Invalid data format');
  }
  
  const sanitizedData = data.map(item => {
    if (typeof item.value !== 'number' || isNaN(item.value)) {
      return { ...item, value: 0 };
    }
    return item;
  });
  
  myChart.setOption({
    series: [{
      data: sanitizedData
    }]
  });
}

XSS Protection Measures

ECharts supports rich text configurations, which may lead to XSS vulnerabilities. User input must be escaped:

// Unsafe rich text usage
function unsafeTooltipFormatter(params) {
  return params.name + ': ' + params.value + '<br>' + 
         'User input: ' + userInput; // Directly concatenating user input
}

// Safe rich text handling
import { escape } from 'lodash';

function safeTooltipFormatter(params) {
  return escape(params.name) + ': ' + escape(params.value.toString()) + '<br>' + 
         'User input: ' + escape(userInput);
}

// Usage in configuration
option = {
  tooltip: {
    formatter: safeTooltipFormatter
  }
};

Cross-Origin Resource Loading Restrictions

When loading map JSON or asynchronous data, ensure the resource sources are trustworthy:

// Unsafe map resource loading
echarts.registerMap('unsafe-map', 'http://untrusted.com/map.json');

// Safe map resource loading
async function loadSafeMap() {
  try {
    const response = await fetch('/trusted/map.json');
    const mapData = await response.json();
    // Validate map data structure
    if (mapData && mapData.type === 'FeatureCollection') {
      echarts.registerMap('safe-map', mapData);
    }
  } catch (error) {
    console.error('Failed to load map:', error);
  }
}

Sensitive Information Handling

Charts may contain sensitive data, requiring special attention:

// Unsafe display of sensitive data
option = {
  series: [{
    data: [
      {value: 1048, name: 'User A (ID: 123456)'},
      {value: 735, name: 'User B (Phone: 13800138000)'}
    ]
  }]
};

// Safe handling of sensitive data
option = {
  series: [{
    data: [
      {value: 1048, name: 'User A'},
      {value: 735, name: 'User B'}
    ],
    label: {
      formatter: param => {
        // Desensitization
        return param.name.replace(/[^\u4e00-\u9fa5]/g, '') + ':' + param.value;
      }
    }
  }]
};

Permission Control Implementation

Dynamically control chart features based on user permissions:

// Configure chart based on permissions
function configureChartByPermission(userPermissions) {
  const baseOption = {
    // Basic configuration
  };
  
  if (userPermissions.includes('export')) {
    baseOption.toolbox = {
      feature: {
        saveAsImage: {}
      }
    };
  }
  
  if (userPermissions.includes('dataZoom')) {
    baseOption.dataZoom = [
      {
        type: 'slider'
      }
    ];
  }
  
  return baseOption;
}

Dynamic Configuration Validation

Validate dynamically generated configurations:

// Configuration validation function
function validateChartOption(option) {
  const schema = {
    type: 'object',
    required: ['series'],
    properties: {
      series: {
        type: 'array',
        minItems: 1,
        items: {
          type: 'object',
          required: ['type'],
          properties: {
            type: { type: 'string', enum: ['line', 'bar', 'pie'] }
          }
        }
      }
    }
  };
  
  const Ajv = require('ajv');
  const ajv = new Ajv();
  const valid = ajv.validate(schema, option);
  if (!valid) {
    throw new Error('Invalid chart option: ' + ajv.errorsText());
  }
}

// Usage of validation
try {
  validateChartOption(userProvidedOption);
  myChart.setOption(userProvidedOption);
} catch (error) {
  console.error('Chart configuration error:', error);
  // Fallback to safe configuration
  myChart.setOption(fallbackOption);
}

Event Handling Security

Safely handle user interaction events:

// Unsafe event handling
myChart.on('click', params => {
  // Directly executing user-provided callback
  userProvidedCallback(params); 
});

// Safe event handling
const safeHandlers = {};

function registerSafeHandler(event, handler) {
  if (typeof handler !== 'function') return;
  
  if (!safeHandlers[event]) {
    safeHandlers[event] = [];
  }
  
  safeHandlers[event].push(handler);
  
  // Bind actual event only once
  if (safeHandlers[event].length === 1) {
    myChart.on(event, params => {
      safeHandlers[event].forEach(h => {
        try {
          h(params);
        } catch (e) {
          console.error('Handler error:', e);
        }
      });
    });
  }
}

// Safe registration usage
registerSafeHandler('click', params => {
  console.log('Chart clicked:', params.name);
});

Balancing Performance and Security

Complex charts may impact performance; balance security and performance:

// Unsafe performance optimization
function unsafePerformanceOptimize() {
  // Disable all security checks
  echarts.setOption({
    _disableSecurityCheck: true // Dangerous operation
  });
}

// Safe performance optimization
function safePerformanceOptimize() {
  // Render on demand
  const option = {
    series: [{
      progressive: 1000,
      progressiveThreshold: 3000
    }],
    // Disable unnecessary animations
    animation: false,
    // Limit rendered data volume
    dataSampling: 'lttb'
  };
  
  // Use worker
  if (window.Worker) {
    const worker = new Worker('chart-worker.js');
    worker.postMessage({ action: 'compute', data: largeDataSet });
    worker.onmessage = e => {
      if (e.data.error) {
        console.error(e.data.error);
      } else {
        option.series[0].data = e.data.result;
        myChart.setOption(option);
      }
    };
  }
}

Third-Party Plugin Management

Security considerations when using third-party extensions:

// Unsafe plugin loading
function loadUnsafePlugin(url) {
  const script = document.createElement('script');
  script.src = url;
  document.head.appendChild(script); // Directly loading untrusted source
}

// Safe plugin management
const approvedPlugins = {
  'echarts-gl': 'https://cdn.jsdelivr.net/npm/echarts-gl@2.0.8/dist/echarts-gl.min.js',
  'echarts-liquidfill': 'https://cdn.jsdelivr.net/npm/echarts-liquidfill@3.0.0/dist/echarts-liquidfill.min.js'
};

function loadPlugin(name) {
  if (!approvedPlugins[name]) {
    throw new Error(`Plugin ${name} is not approved`);
  }
  
  return new Promise((resolve, reject) => {
    const script = document.createElement('script');
    script.src = approvedPlugins[name];
    script.onload = resolve;
    script.onerror = reject;
    document.head.appendChild(script);
  });
}

// Safe loading usage
loadPlugin('echarts-gl')
  .then(() => {
    // Safely use the plugin
  })
  .catch(err => {
    console.error('Failed to load plugin:', err);
  });

Error Handling and Logging

Robust error handling mechanisms:

// Error handling during chart initialization
try {
  const chart = echarts.init(document.getElementById('chart'));
  chart.setOption(option);
  
  // Listen for error events
  chart.on('error', error => {
    logError('Chart error:', error);
    // Display user-friendly error message
    chart.setOption({
      graphic: {
        type: 'text',
        style: {
          text: 'Chart loading failed',
          fontSize: 16,
          fill: '#ff0000'
        }
      }
    });
  });
  
  // Safe handling during window resize
  window.addEventListener('resize', () => {
    try {
      chart.resize();
    } catch (e) {
      logError('Resize error:', e);
    }
  });
  
} catch (initError) {
  logError('Init error:', initError);
  // Fallback solution
  document.getElementById('chart').innerHTML = '<p>Unable to load chart</p>';
}

// Safe logging
function logError(message, error) {
  const errorInfo = {
    message,
    error: error?.message,
    stack: error?.stack,
    time: new Date().toISOString()
  };
  
  // Send to server
  if (window.navigator.onLine) {
    fetch('/log/error', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(errorInfo)
    }).catch(e => console.error('Log failed:', e));
  }
  
  // Console output
  console.error(errorInfo);
}

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

Front End Chuan

Front End Chuan, Chen Chuan's Code Teahouse 🍵, specializing in exorcising all kinds of stubborn bugs 💻. Daily serving baldness-warning-level development insights 🛠️, with a bonus of one-liners that'll make you laugh for ten years 🐟. Occasionally drops pixel-perfect romance brewed in a coffee cup ☕.