我目前正在开发一个 Symfony Web 应用程序,为此我有一个测试和生产 nginx 服务器。我目前正在开发的 API 应该使用不记名令牌进行授权。
我的生产服务器位于 https://api.mydomain.de,而我的测试环境位于 https://dev.api.mydomain.de
目前我已经设置了如下基本身份验证系统:
location / {
try_files $uri /index.php$is_args$args;
auth_basic "Restricted Access";
auth_basic_user_file /etc/nginx/passwd/dev_passwd.txt;
}
进入开发服务器时,我们需要授权标头为
Authorization: Basic <token>
但是当测试我的用户系统时,我意识到当我的应用程序也尝试使用标头进行身份验证时,这不会像我想象的那样工作。
这意味着我要么使用应用程序的有效登录凭据请求服务器,但 nginx 不批准我的请求,或者反之亦然。
我对如何解决这个问题有一些想法,但不确定如何实现它们,甚至不知道选择哪一个:
auth_request
我考虑使用 auth_request 指令通过向 /user/validate-token/{token} 等端点发出子请求来验证承载令牌。我遇到了 if 语句中不允许使用 auth_request 块的问题,并且不得不处理无法修复的 502 错误。
这是我到目前为止所得到的:
location / {
try_files $uri /index.php$is_args$args;
# Check if the Authorization header contains a Bearer token
if ($http_authorization ~* ^Bearer\s+(.*)) {
set $bearer_token $1;
# Use an internal location to validate the token
auth_request /internal/token-validation;
}
auth_basic "Restricted Access";
auth_basic_user_file /etc/nginx/passwd/dev_passwd.txt;
}
location = /internal/token-validation {
internal;
proxy_pass http://localhost/user/validate-token/$bearer_token;
proxy_pass_request_body off;
proxy_set_header Content-Length "";
}
# Maybe remove auth_basic here ?
location /user/validate-token {
try_files $uri /index.php$is_args$args;
auth_basic "Restricted Access";
auth_basic_user_file /etc/nginx/passwd/dev_passwd.txt;
}
error_page 401 = @error_401;
location @error_401 {
return 401 "Unauthorized";
}
我还考虑过创建单独的内部位置来处理基本身份验证和承载令牌验证。这将使我能够保持逻辑清晰和独立,但我遇到了类似的问题。另外,我的应用程序将所有请求 URL 识别为
/basic_auth
或 /validate_bearer_token
,因为请求在内部被重写。
这是我所拥有的:
location /basic_auth {
auth_basic "Restricted Access";
auth_basic_user_file /etc/nginx/passwd/dev_passwd.txt;
rewrite ^/basic_auth(/.*)$ $1 break;
try_files $uri $uri/ /index.php?$query_string;
}
location ~ ^/validate_bearer_token/(.*) {
internal; # Internal only, not accessible from outside
set $auth_token $1;
# Pass the request to the validation endpoint
proxy_pass http://127.0.0.1/user/validate-token/$auth_token;
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 X-Forwarded-Proto $scheme;
# Intercept errors to handle token validation response
proxy_intercept_errors on;
error_page 401 = @error_401;
# I couldn't use 200 for some reason
error_page 399 = @auth_success;
}
# [...]
我还考虑重命名应用程序中的授权标头,以避免基本身份验证和不记名令牌身份验证之间的冲突。然而,我不确定这是否是处理这个问题的最佳方法,而且我也不知道该怎么做,因为我将 Symfony 与 LexikJWTBundle 一起使用,它需要标准的授权标头格式。我不知道这是否会使集成变得复杂并可能导致进一步的问题?还可能吗?
我想到的另一种方法是使用一种密码标头,它代替授权基本标头,并由我的 Symfony 应用程序(而不是 nginx 本身)检查。不过,我也可以看到这如何导致问题,特别是在 https://dev.mydomain.de 上测试我的前端时,但我也愿意接受这个解决方案。
我很想知道,这些解决方案中哪一种是客观上最好使用的,或者是否有我错过的东西?
如 LexikJWTAuthenticationBundle 文档所述:
默认仅启用授权标头模式:
Authorization: Bearer {token}
文档以启用查询字符串参数模式或更改标头值前缀。1-configuration-reference
在前面提到的配置参考中,我们看到以下配置示例:
# config/packages/lexik_jwt_authentication.yaml lexik_jwt_authentication: ... # token extraction settings token_extractors: # look for a token as Authorization Header authorization_header: enabled: true prefix: Bearer name: Authorization # check token in a cookie cookie: enabled: false name: BEARER # check token in query string parameter query_parameter: enabled: false name: bearer # check token in a cookie split_cookie: enabled: false cookies: - jwt_hp - jwt_s
这意味着您可以调整令牌提取设置以使用不同的令牌检查方法或替代的自定义 HTTP 标头,例如
X-Bearer-Auth
。此外,您可以在将请求传递到 API 服务器时将此自定义 HTTP 标头替换为 Authorization 标头值:
server {
...
location / {
try_files $uri /index.php$is_args$args;
auth_basic "Restricted Access";
auth_basic_user_file /etc/nginx/passwd/dev_passwd.txt;
}
location ~ \.php$ {
include fastcgi_params;
# overwrite `Authorization` header for PHP-FPM engine
# with the custom `X-Bearer-Auth` header value
fastcgi_param HTTP_AUTHORIZATION $http_x_bearer_auth;
fastcgi_param SCRIPT_FILENAME $document_root$uri;
fastcgi_pass /path/to/php-fpm.sock;
}
...
(注意:原始
Authorization
标头值仍会出现在 FastCGI 请求负载中;但是,只有第二个被覆盖的值会出现在 PHP $_SERVER['HTTP_AUTHORIZATION']
array 中。)
您还可以使用
Authorization
和 Basic
方法有条件地检查 Bearer
标头是否为 auth_basic
或 auth_request
类型。即使您与服务器的初始协商通过基本身份验证进行保护,也可以使用 Authorization: Bearer ...
:以编程方式将
XMLHttpRequest()
标头添加到 AJAX 请求中
xhr.setRequestHeader("Authorization", "Bearer " + token);
或
fetch()
:
fetch(url, {
method: "POST",
headers: {
"Authorization", "Bearer " + token,
...
},
...
将阻止浏览器在请求中包含额外的
Authorization: Basic ...
标头(这是有益的,因为如果存在多个 HTTP 400 Bad Request
标头,nginx 会响应 Authorization
错误)。您最初选择的方法不正确。 nginx 中的 if
指令在 location
上下文中指定时,与常规编程语言中典型的 if
语句不同(如果您有兴趣,可以阅读 this 答案以了解原因。 )但是,您可以使用两个 map
块以及以下配置:
map $http_authorization $auth_realm {
~^Bearer off;
default "Restricted Access";
}
map $http_authorization $auth_basic {
~^Basic 1;
# default is empty string
}
server {
...
location / {
# auth_basic is off in case we have an "Authorization: Bearer ..." header
auth_basic $auth_realm;
auth_basic_user_file /etc/nginx/passwd/dev_passwd.txt;
auth_request /internal/validate/;
try_files $uri /index.php$is_args$args;
}
location /internal/validate/ {
internal;
# skip further processing in case we have an "Authorization: Basic ..." header
if ($auth_basic) { return 200; }
include fastcgi_params;
# the following script validate the "Authorization: Bearer ..." header
# (accessible in PHP script via $_SERVER['HTTP_AUTHORIZATION'])
# script shoud return an HTTP 2xx status code on success or 401/403 otherwise
fastcgi_param SCRIPT_FILENAME $document_root/relative/path/to/validator.php;
fastcgi_pass /path/to/php-fpm.sock;
}
...
您甚至可以定义一个单独的 Symfony 路由来进行令牌验证,例如
/verify_token
。要使用此路由进行验证请求,请在将请求传递到 Symfony 控制器之前重新定义 REQUEST_URI
FastCGI 参数,修改上一示例中的内部位置,如下所示:
location /internal/validate/ {
internal;
# skip further processing in case we have an "Authorization: Basic ..." header
if ($auth_basic) { return 200; }
include fastcgi_params;
# overwrite the route seen by Symfony
fastcgi_param REQUEST_UTI "/validate_token";
# pass the request to Symfony's main controller
fastcgi_param SCRIPT_FILENAME $document_root/index.php;
fastcgi_pass /path/to/php-fpm.sock;
}