vault backup: 2026-11-07 16:33:44
This commit is contained in:
@@ -0,0 +1,218 @@
|
||||
---
|
||||
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。此外还可以看到,不需要鉴权的接口,即使不添加鉴权配置也是可以正常访问的。
|
||||
Reference in New Issue
Block a user