问题
你是否有过这种需求,我已经部署好了一个站点,这个站点的代码我无法修改(或者修改成本很高),比如是三方提供的容器化部署
,这个站点现在是没有权限认证的,任何人都可以登录使用,那我们如何保护这站点呢?
一个解决思路
既然这个站点代码不能控制,那我们只能控制/修改我们能控制的地方,那就是部署。
我们是使用Nginx
来部署这个站点,那么一个可能的思路是:在访问这个站点A之前,跳转到另一个站点B进行授权,成功之后,再跳转回来。
那具体如何操作呢?
我们需要知道两个知识点:
- 1、Nginx中的auth_request模块
- 2、二级域名共享cookie
Nginx中的auth_request模块
Nginx
的 auth_request
模块提供了一个强大的功能,它允许你在处理主请求之前向另一个上游服务器发送子请求来验证用户的访问权限。这通常用于验证请求是否应该被允许访问受保护的资源。
这个模块不会处理认证本身,而是将认证决策委托给一个外部服务器
。如果这个外部服务器返回成功的响应(如 HTTP 200 状态码),则 Nginx 会继续处理主请求。如果返回失败的响应(如 HTTP 401 或 403 状态码),则 Nginx 会中断主请求的处理并返回相应的错误码。
如何使用auth_request模块
这里是一个简单的使用 auth_request
模块的例子:
-
1、启用
auth_request
模块:首先,确保Nginx
编译时包含了ngx_http_auth_request_module
。大多数预编译的 Nginx 包都已经包含了这个模块。 -
2、配置认证服务器:你需要设置一个外部服务器(或 Nginx 的另一个位置块),它将处理认证逻辑。
location = /auth {
# 这里是认证服务器的配置
proxy_pass http://my_auth_backend;
# 可以传递原始请求头信息到认证服务器
proxy_set_header Original-URI $request_uri;
}
- 3、在需要保护的位置使用 auth_request:然后,在你的 Nginx 配置中,你可以在需要保护的位置使用 auth_request 指令指向上面配置的认证服务器。
location /protected {
# 指定子请求的位置
auth_request /auth;
# 可以设置子请求返回非200/204响应时的错误处理页面
auth_request_set $auth_status $upstream_status;
error_page 401 = @error401;
# 这里是受保护资源的配置
proxy_pass http://my_backend;
}
location @error401 {
return 401 "Unauthorized";
}
在这个例子中,当用户尝试访问 /protected
时,Nginx 会先向 /auth
发送一个子请求。如果 /auth
返回 HTTP 200
状态码,Nginx 将继续代理请求到 my_backend
。如果 /auth
返回 HTTP 401
或 403
状态码,用户将看到一个 "Unauthorized"
的错误消息。
共享cookie
上面只是介绍auth_request,但是并不符合我们的要求,即不修改代码。如果不做更多操作,我们需要使用另外一个技术要点:cookie共享
如果你有两个二级域名 sub1.example.com 和 sub2.example.com,你可以设置一个 Cookie 的域为 .example.com(注意域名前的点),这样两个二级域名都可以访问这个 Cookie。
在设置 Cookie 时,你需要在响应头中包含 Set-Cookie,并指定 Domain 属性。比如:
Set-Cookie: name=value; Domain=.example.com; Path=/;
现在,任何以 .example.com
结尾的域名都可以访问这个名为 name
的 Cookie
。
具体例子
有了前面的2个知识点,现在来做个具体的例子。
假设我有一个网站 site.fandou.com
,使用nginx
部署,配置如下:
server {
listen 0.0.0.0:80;
server_name site.fandou.com;
client_max_body_size 1G;
location / {
proxy_pass http://0.0.0.0:3000/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
现在我需要对它做保护,那么我弄一个子域名:auth.fandou.com
,用它来做鉴权验证。
server {
listen 0.0.0.0:80;
server_name auth.fandou.com;
client_max_body_size 1G;
location / {
# Use IPv4 upstream address instead of DNS name to avoid attempts by nginx to use IPv6 DNS lookup
proxy_pass http://0.0.0.0:5000/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
如何做鉴权呢?
修改site.fandou.com
的部署配置:
server {
listen 0.0.0.0:80;
server_name site.fandou.com;
client_max_body_size 1G;
location / {
auth_request /auth;
# Use IPv4 upstream address instead of DNS name to avoid attempts by nginx to use IPv6 DNS lookup
proxy_pass http://0.0.0.0:3000/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
location = /auth {
internal;
#proxy_pass http:/auth.fandou.com/auth;
proxy_pass http://127.0.0.1:5000/auth;
proxy_pass_request_body off;
proxy_set_header Cookie $http_cookie;
proxy_set_header Content-Length "";
proxy_set_header X-Original-URI $request_uri;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
error_page 401 = @error401;
location @error401 {
return 302 http://auth.fandou.com/login;
}
}
上面的配置有以下几个改动:
1、配置了auth_request
到 /auth
2、/auth
转到了我们的auth服务,我们使用internal
来指定这是内部请求,因此直接使用本地的auth路径:http://127.0.0.1:5000/auth
3、一旦/auth
校验错误,返回401页面,而这个我们又重定向到我们的登录页面了http://auth.fandou.com/login
当我们试图进入site.fandou.com
时,Nginx会校验auth_request,auth.fandou.com/auth
发现没有对应登录的Cookie,就给返回401,又重定向到auth.fandou.com/login
页面,用户登录后,设置cookie成功后,再次进入site.fandou.com
时,Nginx再次校验auth_request,然后这个时候发现有了对应Cookie(login=1),那么就正常登录了。
这里给一个auth.fandou.com
的简单demo:
from flask import Flask, request, make_response, jsonify
app = Flask(__name__)
# 假设的用户名和密码
USER_NAME = 'admin'
PASSWORD = 'fandou123'
@app.route('/auth')
def auth():
# 检查 Cookie 是否存在且值为 '1'
if request.cookies.get('login') == '1':
return '', 200 # 状态码 200 表示认证成功
else:
return '', 401 # 状态码 401 表示认证失败
@app.route('/login', methods=['POST'])
def login():
# 获取表单数据
user_name = request.form.get('user_name')
password = request.form.get('password')
# 简单的用户名和密码校验
if user_name == USER_NAME and password == PASSWORD:
# 创建响应对象
response = make_response(jsonify({"message": "登录成功"}))
# 设置 Cookie
response.set_cookie('login', '1', domain='.fandou.com', path='/', httponly=True)
return response
else:
# 登录失败
return jsonify({"message": "用户名或密码错误"}), 401
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)
更企业级的做法
上面的登录校验是一个比较简单的例子,更好的方法是使用三方登录来进行企业资产保护,比如我们需要公司的某个Group的人全部可以访问,并且快速接入Google/Github等授权,这个时候的示例图大致如下:
总结
如果遇到了一个无法修改代码,但是要保护的站点,可以通过新增一个同级的子域名来保护他,配合Nginx的auth_request
模块,校验的时候使用Cookie共享
即可。在实际应用中,最好使用合理的三方登录来更方便/安全的进行授权。
评论区