使用Nginx来保护第三方API

最近App通过Exchange Api接入Exchange邮箱,客户提出了一个需求,就是不希望Exchange Api能到处被访问,而限制只有我们的App才能访问Exchange Api

大BOSS把这个需求交给我调研,笔者通过调研,发现使用Nginx代理+第三方授权来实现这个需求是最合适的,特把此方案贴上来分享

1 文档目标

本文档描述的方案为解决Exchange服务器安全问题,而提出的技术方案

2 客户需求

当前Exchange邮箱服务的客户端实现方式是:

  • 客户端通过Exchange提供的EWS API直连,以访问并进行邮件的各种操作

客户认为此种方式非常不安全,原因在于其员工可以通过WorkPlus来访问邮件,同样在知晓配置后,通过其它各种方式来访问邮件

由于客户在邮件上做了水印等安全操作,客户希望能限制用户只在WorkPlus中访问邮件,禁止通过其它方式来访问邮件

3 解决方案提议

初步建议有以下三种方式来解决客户的安全性问题

  1. 通过VPN方式来限制只能在WorkPlus客户端访问华三邮箱
  2. 通过在Exchange API服务端做出一些授权限制
  3. 考虑使用nginx代理转发并授权控制

4 解决方案初步建议

考虑到第1种方式用户体验较差

第2种方式可能性不大,要修改Exchange相关逻辑;

个人建议使用第三种方式来实现此目标

5 问题与调研

第三种方案的需要解决的痛点在于两个

  1. 解决将请求进行代理转发的实现
  2. 解决授权问题

5.1 解决转发问题

其中,第1点比较好解决,使用nginx的反向代理就能解决此问题

参考配置如下

1
2
3
4
5
6
7
8
location /api {
proxy_pass https://mail.***.com/EWS/Exchange.asmx;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Authorization $http_authorization;
}

根据上述nginx配置,我们会设置一台nginx代理服务器,并将其/api的请求转发到Exchange Api的服务器;由于Exchange API是通过Base Authorization来做认证,因此我们在转发时会把Authorization也代理转发过去

5.2 解决授权问题

nginx有一个ngx_http_auth_request_module的模块,官网为: 访问

这个模块的功能就是进行第三方认证

我的想法就是,客户端在请求代理服务器时,将access_token或ticket等参数一同请求到代理服务器,代理服务器再通过这个模块,拿到这些参数进行第三方认证; 认证通过则转发,不通过则拒绝;

由于第三方客户端无法获取WorkPlus的相关token或ticket,由此第三方难以得到授权并访问exchange服务器

ngx_http_auth_request_module安装

略,非重点

ngx_http_auth_request_module配置

1
2
3
4
5
6
7
8
9
10
11
12
13
# 开启第三方授权
auth_request /auth;

location /auth {
## http://192.168.50.245:8080/auth 为第三方授权服务地址
proxy_pass http://192.168.50.245:8080/auth;
proxy_pass_request_body off;
proxy_set_header Content-Length "";
proxy_set_header X-Original-URI $request_uri;
proxy_set_header Connection "";
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}

如上述配置所述,我编写了一个简单的授权服务,逻辑是

  • 当Header中的name参数为”AAA”时,通过授权 (返回2XX状态开头的,nginx均理解为OK)
  • 当Header中的name参数非”AAA”时,拒绝授权 (返回401,403等表示授权失败的,nginx理解为Fail)

代码所示如下

1
2
3
4
5
6
7
8
9
10
11
12
@RestController
public class AuthResource {

@RequestMapping("/auth")
public ResponseEntity auth(@RequestHeader("name")
String name) {
if (name.equals("AAA")){
return ResponseEntity.ok(name);
}
return ResponseEntity.status(401).build();
}
}

6 结论

笔者已经在本地搭建了一套类似的服务,证明上述方案是合理有效的