基于企业微信快速搭建后台编辑系统

背景

  针对小、微型企业来说,IT往往只是辅助工具,或许全公司也就那么一个系统开发,当公司业务量上升,碰到人工效率天花板的时候,直接面向客户的业务系统开发又必须是优先于面向内部员工的编辑系统的,所以前期一些数据运营性工作就只能先找现成系统,如:phpMyAdmin, Adminer之类来直接暴露数据库勉强用着了。
  不过这种方式快倒是快,可直接暴露在公网上的东西,并且还是开源的,保不齐哪天一不留神来个授权漏洞公司数据可就尴尬了……
  所以企业微信这个东西吧,虽然以前一直不理解都有微信了干嘛还搞一企业版,现在看来也还是能解决一部分人群现实问题的,至少基于它的OAuth2加道系统访问的闸门,即使访问入口暴露在官网上也是放心多了。

实现

方案说明

  简单来说就是基于cookie一次设置、四处自动携带的特性,然后利用Nginx的http_auth_request_module来将每次请求转发给自实现的授权服务器进行验证,一次来做到授权验证跟后台业务逻辑完全分离的效果。

实现步骤

申请免费的ssl证书

  从上面流程中可以看到,最薄弱的地方就是header中传输的sessionId,如果员工连接不可信网络被第三方截取到了,就能够直接用来访问后台编辑系统,所以首先到sslforfree申请一份免费的SSL证书对传输通道做层加固对安全性有立竿见影的效果。

安装Express Admin

  Express Admin这个基于node实现的通用数据库管理系统相对于其他php实现来说有环境上的绝对优势。

1
2
3
4
5
6
7
8
9
10
11
>npm install -g express-admin
>mkdir -p backend/config
>admin backend/config
Database type: mysql
Database name: test
Database user: root
Database password: 123456
Server port: 8888 # 服务监听端口
Admin user: admin # 设置管理员用户名
Admin password: 123qwER # 设置管理员密码
Express Admin listening on port 8888

授权服务器开发

  关于企业微信OAuth2登录授权的交换逻辑,直接参考官方文档的“网页授权登录”即可。
  在OAuth2登录授权成功后一方面需要生成sessionId保存至redis中以待后续验证,另一方面需要将生成的sessionId设置到页面cookie中

1
2
3
4
5
6
7
8
// 企业微信OAuth2登录
if ( "/backend/login".equals(uri.getPath()) ) {
jedis = rPool.getResource();
resp = staffManager.loginByWorkWeixin(jedis, params.get("code"));
if ( 0 == resp.ret ) { // 设置sessionId到cookie中
result.headers.put("Set-Cookie", "sessionId="+resp.data.getString("sessionId")+"; Path=/");
}
}

  在后续的每次访问中则需要对请求中的cookie有效性进行验证,如果验证不通过则返回403

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// cookie有效性验证
if ( "/backend/auth".equals(uri.getPath()) ) {
jedis = rPool.getResource();
result.status = HttpResponseStatus.FORBIDDEN;
do {
if ( !headers.containsKey("Cookie") ) {
break;
}
String[] cookies = headers.get("Cookie").split(";");
for ( int i=0; i<cookies.length; ++i ) {
String[] pair = cookies[i].trim().split("=");
if ( 2 != pair.length ) {
continue;
}
if ( "sessionId".equals(pair[0]) ) {
resp = staffManager.checkSession(jedis, pair[1].trim());
if ( 0 == resp.ret ) {
result.status = HttpResponseStatus.OK;
} else {
break;
}
}
}
} while(false);
}

实现Oauth2授权后跳转页面

  需要用拿到的企业微信服务器返回code给到授权服务器的登录接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
<!DOCTYPE html>
<html>

<head>
<meta charset="utf-8">
<title>Welcome to backend's World</title>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
</head>

<body>
<div id="app">
<p>{{ message }}</p>
</div>

<script>
new Vue({
el: '#app',
data: {
message: 'waiting...'
},
mounted: function () {
this.login();
},
methods: {
getUrlKey(name){
return decodeURIComponent((new RegExp('[?|&]'+name+'='+'([^&;]+?)(&|#|;|$)').exec(location.href)||[,""])[1].replace(/\+/g,'%20'))||null;
},
login() {
let code = this.getUrlKey('code');
console.log('code=' + code);
axios.get('https://backend.foorbar.com/backend/login?code=' + code)
.then(function (response) {
// 跳转至Express Admin登录页
window.location.href='/login';
}).catch(function (error) {
console.log(error);
}).then(function () {
});
},
}
})
</script>
</body>

</html>

配置nginx

  需要主要的是,权限验证的请求不要连请求body一块转发过去了……

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
error_page  404  /;
error_page 403 /exchange;

location ^~ /index.html {
}

location /exchange {
rewrite ^(.*)$ https://open.weixin.qq.com/connect/oauth2/authorize?appid=xxxx&redirect_uri=https%3a%2f%2fbackend.foorbar.com%2findex.html&response_type=code&scope=snsapi_userinfo&agentid=xxxx&state=backend#wechat_redirect last;
}

location /backend/auth {
proxy_pass http://127.0.0.1:9000;
proxy_pass_request_body off;
proxy_set_header Content-Length "";
proxy_set_header X-Original-URI $request_uri;
}

location /backend/ {
proxy_pass http://127.0.0.1:9000;
proxy_set_header X-Real-Ip $remote_addr;
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-For $remote_addr;
}

location / {
auth_request /backend/auth;
proxy_pass http://127.0.0.1:8888;
}

效果

  这样如果在非企业微信客户端打开的效果如下:

403

  而如果在企业客户端中打开则会静默完成登录授权,直接跳转至Express Admin的登录界面:

200