【问题标题】:How to prevent personal information showing in network request through xhr fetch response如何通过 xhr fetch response 防止网络请求中显示个人信息
【发布时间】:2020-09-24 16:18:01
【问题描述】:

我正在构建一个简单的日程安排模块,我将允许任何人访问网站通过日程安排程序提交约会。

在调度程序模块中,我有以下内容:

  1. 预约以 30 分钟为增量,例如,如果预约在上午 9:00 进行,则预约时间为上午 9:00 至上午 9:30。
  2. 我正在存储根据用户输入安排的约会中的个人信息,例如电话号码、电子邮件地址、全名
  3. 在用户提交日程安排后,我运行 SQL 选择语句,它从 MYSQL DB 中获取已安排约会中的所有数据到日程安排程序组件中,以便用户可以查看已进行哪些约会以及在指定时间。

这很好用,但我有点担心,当我将它推送到生产环境时,我注意到在我的网络请求中,我可以看到从数据库中获取的所有数据。允许任何人通过网络请求查看获取的包含个人信息的数据,这可能是一个安全问题吗?

所以我的问题是如何防止这种情况发生?

这是我的代码:

ReactJS

import React, { Component } from 'react';

import appController from '../../controllers/appController';

import Scheduleservice from '../../services/Scheduleservice';
import { withRouter } from "react-router-dom";
import { Helmet } from 'react-helmet';
import Moment from 'moment';
import AOS from 'aos'; // Animate on scroll

import {
  ScheduleComponent,
  ResourcesDirective,
  ResourceDirective,
  ViewsDirective,
  ViewDirective,
  Inject,
  TimelineViews,
  Resize,
  DragAndDrop,
  TimelineMonth,
  Day,
  Week,
  WorkWeek,
  Month,
  Agenda
} from '@syncfusion/ej2-react-schedule';

import { DateTimePickerComponent } from '@syncfusion/ej2-react-calendars';
import { DropDownListComponent } from '@syncfusion/ej2-react-dropdowns';
import { DataManager, WebApiAdaptor, ODataV4Adaptor } from '@syncfusion/ej2-data';


//import * as dataSource from './datasource.json';

class Voipcall extends Component {
  constructor(props) {
    super(props);
    this.dataManager = new DataManager({
      url: '/api/selectSchedule',
      adaptor: new WebApiAdaptor,
    })
    this.state = {
      scheduleData: [],
      employeeData: [
        {
          Text: 'Facility Appointment',
          Id: 1,
          GroupId: 1,
          Color: '#9e5fff',
          Designation: 'Schedule an appointment',
        },
      ],
    };
  }

  onActionBegin(args) {
    //console.log('data sent after insertion', args);

    if(args.requestType === 'eventCreate' || args.requestType === 'eventChange') {
      let data = args.data instanceof Array ? args.data[0] : args.data;

      //console.log('Original Data', data);

      // If Subject is blank
      if(data.Subject !== 'Add title') {
        //console.log('subject is blank')

        // Format the data Role Id is guest which is 3
        let insertData = {
          RoleId: undefined ? "3" : "3",
          Subject: data.Subject === undefined ? null : data.Subject,
          FullName: data.fullName === undefined ? null : data.fullName,
          Resident: data.resident === undefined ? null : data.resident,
          CommunicationInfo: data.communicationInfo === undefined ? null : data.communicationInfo,
          CommunicationMethod: data.communicationMethod === undefined ? null : data.communicationMethod,
          Email: data.email === undefined ? null : data.email,
          Phone: data.phone === undefined ? null : data.phone,
          StartTime: data.StartTime === undefined ? null : appController.converttoISO(data.StartTime),
          EndTime: data.EndTime === undefined ? null : appController.converttoISO(data.EndTime),
          Description: data.Description === undefined ? null : data.Description
        }

      console.log('data insertion', insertData)

      // Insert Schedule Data into DB
      const insertSchedule = Scheduleservice.insertSchedule(insertData);


      // Refresh the scheduler
      this.props.history.push({
        pathname: '/voipCall',
        });
      }
    }
}

onEventRendered(args) {

  console.log("Start time ", Moment(args.data.startTime).format());
  console.log("End time ", Moment(args.data.endTime).format());

  if(args.element.innerText !== 'Not Available') {
    args.element.style.backgroundColor = '#0CA6C4';
    args.element.innerText = 'Appointment Booked';
  }

  if(args.element.innerText === 'Not Available') {
    args.element.style.backgroundColor = '#ccc';
  }

  if(args.element.innerText === 'Appointment Booked') {
    args.element.style.backgroundColor = '#09A6CA';
  }
}

  getEmployeeName(value) {
    return value.resourceData[value.resource.textField];
  }
  getEmployeeImage(value) {
    let resourceName = this.getEmployeeName(value);
    return resourceName.toLowerCase();
  }
  getEmployeeDesignation(value) {
    return value.resourceData.Designation;
  }
  resourceHeaderTemplate(props) {
    return (
      <div className='template-wrap'>
        <div className='employee-category'>
          <div
            className={'employee-image ' + this.getEmployeeImage(props)}
          ></div>
          <div className='employee-name'>{this.getEmployeeName(props)}</div>
          <div className='employee-designation'>
            {this.getEmployeeDesignation(props)}
          </div>
        </div>
      </div>
    );
  }

  editorTemplate(props) {
    return props !== undefined ? (
      <table
        className='custom-event-editor'
        style={{ width: '100%', cellpadding: '5' }}
      >
        <tbody>
          <tr>
            <td className='e-textlabel'>Meeting Name</td>
            <td style={{ colspan: '4' }}>
              <input
                id='Summary'
                className='e-field e-input'
                type='text'
                name='Subject'
                style={{ width: '100%' }}
                placeholder='Event Name...'
              />
            </td>
          </tr>
          <tr>
            <td className='e-textlabel'>Full Name</td>
            <td style={{ colspan: '4' }}>
              <input
                id='fullName'
                className='e-field e-input'
                type='text'
                name='fullName'
                style={{ width: '100%' }}
                placeholder='Full Name'
              />
            </td>
          </tr>
          <tr>
            <td className='e-textlabel'>Resident Requesting to Speak to</td>
            <td style={{ colspan: '4' }}>
              <input
                id='resident'
                className='e-field e-input'
                type='text'
                name='resident'
                style={{ width: '100%' }}
                placeholder='Resident you wish to speak to...'
              />
            </td>
          </tr>
          <tr>
            <td className='e-textlabel'>Preferred Method of Communication</td>
            <td style={{ colspan: '4' }}>
              <DropDownListComponent
                id='communicationMethod'
                placeholder='Choose Method of Communication'
                data-name='communicationMethod'
                className='e-field'
                style={{ width: '100%' }}
                dataSource={['Zoom', 'Skype', 'Facetime', 'Viber']}
              ></DropDownListComponent>
            </td>
          </tr>
          <tr>
            <td className='e-textlabel'>Communication Information</td>
            <td style={{ colspan: '4' }}>
              <input
                id='communicationInfo'
                className='e-field e-input'
                type='text'
                name='communicationInfo'
                style={{ width: '100%' }}
                placeholder='Communication ID'
              />
            </td>
          </tr>
          <tr>
            <td className='e-textlabel'>Email Address</td>
            <td style={{ colspan: '4' }}>
              <input
                id='email'
                className='e-field e-input'
                type='email'
                name='email'
                style={{ width: '100%' }}
                placeholder='Email Address'
              />
            </td>
          </tr>
          <tr>
            <td className='e-textlabel'>Phone Number ex: 123456789</td>
            <td style={{ colspan: '4' }}>
              <input
                id='phone'
                className='e-field e-input'
                type='tel'
                name='phone'
                style={{ width: '100%' }}
                placeholder='Phone Number'
              />
            </td>
          </tr>
          <tr>
            <td className='e-textlabel'>From</td>
            <td style={{ colspan: '4' }}>
              <DateTimePickerComponent
                id='StartTime'
                format='MM/dd/yy hh:mm a'
                data-name='StartTime'
                value={new Date(props.startTime || props.StartTime)}
                className='e-field'
                disabled
              ></DateTimePickerComponent>
            </td>
          </tr>
          <tr>
            <td className='e-textlabel'>To</td>
            <td style={{ colspan: '4' }}>
              <DateTimePickerComponent
                id='EndTime'
                format='MM/dd/yy hh:mm a'
                data-name='EndTime'
                value={new Date(props.endTime || props.EndTime)}
                className='e-field'
                disabled
              ></DateTimePickerComponent>
            </td>
          </tr>
          <tr>
            <td className='e-textlabel'>Reason</td>
            <td style={{ colspan: '4' }}>
              <textarea
                id='Description'
                className='e-field e-input'
                name='Description'
                rows={3}
                cols={50}
                style={{
                  width: '100%',
                  height: '60px !important',
                  resize: 'vertical',
                }}
              ></textarea>
            </td>
          </tr>
        </tbody>
      </table>
    ) : (
      <div></div>
    );
  }

  fetchSchedule = async () => {
    const selectSchedule = await Scheduleservice.selectSchedule();

    await this.setState({
      scheduleData: selectSchedule
    })

    //console.log("Schedule Data State is ", this.state.scheduleData);
    //console.log("Local Datasource is ", dataSource.scheduleData);
    //console.log("state is ", this.state)

  }

  componentDidMount = async () => {
    await this.fetchSchedule();
  }

  render() {
    const { match, location, history } = this.props;
    return (
      <div className='voipContainer'>
        <div className='fluid-container'>
          <div className='container'>
            <div className='row'>
              <div className='schedule-control-section'>
                <div className='col-lg-12 control-section'>
                  <div className='control-wrapper drag-sample-wrapper'>
                    <div className='schedule-container'>
                      <ScheduleComponent
                        //ref={(schedule) => (this.scheduleObj = schedule)}
                        //ref={(schedule) => console.log("Schedule Data is", schedule)}
                        cssClass='block-events'
                        width='100%'
                        height='650px'
                        selectedDate={new Date()}
                        minDate={appController.previousDay(new Date())}
                        showTimeIndicator={false}
                        currentView='Day'
                        resourceHeaderTemplate={this.resourceHeaderTemplate.bind(
                          this
                        )}
                        actionBegin={this.onActionBegin.bind(this)}
                        eventSettings={{
                          dataSource: this.dataManager,
                        }}
                        eventRendered={this.onEventRendered.bind(this)}
                        // group={{
                        //   enableCompactView: false,
                        //   resources: ['Employee'],
                        // }}
                        //readonly={true}
                        timeScale={{ enable: true, interval: 30, slotCount: 1 }}
                        editorTemplate={this.editorTemplate.bind(this)}
                      >
                        <ResourcesDirective>
                          <ResourceDirective
                            field='EmployeeId'
                            title='Employees'
                            name='Employee'
                            allowMultiple={true}
                            dataSource={this.state.employeeData}
                            textField='Text'
                            idField='Id'
                            colorField='Color'
                          ></ResourceDirective>
                        </ResourcesDirective>
                        <ViewsDirective>
                          <ViewDirective option='Day' />
                          <ViewDirective option='TimelineDay' />
                        </ViewsDirective>
                        <Inject
                          services={[
                            Day, Week, WorkWeek, Month, Agenda
                          ]}
                        />
                      </ScheduleComponent>
                    </div>
                  </div>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
    );
  }
}

export default withRouter(Voipcall);

MYSQL 语句

var db = require('../dbconnection');

var schedule = {
  selectSchedule: function (data, callback) {
    db.query(
      'select Id, Role_Id, Subject, Location, StartTime, EndTime, Description, Owner, Priority, Recurrence, RecurrenceType, RecurrenceTypeCount, Reminder, Categorize, CustomStyle, AllDay, RecurrenceStartDate, RecurrenceEndDate, RecurrenceRule, StartTimeZone, EndTimeZone, IsBlock, isSlotAvailable, FullName, Resident, CommunicationMethod, CommunicationInfo, Email, Phone, Reason from schedule',
      callback
    );
  },
  insertSchedule: function(data, callback) {
    db.query('insert schedule set Role_Id=?, Subject=?, FullName=?, Resident=?, CommunicationMethod=?, CommunicationInfo=?, Email=?, Phone=?, Reason=?, IsBlock=?, StartTime=?, EndTime=?', [data.RoleId, data.Subject, data.FullName, data.Resident, data.CommunicationMethod, data.CommunicationInfo, data.Email, data.Phone, data.Description, "true", data.StartTime, data.EndTime], callback)

    //console.log('data got sent to backend', data);
  }
};

module.exports = schedule;

网络请求屏幕截图以及 JSON 响应:

[
   {
      "Id":1,
      "Role_Id":null,
      "Subject":"Bering Sea Gold DB Test",
      "Location":"chn",
      "StartTime":"2020-09-23T09:00:00",
      "EndTime":"2020-09-23T10:30:00",
      "Description":"",
      "Owner":1,
      "Priority":null,
      "Recurrence":1,
      "RecurrenceType":null,
      "RecurrenceTypeCount":null,
      "Reminder":null,
      "Categorize":"1,2",
      "CustomStyle":null,
      "AllDay":"false",
      "RecurrenceStartDate":null,
      "RecurrenceEndDate":null,
      "RecurrenceRule":"FREQ=DAILY;INTERVAL=2;COUNT=10",
      "StartTimeZone":null,
      "EndTimeZone":null,
      "IsBlock":"",
      "isSlotAvailable":null,
      "FullName":null,
      "Resident":null,
      "CommunicationMethod":null,
      "CommunicationInfo":null,
      "Email":null,
      "Phone":null,
      "Reason":null
   },
   {
      "Id":2,
      "Role_Id":null,
      "Subject":"Not Available",
      "Location":null,
      "StartTime":"2020-08-01T03:00:00.000Z",
      "EndTime":"2020-08-01T09:00:00.000Z",
      "Description":null,
      "Owner":null,
      "Priority":null,
      "Recurrence":0,
      "RecurrenceType":null,
      "RecurrenceTypeCount":null,
      "Reminder":null,
      "Categorize":null,
      "CustomStyle":null,
      "AllDay":null,
      "RecurrenceStartDate":null,
      "RecurrenceEndDate":null,
      "RecurrenceRule":"FREQ=DAILY;INTERVAL=1;",
      "StartTimeZone":null,
      "EndTimeZone":null,
      "IsBlock":"true",
      "isSlotAvailable":null,
      "FullName":null,
      "Resident":null,
      "CommunicationMethod":null,
      "CommunicationInfo":null,
      "Email":null,
      "Phone":null,
      "Reason":null
   },
   {
      "Id":22,
      "Role_Id":3,
      "Subject":"Test Appointment 1",
      "Location":null,
      "StartTime":"2020-09-24T14:30:00.000Z",
      "EndTime":"2020-09-24T15:00:00.000Z",
      "Description":null,
      "Owner":null,
      "Priority":null,
      "Recurrence":0,
      "RecurrenceType":null,
      "RecurrenceTypeCount":null,
      "Reminder":null,
      "Categorize":null,
      "CustomStyle":null,
      "AllDay":null,
      "RecurrenceStartDate":null,
      "RecurrenceEndDate":null,
      "RecurrenceRule":null,
      "StartTimeZone":null,
      "EndTimeZone":null,
      "IsBlock":"true",
      "isSlotAvailable":null,
      "FullName":"John Doe",
      "Resident":"Alex West",
      "CommunicationMethod":"Facetime",
      "CommunicationInfo":"test@test.com",
      "Email":"test@test.com",
      "Phone":"999999999",
      "Reason":"This is a test Module."
   },
   {
      "Id":23,
      "Role_Id":3,
      "Subject":"Test Appointment 2",
      "Location":null,
      "StartTime":"2020-09-24T18:30:00.000Z",
      "EndTime":"2020-09-24T19:00:00.000Z",
      "Description":null,
      "Owner":null,
      "Priority":null,
      "Recurrence":0,
      "RecurrenceType":null,
      "RecurrenceTypeCount":null,
      "Reminder":null,
      "Categorize":null,
      "CustomStyle":null,
      "AllDay":null,
      "RecurrenceStartDate":null,
      "RecurrenceEndDate":null,
      "RecurrenceRule":null,
      "StartTimeZone":null,
      "EndTimeZone":null,
      "IsBlock":"true",
      "isSlotAvailable":null,
      "FullName":"Jason Stride",
      "Resident":"Chris Thomson",
      "CommunicationMethod":"Facetime",
      "CommunicationInfo":"test2@test.com",
      "Email":"test2@test.com",
      "Phone":"999999999",
      "Reason":"This is test appointment 2"
   }
]

【问题讨论】:

    标签: mysql node.js reactjs security


    【解决方案1】:

    记住三件事:

    1. 使用 https:// 将数据传送到用户的浏览器。网络爬虫很难拦截以这种方式传递的数据。

    2. 确保每个用户只收到他们自己的数据,或他们有权查看的数据。不要依赖您的前端代码来向用户 B 隐藏用户 A 的数据。它不会起作用,因为任何用户都可以在 devtools 的“网络”选项卡中查看您的请求和响应。您通常使用与每个登录用户绑定的难以猜测的会话令牌来执行此操作。

    3. 不要让您的网络服务器返回任何您不希望浏览器用户看到的数据。

    【讨论】:

    • 感谢您的建议。在生产中,我使用的是 https,但我认为我最终要做的只是从我的后端选择 ID 并将其显示给所有用户以查看,而不是个人信息,这仍应显示在调度程序上。这样做可以吗?
    • 其实我明白了。我刚刚在我的后端修改了我的 select 语句,只选择了仍然显示在调度程序上的 ID,并删除了任何包含个人信息的 select 语句给其他用户。
    • 是的,就是这样。始终知道用户的 ID,并始终使用 WHERE ID = ? 作为您知道的 ID,当您将 REST 端点传递给您的应用程序时。
    猜你喜欢
    • 2021-03-20
    • 1970-01-01
    • 1970-01-01
    • 2016-11-01
    • 1970-01-01
    • 2016-04-06
    • 1970-01-01
    • 1970-01-01
    • 2022-01-06
    相关资源
    最近更新 更多