PSY简介
“心灵有约”是一款基于微信企业号的Web应用
,用于在校师生进行心理咨询的在线预约和受理,详见:心理有约-使用手册。系统已通过学校网络中心的安全评估和性能测试,于五月份在校企业号上线。截止目前已有3000+师生注册使用。
系统已申请一项软件著作权。
技术架构简述
系统采用前后端分离模式进行开发。
前端使用的是基于Bootstrap
的开源UI模板AdminLTE的V3
版本,配合其它前端工具;
后端框架为开源接口框架PhalApi,好处是可以快速且规范地写业务并发布接口,主要借助此框架完成业务处理的分层和接口封装。
由于后端框架API层只负责接口发布,于是自行结合签名认证
和PHP-NoCSRF自行造了一个接口验证的轮子改良NoCSRF实现对PHP后端接口的安全验证。
身份认证基于企业微信进行开发,可以实现外来身份识别和校内用户自动注册。
运行于CentOS
上的LAMP
。
身份认证
身份认证主要由身份控制脚本和用户登录、注册接口组成。踩过无数坑之后造的轮子。
流程描述为:访问页面 - 获取微信用户身份 - 判断用户身份 - 查询、插入数据库 - 返回用户信息 - 重定向到访问页面
用户身份控制脚本封装为了Header类,只能由统一入口进行调用,且用户只能通过统一入口访问应用,通过URL后缀参数定向到不同页面。这样设置的原因:
- 将用户身份验证模块剥离出来,业务界面专门负责内容渲染,而统一入口配合Header脚本管控用户身份。
- 微信授权中
code
的获取,需要通过页面访问来实现,而回调的也是一串带有很多参数的URL,这些参数会带来很多意想不到的bug,产生的bug也是驱动“设置统一入口”的原因。设置统一入口后,身份验证成功后便重定向到业务页面。
<?php
/**
* 唯一入口
* Created by PhpStorm.
* User: Aris
* Date: 2019-03-03
* Time: 21:41
*/
require_once"../vendor/autoload.php";
use App\Common\Header;
// http://xxxxx/psy/pages/index.php?href=main
if(isset($_GET['href'])){
$href = $_GET['href'];
}else{
$href = 'submit';
}
$t_url='http://'.$_SERVER['HTTP_HOST'].'/psy/pages/index?href='.$href;
$header = new Header();
$header->index($t_url);
header("location:$href");
Header.php
<?php
/**
* Created by PhpStorm.
* User: Aris
* Date: 2018/12/19
* Time: 上午8:38
*/
namespace App\Common;
use PhalApi\CUrl;
class Header{
/**
* 开启session,获取用户信息
* @param $t_url
*/
public function index($t_url){
session_start();
if(!isset($_SESSION["adminPsy"]) || $_SESSION["adminPsy"] != true){
$respond = $this->getArray($t_url);
$_SESSION = $respond;
}
}
/**
* 获取用户数组
* @param $t_url
* @return mixed
*/
public function getArray($t_url){
//获取code
if(!isset($_GET['code'])) {
$redirect_uri = urlencode($t_url);//页面地址
echo $t_url;
//拼接地址
$url = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=xxxxxxxxxxxxx&redirect_uri=".$redirect_uri."&response_type=code&scope=snsapi_userinfo&state=1#wechat_redirect";
//重新访问
header("location:".$url);
exit;
}else {
$code = $_GET['code'];
}
// echo "code = ".$code."</br>";
$respond = '';
//带code请求登陆接口
$post_url = 'http://'.$_SERVER['HTTP_HOST'].'/psy/public/?s=Mobile_Login/Index';
try{
$curl = new CUrl(2);
$rs = $curl->post($post_url, array( 'code' => $code ), 3000);
$respond = json_decode($rs, true);
} catch (\PhalApi_Exception_InternalServerError $ex){}
return $respond['data'];
}
}
微信开发
这一部分按照微信官方给出的授权流程进行操作,使用PhalApi封装的CURL请求类PhalApi\CUrl进行接口的请求。主要流程如下:
流程和普通微信公众号开发一样,只是将一些参数换了名称,比如OpenID
在这里叫做UserID
。其余的一些配置信息需要从应用管理员控制台获取到。
达到的效果:组织内用户点开,后台调用接口进行注册,直接使用。组织外用户拒绝访问、毕业学生拒绝访问、微信以外渠道不能访问。
每个模块流程为:检验参数合法 - 拼接URL - 调用$curl->get($URL, 3000);
<?php
/**
* 获取用户信息
* @param $access_token
* @param $UserID
* @return array|bool|string
*/
protected function getUserInfo($access_token, $UserID){
$curl = new CUrl();
$userInfoURL = 'https://qyapi.weixin.qq.com/cgi-bin/user/get?access_token='.$access_token.'&userid='.$UserID;
if (isset($UserID)) {
$UserInfo = $curl->get($userInfoURL, 3000);
$UserInfo= (array)json_decode($UserInfo, true);
return $UserInfo;
}
return false;
}
接口安全
系统后端所有功能都以接口的形式发布,所有的方法都只能通过调用接口来实现。所以接口的安全性是重中之重。
最开始直接使用的签名验证
。后面接触到了防范CSRF
攻击,它是借助csrf_token
来实现,但是原方法似乎只能对“一个页面访问一个接口”起到保护作用,于是进行改良。再后来就想着能否将二者结合起来,将这个token
也用于验签过程中的随机字符串,于是就又开始了造轮子改良NoCSRF实现对PHP后端接口的安全验证。大致流程如下:
其它安全
- 从防护角度浅谈服务器安全和网站安全
- 表单传输的隐私数据,通过RSA进行了加密防止抓包(有https更好):以RSA加密传输密钥(JS-PHP)
- 框架的数据库是由
NotORM
接管,过滤了SQL注入 - 数据库内隐私字段进行了加密存储
- 设置了HTTP-Only,防止XSS
###模板布局
这里是参考ThinkPHP以及其它带有视图渲染的框架的做法,进行了代码精简和统一性控制。
通过将前端页面的公共部分进行提取(比如手机页面的引用的css、菜单栏等),封装为header.php
、footer.php
等,然后在每个业务页面的头部和尾部直接include
,中间部分就直接写content
部分了。这样一来直接更改header.php
中的菜单栏,即可对所有页面生效。
<?php include 'common/header.php';?>
<div class="content">
<!-- ///// -->
</div>
<?php include 'common/footer.php'?>
业务处理
框架本身分层分得十分好,模型层、业务层、接口层,分别处理份内的事情。数据库由NotORM接管,写起来也十分舒服,不过只限于一些简单的操作,复杂的查询或其它业务语句,则要写原生的,不过要按照官方文档的要求写SQL避免被注入。
这一篇其实是众多文档之一,还有很多写给用户的、写给后面做运维同学的、测试文档等,给后面的同学交接本系统应该不会很悲剧(注释还是写得比较多的)。。
优化
- 优化了静态资源的CDN
- 给常查询的字段添加了索引
- 修改了所有
select *
,改为了需要的字段 null
默认值改为了0- Apache改成了Nginx,确实快了一些
后期计划
- 尝试使用Redis进行数据缓存(试过用Redis存储session)
- 尝试使用Docker部署
- Golang复现