25、应用程序安全保障全攻略

应用程序安全保障全攻略

1. 配置负载均衡器相关设置

在配置应用程序的过程中,负载均衡器(ELB)的设置至关重要。我们需要进行一些关键的操作来确保应用的正常运行和安全性。
- 启用 HTTPS 监听器 :点击保存后,会返回上一视图,同时能看到端口已打开的确认信息。
- 启用 Cookie 粘性 :由于应用会使用基于会话的 Cookie,而应用在多个实例上运行时,无法保证用户登录后会两次被定向到同一实例,这会导致会话无法正常使用。ELB 的 Stickiness 功能可以将用户会话绑定到特定实例并转发相关 Cookie。具体操作如下:
1. 返回负载均衡器的描述选项卡,找到端口配置设置。
2. 点击端口 443 配置旁边的“编辑”。
3. 在弹出的模态视图中,选择“启用应用程序生成的 Cookie 粘性”。
4. 在“Cookie 名称”字段中输入“connect.sid”,然后点击保存。

以下是操作流程的 mermaid 流程图:

graph LR
    A[返回负载均衡器描述选项卡] --> B[找到端口配置设置]
    B --> C[点击端口 443 配置编辑]
    C --> D[选择启用应用程序生成的 Cookie 粘性]
    D --> E[输入 Cookie 名称 connect.sid]
    E --> F[点击保存]
2. 修改安全组

为了进一步保障应用的安全性,我们需要修改安全组。虽然 HTTPS 连接在客户端与 CloudFront 以及 CloudFront 与负载均衡器之间进行协商,但由于实例不在 VPC 中,仍有可能直接连接到实例。为了防止恶意用户直接连接到实例,我们可以创建自定义安全组。具体步骤如下:
1. 在 EC2 中,选择左侧导航栏中的“安全组”链接,会看到 OpsWorks 自动生成的安全组列表。
2. 在安全组列表中,找到“AWS - OpsWorks - nodejsApp”。
3. 打开上方的“操作”菜单,点击“复制到新组”。
4. 将新安全组命名为“Photoalbums - nodejs - App”,并给出有用的描述,如“用于 Photoalbums 应用的安全组,限制 HTTP 和 HTTPS 连接”,无需选择 VPC。
5. 更改入站 HTTP 和 HTTPS 连接的安全规则:
- 找到 HTTP 行,将“源”更改为“自定义 IP”,在 IP 字段中输入“amazon - elb/amazon - elb - sg”。
- 对 HTTPS 进行相同的更改。
6. 完成后,点击“创建”。

步骤 操作
1 在 EC2 选择安全组链接
2 找到 AWS - OpsWorks - nodejsApp
3 复制到新组
4 命名并描述新安全组
5 更改 HTTP 和 HTTPS 源
6 点击创建
3. 使应用程序栈使用新安全组

创建好自定义安全组后,需要让应用程序栈使用它。具体操作如下:
1. 导航到 OpsWorks 并选择应用程序栈。
2. 由于不能让应用层没有安全组,所以先添加自定义安全组,再移除默认安全组。
3. 从导航菜单中选择“层”,点击应用层旁边的“安全”。
4. 点击右上角的“编辑”按钮。
5. 从下拉列表中选择自定义安全组,然后点击保存。
6. 从导航菜单中进入“栈”,转到“栈设置”并点击“编辑”。
7. 在底部会看到 OpsWorks 安全组的切换开关,将其设置为“否”,然后点击保存。此时,实例将不再使用自动生成的安全组。

以下是使用新安全组的操作流程 mermaid 流程图:

graph LR
    A[导航到 OpsWorks 选择应用程序栈] --> B[添加自定义安全组并移除默认安全组]
    B --> C[选择层并点击应用层安全]
    C --> D[点击编辑按钮]
    D --> E[选择自定义安全组并保存]
    E --> F[进入栈设置并编辑]
    F --> G[设置 OpsWorks 安全组为否并保存]
4. 应用程序安全设置

为了保障应用程序的安全,我们需要实现两个安全模式:存储加密密码和使用安全会话。
- 添加会话和加密模块 :需要向应用程序添加“easycrypto”和“express - session”两个模块。在“package.json”的“dependencies”对象中添加以下内容:

{
  ...
  "debug": "~1.0.4",
  "easycrypto": "0.1.1",
  "express-session": "^1.7.6",
  "jade": "~1.5.0"
}

然后在命令行中,导航到工作目录并输入“npm install”,依赖项将自动安装。
- 添加密码加密 :在“/lib/model/model - user.js”文件中进行密码加密/解密的操作。
- 在文件顶部添加一个名为“encryptionKey”的变量,并设置为任意随机字符串,例如:

var encryptionKey = '80smoviereferencegoeshere';
- 在文件底部添加两个私有方法用于生成哈希密码和解密哈希密码:
function generatePasswordHash(password){
  var easycrypto = require('easycrypto').getInstance();
  var encrypted = easycrypto.encrypt(password, encryptionKey);
  return encrypted;
}

function decryptPasswordHash(passwordHash) {
  var easycrypto = require('easycrypto').getInstance();
  var decryptedPass = easycrypto.decrypt(passwordHash, encryptionKey);
  return decryptedPass;
}
- 替换“createUser”函数:
function createUser(params, callback){
  var newUser = {
    username: params.username,
    password: generatePasswordHash(params.password),
    email: params.email
  }
  var query = 'INSERT INTO users SET ? ';
  connection.query(query, newUser, function(err, rows, fields) {
      if (err) {
      if(err.errno == 1062){
          var error = new Error("This username has already been taken.");
          callback(error);
      } else {
        callback(err);
      }
    } else {
      callback(null, {message:'Registration successful!'});
    }
  });
}
- 替换“loginUser”函数:
function loginUser(params, callback){
  connection.query('SELECT username, password, userID FROM users WHERE username=' + 
connection.escape(params.username), function(err, rows, fields) {
    if(err){
        callback(err);
    } else if(rows.length > 0){
      var decryptedPass = decryptPasswordHash(rows[0].password);
      if(decryptedPass == params.password){
        var response = {
          username: rows[0].username,
          userID: rows[0].userID
        }
        callback(null, response);
      } else {
        var error = new Error("Invalid login");
        callback(error);
      }
    } else {
      var error = new Error("Invalid login");
      callback(error);
    }
  });
}

通过以上步骤,我们可以逐步提升应用程序的安全性,确保用户数据的安全和应用的稳定运行。后续还需要进行安全会话的配置等操作,以进一步完善应用的安全体系。

5. 使用安全会话

使用安全会话的操作会稍微复杂一些,具体取决于应用程序的目标。由于应用的所有路由都包含受限和不受限的 API 端点,因此需要在各个路由级别手动保护应用程序。
- 配置 express - session 中间件 :在“server.js”中,在引入“express”之后立即添加“express - session”中间件,并在其他“app.use”语句之前添加配置:

var express = require('express');
var expressSession = require('express-session');
var path = require('path');

app.use(expressSession({secret: 'ssshhhhh'}));

建议将“secret”的值替换为自己的密钥。
- 设置用户会话标识 :在用户登录时,需要在会话 Cookie 中设置标识用户的值。打开“/routes/users.js”,找到“/login”路由,在“model.loginUser()”的回调中设置会话的“userID”:

router.post('/login', function(req, res) {
  if(req.param('username') && req.param('password') ){
    var params = {
      username: req.param('username').toLowerCase(),
      password: req.param('password')
    };

    model.loginUser(params, function(err, obj){
      if(err){
        res.status(400).send({error: 'Invalid login'});
      } else {
        req.session.userID = obj.userID;
        res.send(obj);
      }
    });
  } else {
    res.status(400).send({error: 'Invalid login'});
  }
});
  • 简化注销路由 :“/logout”路由只需销毁当前会话:
router.post('/logout', function(req, res) {
  if(req.session){
    req.session.destroy();
  }
  res.send({message: 'User logged out successfully'});
});
  • 保护关键路由 :需要让关键路由要求会话 Cookie 而不是 POST 参数。以“/routes/photos.js”为例,对“/upload”和“/delete”路由进行修改:
    • /photos/upload
router.post('/upload', function(req, res) {
  if(req.session && req.session.userID){
    if(req.param('albumID') && req.files.photo){
      var params = {
        userID : req.session.userID,
        albumID : req.param('albumID')
      }
      if(req.param('caption')){
        params.caption = req.param('caption');
      }

      fs.exists(req.files.photo.path, function(exists) {
        if(exists) {
          params.filePath = req.files.photo.path;
          var timestamp = Date.now();
          params.newFilename = params.userID + '/' + params.filePath.replace('tmp/', timestamp);
          uploadPhoto(params, function(err, fileObject){
            if(err){
              res.status(400).send({error: 'Invalid photo data'});
            }
            params.url = fileObject.url;
            delete params.filePath;
            delete params.newFilename;
            model.createPhoto(params, function(err, obj){
              if(err){
                res.status(400).send({error: 'Invalid photo data'});
              } else {
                res.send(obj);
              }
             });
          });
        } else {
          res.status(400).send({error: 'Invalid photo data'});
        }
      });
    } else {
      res.status(400).send({error: 'Invalid photo data'});
    }
  } else {
    res.status(401).send({error: 'You must be logged in to upload photos'});
  }
});
- **/photos/delete**:
router.post('/delete', function(req, res) {
  if(req.session && req.session.userID){
    if(req.param('id')){
      var params = {
        photoID : req.param('id') ,
        userID : req.session.userID
      }
      model.deletePhoto(params, function(err, obj){
        if(err){
          res.status(400).send({error: 'Photo not found'});
        } else {
          res.send(obj);
        }
      });
    } else {
      res.status(400).send({error: 'Invalid photo ID'});
    }
  } else {
    res.status(401).send({error: 'Unauthorized to create album'});
  }
});

同样,在“/routes/albums.js”中,对“/upload”和“/delete”路由进行类似修改:

router.post('/upload', function(req, res) {
  if(req.session && req.session.userID){
    if(req.param('title')){
      var params = {
        userID : req.session.userID,
        title : req.param('title')
      }
      model.createAlbum(params, function(err, obj){
        if(err){
          res.status(400).send({error: 'Invalid album data'});
        } else {
          res.send(obj);
        }
      });
    } else {
      res.status(400).send({error: 'Invalid album data'});
    }
  } else {
    res.status(401).send({error: 'Unauthorized to create album'});
  }
});

router.post('/delete', function(req, res) {
  if(req.session && req.session.userID){
    if(req.param('albumID')){
      var params = {
        albumID : req.param('albumID') ,
        userID : req.session.userID
      }
      model.deleteAlbum(params, function(err, obj){
        if(err){
          res.status(400).send({error: 'Album not found'});
        } else {
          res.send(obj);
        }
      });
    } else {
      res.status(400).send({error: 'Invalid album ID'});
    }
  } else {
    res.status(401).send({error: 'Unauthorized to create album'});
  }
});
  • 修改模型 :需要确保用户只能删除自己的内容。在“/lib/models/model - photos.js”中,替换“deletePhotoByID()”方法:
function deletePhotoByID(params, callback){
  var query = 'UPDATE photos SET published=0 WHERE photoID=' + connection.escape 
(params.photoID)  + ' AND userID=' + params.userID;
  connection.query(query, function(err, rows, fields){
    if(rows.length > 0){
      callback(null, rows);
    } else {
      if(rows.changedRows > 0){
        callback(null, {message: 'Photo deleted successfully'});
      } else {
        var deleteError = new Error('Unable to delete photo');
        callback(deleteError);
      }
    }
  });
}

在“/lib/models/model - albums.js”中,替换“deleteAlbum()”方法:

function deleteAlbum(params, callback){
  var query = 'UPDATE albums SET published=0 WHERE albumID=' + connection.escape 
(params.albumID) + ' AND userID=' + params.userID;
  connection.query(query, function(err, rows, fields){
    if(err){
      callback(err);
    } else {
      if(rows.changedRows > 0){
        callback(null, {message: 'Album deleted successfully'});
      } else {
        var deleteError = new Error('Unable to delete album');
        callback(deleteError);
      }
    }
  });
}
6. 总结与测试

完成以上所有代码更改后,将更改提交到代码仓库,并部署到 OpsWorks 实例,等待部署过程完成。部署完成后即可开始测试。由于之前的用户数据使用的是明文密码,现在应用会尝试解密,会导致密码不匹配,所以测试的最简单方法是注册一个新用户,登录并创建一个相册。可以通过向域名或负载均衡器本身发送 HTTPS POST 请求来成功完成这些操作,而通过 HTTP 尝试登录域名将被拒绝。

通过一系列的配置和代码修改,我们从负载均衡器的设置、安全组的修改,到应用程序内部的密码加密和安全会话的使用,全面地提升了应用程序的安全性。整个过程涵盖了多个关键步骤,以下是整体的操作步骤总结表格:
| 操作类别 | 具体操作 |
| ---- | ---- |
| 负载均衡器设置 | 启用 HTTPS 监听器、启用 Cookie 粘性 |
| 安全组配置 | 创建自定义安全组、修改安全规则、应用新安全组 |
| 应用程序安全 | 添加会话和加密模块、密码加密、使用安全会话 |

以下是整体操作流程的 mermaid 流程图:

graph LR
    A[负载均衡器设置] --> B[安全组配置]
    B --> C[应用程序安全设置]
    C --> D[提交代码更改]
    D --> E[部署到 OpsWorks 实例]
    E --> F[测试应用程序]

通过遵循这些步骤,开发者可以构建一个更加安全、可靠的应用程序,确保用户数据的安全和应用的稳定运行。同时,随着技术的不断发展,需要持续关注新的安全问题和解决方案,以保持应用程序的安全性。

分布式微服务企业级系统是一个基于Spring、SpringMVC、MyBatis和Dubbo等技术的分布式敏捷开发系统架构。该系统采用微服务架构和模块化设计,提供整套公共微服务模块,包括集中权限管理(支持单点登录)、内容管理、支付中心、用户管理(支持第三方登录)、微信平台、存储系统、配置中心、日志分析、任务和通知等功能。系统支持服务治理、监控和追踪,确保高可用性和可扩展性,适用于中小型企业的J2EE企业级开发解决方案。 该系统使用Java作为主要编程语言,结合Spring框架实现依赖注入和事务管理,SpringMVC处理Web请求,MyBatis进行数据持久化操作,Dubbo实现分布式服务调用。架构模式包括微服务架构、分布式系统架构和模块化架构,设计模式应用了单例模式、工厂模式和观察者模式,以提高代码复用性和系统稳定性。 应用场景广泛,可用于企业信息化管理、电子商务平台、社交应用开发等领域,帮助开发者快速构建高效、安全的分布式系统。本资源包含完整的源码和详细论文,适合计算机科学或软件工程专业的毕业设计参考,提供实践案例和技术文档,助力学生和开发者深入理解微服务架构和分布式系统实现。 【版权说明】源码来源于网络,遵循原项目开源协议。付费内容为本人原创论文,包含技术分析和实现思路。仅供学习交流使用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值