nginx - 将承载令牌身份验证与代理摘要身份验证一起使用的最佳方法是什么?甚至可能吗?
问题描述
我们几乎没有可通过Bearer 令牌身份验证流程访问的 Web API。现在,我们还需要通过基于代理的身份验证来支持这些 API (例如,Basic、Digest、NTLM)。到目前为止,我已经能够测试承载令牌身份验证和基本身份验证,现在我正在探索摘要身份验证。
由于承载令牌流使用授权标头,而代理摘要身份验证在客户端和代理服务器之间的身份验证流中也有授权标头的重叠/冲突使用。我们可以同时实现两种身份验证方案(承载和代理摘要)吗?
在客户端,我在 Java 中使用了 okhttp-digest ( https://github.com/rburgst/okhttp-digest ) 库。在代理服务器上测试 Digest auth 方案时,我尝试了Squid服务器和Nginx/OpenResty服务器与digest_auth模块(https://www.nginx.com/resources/wiki/modules/auth_digest/),但没有任何运气的代理服务器。在 Squid 服务器的情况下,它将从客户端发送的自定义Host标头替换为当前 URL 的主机(从 Web API 端不希望出现)。而在 Nginx/OpenResty 的情况下,它发送401到 okhttp-digest 在代理身份验证的情况下不兼容。
有没有什么特别是 OpenResty/Nginx 中的任何模块可以帮助我从客户端/代理服务器端实现这两种身份验证?
目前,我正在研究如何在后续请求中在客户端成功验证身份时发送承载令牌,但我不确定它是否会工作或在代理服务器端再次发生冲突。
使用 okhttp-digest 库的客户端代码。
import com.burgstaller.okhttp.AuthenticationCacheInterceptor;
import com.burgstaller.okhttp.CachingAuthenticatorDecorator;
import com.burgstaller.okhttp.DispatchingAuthenticator;
import com.burgstaller.okhttp.basic.BasicAuthenticator;
import com.burgstaller.okhttp.digest.CachingAuthenticator;
import com.burgstaller.okhttp.digest.Credentials;
import com.burgstaller.okhttp.digest.DigestAuthenticator;
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.logging.HttpLoggingInterceptor;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class Test {
public static String bearerToken = "Bearer abc";
public static void main(String[] args) throws Exception {
Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("192.168.0.3", 80));
HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
logging.setLevel(HttpLoggingInterceptor.Level.HEADERS);
OkHttpClient.Builder builder = new OkHttpClient.Builder();
final Map<String, CachingAuthenticator> authCache = new ConcurrentHashMap<>();
Credentials credentials = new Credentials("username", "password");
final BasicAuthenticator basicAuthenticator = new BasicAuthenticator(credentials);
final DigestAuthenticator digestAuthenticator = new DigestAuthenticator(credentials);
DispatchingAuthenticator authenticator = new DispatchingAuthenticator.Builder()
.with("digest", digestAuthenticator)
.build();
final OkHttpClient client = builder
.proxy(proxy)
.proxyAuthenticator(new CachingAuthenticatorDecorator(authenticator, authCache))
.addInterceptor(new AuthenticationCacheInterceptor(authCache))
.addInterceptor(logging)
.build();
String url = "http://www.example.com";
Request request = new Request.Builder()
.url(url)
.method("GET", null)
.addHeader("Host", "admin.example.com")
.addHeader("Authorization", bearerToken)
.get()
.build();
Response response = client.newCall(request).execute();
System.out.println(response.body().string());
}
}
OpenResty 中的 Nginx.conf(使用 auth_digest 和其他必需模块构建)。
#user nobody;
worker_processes 1;
#error_log logs/error.log;
#error_log logs/error.log notice;
#error_log logs/error.log info;
#pid logs/nginx.pid;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
resolver 114.114.114.114;
# resolver 8.8.8.8;
log_format main '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'$request_time req_header:"$req_header" req_body:"$request_body" '
'resp_header:"$resp_header" resp_body:"$resp_body"';
lua_need_request_body on;
body_filter_by_lua '
local resp_body = string.sub(ngx.arg[1], 1, 1000)
ngx.ctx.buffered = (ngx.ctx.buffered or "") .. resp_body
if ngx.arg[2] then
ngx.var.resp_body = ngx.ctx.buffered
end
';
header_filter_by_lua '
local h = ngx.req.get_headers()
for k, v in pairs(h) do
ngx.var.req_header = ngx.var.req_header .. k.."="..v.." "
end
local rh = ngx.resp.get_headers()
for k, v in pairs(rh) do
ngx.var.resp_header = ngx.var.resp_header .. k.."="..v.." "
end
';
access_log logs/access.log main;
sendfile on;
#tcp_nopush on;
#keepalive_timeout 0;
keepalive_timeout 65;
#gzip on;
server {
listen 80;
server_name localhost;
set $resp_body "";
set $req_header "";
set $resp_header "";
auth_digest_user_file /etc/proxy/users_digest;
auth_digest 'realm';
proxy_connect;
proxy_connect_allow 443;
proxy_connect_connect_timeout 10s;
proxy_connect_read_timeout 10s;
proxy_connect_send_timeout 10s;
location / {
add_header X-DEBUG-MSG-1 "$uri";
add_header X-DEBUG-MSG-2 "$http_authorization";
proxy_set_header Host $host;
proxy_set_header Authorization $http_authorization;
proxy_pass_header Host;
proxy_pass_header Authorization;
proxy_pass $scheme://$host$request_uri;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
server {
listen 443 ssl;
server_name localhost;
set $resp_body "";
set $req_header "";
set $resp_header "";
auth_digest_user_file /etc/proxy/users_digest;
auth_digest 'realm';
proxy_connect;
proxy_connect_allow 443;
proxy_connect_connect_timeout 10s;
proxy_connect_read_timeout 10s;
proxy_connect_send_timeout 10s;
ssl_certificate /etc/ssl/certs/nginx-selfsigned.crt;
ssl_certificate_key /etc/ssl/private/nginx-selfsigned.key;
ssl_session_cache shared:SSL:1m;
ssl_session_timeout 5m;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
location / {
add_header X-DEBUG-MSG-1 "$uri";
add_header X-DEBUG-MSG-2 "$http_authorization";
proxy_set_header Host $host;
proxy_set_header Authorization $http_authorization;
proxy_pass_header Host;
proxy_pass_header Authorization;
proxy_pass $scheme://$host$request_uri;
}
}
}
PS我认为我不能将Nginx与digest_auth模块一起使用,因为它总是返回401(使用WWW-Authenticate响应标头而不是Proxy-Authenticate响应标头),因为它期望授权请求标头中的摘要身份验证信息而不是Proxy-Authorization请求标头。因此,为了测试 Digest auth 方案,我需要一个转发代理服务器(类似于Squid但不是Squid 本身,因为它与我的用例中不期望的Host请求标头混淆了),它执行代理身份验证而不是服务器身份验证。
解决方案
推荐阅读
- jquery - 页面在 jQuery 中轻微滚动时跳转
- bash - 在 Jenkins 中如何使用带有交叉编译环境的 Docker 镜像
- android - 安卓导航。从嵌套图片段弹出到外部片段
- graphql - 从自省选项卡 graphql 获取有关查询的信息
- javascript - 在 for 循环中使用 @ref 获取元素引用
- c++ - Windows 10 夜灯是如何实现的?
- neo4j - 在 Neo4j 中,查询以检索短于某个长度的子链
- java - 无法在 Spring 中为集成测试配置模块 - 没有可用的任务
- bash - 使用 bash 将路由器配置部分转换为 csv 文件
- jinja2 - 在 Flask 的页面上多次使用相同的 WTForms 字段