Files
Knowledge/1 使用/使用NGINX的auth_request进行统一jwt鉴权 - 侯锐的思考与分享.md
T

218 lines
5.6 KiB
Markdown

---
title: "使用NGINX的auth_request进行统一jwt鉴权 - 侯锐的思考与分享"
source: "https://www.nosuchfield.com/2025/07/24/Using-NGINX-auth_request-for-unified-JWT-authentication/"
author:
- "[[Raymond]]"
published:
created: 2025-10-30
description: "<p>NGINX的<code>auth_request</code>模块提供了一种统一的认证机制,可以在NGINX层面进行JWT鉴权,而不需要在每个后端服务中重复实现认证逻辑。</p><p>首先我们定义一下nginx的配置,它的配置如下</p><figure class=\"highlight plaintext\"><figcaption><span>flat</span></figcaption><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"li"
tags:
- "clippings"
---
NGINX的 `auth_request` 模块提供了一种统一的认证机制,可以在NGINX层面进行JWT鉴权,而不需要在每个后端服务中重复实现认证逻辑。
首先我们定义一下nginx的配置,它的配置如下
```
flatserver {
listen 8965;
# 鉴权接口,仅供 Nginx 内部 auth_request 使用
location = /auth {
internal; # 该接口只能被 Nginx 内部请求,防止外部访问
# 转发到实际的认证服务
proxy_pass http://localhost:5001/api/auth/verify;
# 不转发请求体,提升效率
proxy_pass_request_body off;
# 防止后端因 Content-Length 不确定而报错
proxy_set_header Content-Length "";
# 将客户端传来的 Authorization 头(JWT Token)传给认证服务
proxy_set_header Authorization $http_authorization;
# 传入原始请求路径,供认证服务判断路径是否需要鉴权
proxy_set_header X-Original-URI $request_uri;
}
# 所有 / 路径下的请求都进行认证
location / {
# 认证请求会先调用上面的 /auth 接口
auth_request /auth;
# 如果认证失败(如返回 401),跳转到自定义处理逻辑
error_page 401 = @unauthorized;
# 从 /auth 的响应头中提取用户信息
auth_request_set $user_id $upstream_http_x_user_id;
# 把用户信息注入到请求头中,转发给后端业务服务
proxy_set_header X-User-ID $user_id;
# 转发到后端服务
proxy_pass http://localhost:5001;
}
# 自定义未授权响应(认证失败时返回)
location @unauthorized {
# 返回 401 状态码 + 文本内容
return 401 "Unauthorized";
}
}
```
有了这个nginx的配置之后,我们就可以实现鉴权的逻辑了,具体逻辑如下
```python
flatimport jwt
from flask import Flask, request, make_response, jsonify
from jwt import InvalidTokenError
app = Flask(__name__)
SECRET_KEY = "a-string-secret-at-least-256-bits-long"
# 路径白名单,不需要鉴权的接口
PUBLIC_PATHS = [
"/api/public/hello",
"/api/login",
"/api/register"
]
def verify_token():
original_uri = request.headers.get("X-Original-URI", "")
# 不需要鉴权的接口,直接返回200
if original_uri in PUBLIC_PATHS:
return "", 200
# 否则继续验证 JWT
auth_header = request.headers.get("Authorization")
if not auth_header or not auth_header.startswith("Bearer "):
return "Missing or invalid Authorization header", 401
# 解析得到token
token = auth_header.split(" ", 1)[1]
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
response = make_response("", 200)
response.headers["X-User-ID"] = str(payload.get("user_id", ""))
return response
except InvalidTokenError:
return "Invalid token", 401
def hello():
return jsonify({"message": "hello from auth", "user_id": request.headers.get("X-User-ID")})
def ping():
return {"msg": "hello without auth"}
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5001)
```
启动nginx和如上python服务,之后我们使用如下payload和header以及密钥 [生成token](https://www.jwt.io/)
payload
```
{
"alg": "HS256",
"typ": "JWT"
}
```
header
```
{
"user_id": 656670838050885
}
```
密钥
```
a-string-secret-at-least-256-bits-long
```
生成得到token
```
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjo2NTY2NzA4MzgwNTA4ODV9.caQ6cp-BA-OMxXu4zTUjV0OiZo1iygvdi7GPQNjNVHM
```
之后我们就可以使用token进行测试了,具体测试结果如下
```
~ AUTH_HEADER="Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjo2NTY2NzA4MzgwNTA4ODV9.caQ6cp-BA-OMxXu4zTUjV0OiZo1iygvdi7GPQNjNVHM"
~ curl -H "$AUTH_HEADER" http://localhost:8965/api/hello
{"message":"hello from auth","user_id":"656670838050885"}
~ curl -H "$AUTH_HEADER" http://localhost:8965/api/public/hello
{"msg":"hello without auth"}
~ curl -H "$AUTH_HEADER" http://localhost:8965/api/login
<!doctype html>
<html lang=en>
<title>404 Not Found</title>
<h1>Not Found</h1>
<p>The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.</p>
~ curl http://localhost:8965/api/public/hello
{"msg":"hello without auth"}
~ curl http://localhost:8965/api/hello
Unauthorized%
```
如上我们正确设置了Authorization之后就可以正常访问需要鉴权的接口了,但是去掉了Authorization之后需要鉴权的接口就会返回Unauthorized。此外还可以看到,不需要鉴权的接口,即使不添加鉴权配置也是可以正常访问的。