🌎 Web | XMatrix OJ 开发日记

之前花了两天弄好了网页的大概框架,现在又花了一个星期的时间完成了后端的代码,才知道做出一个有功能的网站是需要顾及很多东西的,排版、交互、安全、前后端。

外观

XMatrix 使用了Bootstrap框架,这个框架排版起来很方便,而且在移动端的显示效果也能很好的适配,在各种交互操作上用了jQuery(用于动态加载导航栏)、 CodeMirror(代码输入框)。

后端

由于 XMatrix 现在是部署在一个低配服务器上,那么考虑到性能问题,我们使用了 MySQL 作为了网站的数据库。然后用nodejs 以及 Express 作为后端服务,用于传输用户数据和代码数据。现在 nodejs 的代码都是挤在一块的,用 nginx 的把/api/这个目录统一转发到 nodejs 监听的端口来实现我们的功能,但是一旦功能多起来就会显得异常的乱,所以我打算下一步就把这些端口分离开(数据库操作,前端交互操作,评测操作…)通过内部的 post 请求来实现各个模块的通讯。

安全

在构建网站的用户系统的时候,我考虑最多的就是用户系统的安全性问题。

我了解到实现用户认证的方法一般是生成一个 session 分别保存在 cookies 和服务器数据库,用户通过发送 session 来获取服务器里对应的用户状态和数据,但是这个方法需要占用大量的服务器内存和对数据库的操作,于是我看了 JWT 的一些方法,写出了自己的一套用户认证系统

用户认证系统

实际数据:

1
2
3
4
5
6
7
8
9
10
11
12
JSON{

"userID":10000,

"token":666666,//(一个随机数)[每次涉及到用户权限和数据库的操作都会更新一次]

"lastDate":"2017-04-28 17:50:00"//[数据:登陆过期时间]

}


"sign":'17a1b9fcaca894d6770fbcf0ca4421e5c740da63'//(以上三个信息 + 密钥字符串 生成的SHA1)

cookie:
userSession:
1e1ed93e21e0fd7778238eb6f71ae918812c9f806caa631c55af91d4429c812e7c89c5356a60f14f90e59811d253b89966963c594403db318105c57d9191c25e52f7b766a8d34ebf8a1253ea059391d20793963e16e820497f9ff7bdccdfc4045c0968a527614c22f42dc1f165ed4b3b
(服务端用密钥加密的字符串)[数据:userID + token + 登陆过期时间]
sign:17a1b9fcaca894d6770fbcf0ca4421e5c740da63(userSession + 密钥字符串 生成的 SHA1)

密钥字符串【储存在服务器】

每次涉及到用户权限的操作:

把 userSession 和 sign 附在 post 数据里面发给服务器(后期用 httponly 代替)

服务器:

  1. 没有认证信息, 直接返回 failed.
  2. 把 userSession + 密钥字符串 连起来生成 SHA1:
    1. sign 不一致:那么返回 failed,
    2. sign 一致: 继续进行
  3. 用密钥解密 session:
    1. 验证时间是否在 3 小时之内: 返回 failed (PS: 每次登陆生成一个当前时间的 session 给用户)
    2. 时间没有过期: 生成一个当前时间的 session 给用户,更新 json 对象里面的 session
  4. 验证 token 与 user 数据库里面的是否一致:
    1. 不一致: 生成一个随机数,更新数据库中的 token,然后返回 failed
    2. 一致: 生成一个随机数,更新数据库中的 token,然后继续执行
  5. 使用 userID 进行数据库操作,然后用 生成加密的 userSession + 密钥字符串 生成的 SHA1 对 sesssion 进行签名, 最后返回新的 json 对象给用户。

这个用户认证系统的作用之一是防止非法请求

比如 post /api/submit 是可以提交代码的 API,那么如果有人恶意地调用这个 API 向服务器提交代码就有可能导致服务器不堪重负,那么我在 post 请求里面添加了 userSession 和 sign,只有通过附加这两个参数才可以验证用户身份才允许提交代码。

那么是不是只要获得这两个参数就可以无限提交了呢?

答案肯定是否定的,首先 userSession 这个加密过的数据含有用户 id 以及这一次操作的 token,如果你进行了一次的提交,那么 token 就会发生改变,这两个数据就变得完全没有用了,所以说 userSession 是一个一次性的数据。虽然成功提交之后,会返回一个新的 userSession,但是我可以在服务端对某些 id 进行监控,一旦发现有非正常的提交或者短时间内提交次数过多可以对这个账号进行短时间的禁用。

其二,防止异地同时登陆

这一个措施也同时在保护用户账号的安全,一般的用户系统是通过 cookie 来储存用户的登陆状态,那么如果有人获得了某个账户的 cookies,那么就可以登陆账号进行操作(题外话:几年前 QQ 的快速登陆的 token 是以 get 形式传递的,可以被截取,导致只要你获得了这个 token,就可以通过快速登陆接口进入这个账号的 QQ 空间,邮箱,账户安全中心等网页)。但是我这个用户认证系统中的 token 是每次操作都会改变的,虽然增加了服务器的负担,但是如果你的账号被别人登陆的话,那么存在你这边的 cookies 中的 userSession 就会失效而退出登陆,一来保证了服务端操作的逻辑性,二来提醒你你的账号被异地登陆。

如果是密码泄露了那么就需要更改密码了,如果是 userSession 泄露了,那么只需要重新登陆那么对方那边的 userSession 就会失效。

其三,验证用户身份

userSession 中储存有用户的 ID,那么我只需要根据用户的 ID 获取他的权限那么就知道那些操作是可以进行那些是不被允许的了。

其四,长期没有操作将需要重新登陆

userSession 中储存有签名验证的时间,如果用户长时间没有操作,那么我将会判断用户是离开了电脑,那么他的那个 userSession 就会自动失效,防止被他人利用。

后期我会加入验证码,IP 地址检测等安全措施

除了用户认证系统外,我还对密码的储存做了一些加密措施。

在前端先服务器提起 post 请求时候,用户的密码就会进行一次 SHA1 的加密,提交到后端的只是一串 SHA1,那么就可以防止后端人员获取到用户的密码而进行撞库攻击,

然后在密码存储到数据段之前,又会进行一次 SHA1 的加密,这个的作用就是防止数据库脱库之后被人登陆账号,或对同样是 SHA1 储存密码的网站进行撞库攻击。因为你只是获得了密码的两次 SHA1 的结果,并不知道密码以及一次 SHA1 后的密码是什么。如果缺少了这个步骤,那么别人就可以获得 SHA1 一次加密后的密码,那么就通过直接 POST 请求提交 SHA1 一次后的结果登陆账号。

有报道说 SHA1 已经并不安全了,后期会考虑更换加密算法。

防止中间人攻击

全程使用了https协议对数据的通讯进行了加密

防止XSS 攻击

首先,我们关系到数据库以及重要的操作都是交给服务端处理的,这一来就可以防止了绝大多数的 js 注入攻击,然后我一开始对用户提交的数据的过滤检测是放在网页上的,但是这也暴露了一个问题,别人可以通过直接 POST 请求注入 js,那么我就把对数据的过滤放到了服务端。

后记

现在网页的用户系统也具有了基本的登陆注册功能了,接下来的工作可能要等期中考试过了再来继续做了

todo:(更新:现已基本完成,进入下一阶段的开发)

  1. 修改 session 和 sign 为 httponly(提高安全性,防止 js 访问 cookie)[需要重构后端用户认证系统]
  2. 完成用户中心的服务端 API
  3. 利用 nodejs 的 express 返回 html(动态渲染 html)3.1 动态返回问题列表页面(need 问题数据库)3.2 动态返回问题详情页面(need 问题数据库)
  4. 上线评测模块
  5. 构建问题数据库
  6. 增加验证码系统(注册账号)
  7. 发送验证邮件添加 60s 时间间隔,并且每天最多 3 次
  8. 重新设计成绩回应界面
  9. 设计管理员页面(控制面板)并完成后端
  10. 实现后端模块化,各种模块分离开来
  11. 实现找回密码功能

源代码: GIthub

土豪通道
0%