项目场景:
客户私有化部署,多个应用共享同一个身份验证服务。将身份验证逻辑从应用层剥离,由 Nginx 统一处理
使用 auth_request 模块判断License有效期和鉴权函数统一放到了/auth接口里,确保License有效并且在期限内的同时是合法登录的用户。
问题描述
后端返回license状态和过期时间给前端进行展示,即将过期时提示用户剩余的时间和续期优惠
最初构想返回一个特定的HTTP状态码(505)给客户端代表着过期,前端会根据这个status code来做响应的处理。
但是前端反馈没能正确的获取到505,客户端获取到的是500。
server {
listen 80;
server_name example.com;
# 身份验证服务地址
location = /auth {
internal; # 仅允许内部请求
proxy_pass http://auth-service-api.com/validate;
proxy_pass_request_body off; # 不转发请求体
proxy_set_header Content-Length "";
proxy_set_header X-Original-URI $request_uri;
}
location /protected {
auth_request /auth;
auth_request_set $auth_status $upstream_status;
# 验证失败时返回自定义响应
error_page 401 = @error401;
error_page 403 = @error403;
error_page 505 = @error505;
# 验证成功时转发请求
proxy_pass http://backend-service-api.com;
}
# 自定义错误页面
location @error401 {
return 401 "Unauthorized";
}
location @error403 {
return 403 "Forbidden";
}
location @error505 {
return 505 "Expired";
}
}
原因分析:
auth_request 模块是独立的 HTTP 服务,返回 200 表示验证成功,返回 401 或 403 表示验证失败。
只能返回这三个状态码。其中200会通过验证会继续走后续的代理。401和403就跳出到error_page对应的返回。其他状态码客户端获取的都是500。
解决方案:
使用HTTP返回的 Status Code 和 Response Header 组合判断。
由 auth_request 转发的 http://auth-service-api.com/validate 接口,给所有经过校验接口的 Response Header 中都添加以下两个值。
- License-Status是0,1 接口返回200。
– 1的时候前端根据Expiration-Time提醒用户剩余时长。
– 0正常放行。 - License-Status是2,3,4 接口返回403。
– 前端根据不同状态展示不同报错信息并返回登录页。
License-Status: 状态
0 未过期
1 临近过期
2 过期
3 系统时间错误
4 许可证信息异常
Expiration-Time: 过期时间(剩余的小时数转换成天 / 24)
修改后 Nginx 配置如下
server {
listen 80;
server_name example.com;
# 身份验证服务地址
location = /auth {
internal; # 仅允许内部请求
proxy_pass http://auth-service-api.com/validate;
proxy_pass_request_body off; # 不转发请求体
proxy_set_header Content-Length "";
proxy_set_header X-Original-URI $request_uri;
}
location /protected {
auth_request /auth;
auth_request_set $auth_status $upstream_status;
auth_request_set $license_status $upstream_http_license_status;
auth_request_set $expiration_time $upstream_http_expiration_time;
proxy_set_header License-Status $license_status; # 添加到响应头
proxy_set_header Expiration-Time $expiration_time; # 添加到响应头
# 验证失败时返回自定义响应
error_page 401 = @error401;
error_page 403 = @error403;
# 验证成功时转发请求
proxy_pass http://backend-service-api.com;
}
# 自定义错误页面
location @error401 {
return 401 "Unauthorized";
}
location @error403 {
return 403 "Forbidden";
}
}
通过以上方案可以实现鉴权和License过期统一管理。
部署完成之后重新思考了一下,还可以通过以下方式来实现:
- 前端定期轮询来实现(时效性并不强,不过无伤大雅)
- auth_request 提供的 $auth_response_body(效果和现在差不多)