使用 nginx 反向代理后面的 keycloak 保护 nodejs 中的路由

Posted

技术标签:

【中文标题】使用 nginx 反向代理后面的 keycloak 保护 nodejs 中的路由【英文标题】:protecting route in nodejs with keycloak behind nginx reverse proxy 【发布时间】:2019-11-26 12:08:38 【问题描述】:

我启动了一个 keycloak 容器:

docker run --name keycloak -p 8080:8080 -e PROXY_ADDRESS_FORWARDING=true -d jboss/keycloak

配置了一个nginx反向代理:

add_header Strict-Transport-Security "max-age=31536000; includeSubdomains;";

server 
    listen 80;
    listen [::]:80;

    server_name 192.168.32.132;

    return 301 https://$server_name$request_uri;


server 
    listen 443 ssl;
    listen [::]:443 ssl;

    server_name 192.168.32.132;

    ssl_certificate     /etc/ssl/private/fullchain.pem;
    ssl_certificate_key /etc/ssl/private/privkey.pem;
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;

    location /login 
        proxy_pass http://127.0.0.1:9080;

        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-Host    $host;
        proxy_set_header    X-Forwarded-Server  $host;
        proxy_set_header    X-Forwarded-Port    $server_port;
        proxy_set_header    X-Forwarded-Proto   $scheme;

        proxy_buffer_size          128k;
        proxy_buffers              4 256k;
        proxy_busy_buffers_size    256k;

    

    location /auth 
        proxy_pass http://localhost:8080;

        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-Host    $host;
        proxy_set_header    X-Forwarded-Server  $host;
        proxy_set_header    X-Forwarded-Port    $server_port;
        proxy_set_header    X-Forwarded-Proto   $scheme;

        proxy_buffer_size          128k;
        proxy_buffers              4 256k;
        proxy_busy_buffers_size    256k;
    

    location /app 
        rewrite ^/app(.*)$ $1 last;
        proxy_pass http://localhost:7080;

        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-Host    $host;
        proxy_set_header    X-Forwarded-Server  $host;
        proxy_set_header    X-Forwarded-Port    $server_port;
        proxy_set_header    X-Forwarded-Proto   $scheme;

        proxy_buffer_size          128k;
        proxy_buffers              4 256k;
        proxy_busy_buffers_size    256k;
        
    

然后我启动了一个 nodeJS 服务器,在用户可以访问应用程序之前,它需要一些自定义逻辑:

// Setup Axios HTTP Request client
AXIOS.defaults.baseURL = CONF.URL;
AXIOS.defaults.headers.common['apikey'] = CONF.apiKey;

// Setup keycloak to use the session memoryStore
var memoryStore = new SESSION.MemoryStore();
var keycloak = new KEYCLOAK( store: memoryStore );

// Set up a server instance and create a session middleware
const APP = EXPRESS();

APP.use(SESSION(
    secret: CRYPTO.randomBytes(512).toString('hex'),
    resave: false,
    saveUninitialized: true,
    store: memoryStore
));


// Default route handler
APP.get('/login', keycloak.protect(), function(req, res) 
    console.log('%s accessing protected route', req.get('X-Forwarded-For'));
    let sessionID = '';
    let tokenID = '';

    // Dispatch requests to APP's REST Endpoints
    AXIOS(
        method:'POST',
        url: 'session',
        headers: 'Accept': 'application/json'
    )
    .then(response => 
        if (response.status == 201) 
            sessionID =  response.data['sessionId'];
            console.log('Received session %s', sessionID);
            return AXIOS(
                method:'POST',
                url: 'session/' + sessionID + '/token',
                headers: 'Accept': 'application/json',
            );
        
        else throw new ServerError(response);
    )
    .then(response => 
        if (response.status == 201) 
            tokenID =  response.data['tokenId'];
            console.log('Received token %s', tokenID);
            res.redirect('/app/html/createCustomer?' +  QUERYSTRING.encode('tokenId': tokenID));
        
    )
    .catch( error => console.log('Error', error.message);
    );
);

APP.use(keycloak.middleware(  logout: '/logout'));

// Start servers
HTTP.createServer(APP).listen(CONF.serverPort, CONF.serverInterface, () =>         
console.log('Server listening on %s:%d', CONF.serverInterface, 
CONF.serverPort));

CONF.serverPort=9080CONF.serverInterface=127.0.0.1 的位置。

keycloak 服务器管理控制台中的 keycloak.json 读取


    "realm": "app-realm",
    "auth-server-url": "https://192.168.32.132/auth",
    "ssl-required": "external",
    "resource": "app-client",
    "public-client": true,
    "confidential-port": 0

在 keycloak 管理控制台上,我添加了一个用户。客户端的重定向 URL 设置为通配符。

当我在路由 /login 访问 VM 的 IP 192.168.32.132 时,我被重定向到 keycloak 登录页面,该页面告诉我更改用户的初始密码。

无论我尝试什么,每次进一步的登录尝试都会导致:ERROR_TOO_MANY_REDIRECTS。

【问题讨论】:

【参考方案1】:

当您尝试访问 /auth 路径下的 keycloak 时,它会将您重定向到 /auth,这会命中反向代理并导致循环。

要解决这个问题,请在您的 ngnix 配置中 /auth 和 proxy_pass 之后添加 /

location /auth/ 
   proxy_pass http://localhost:8080/;
   ...

【讨论】:

以上是关于使用 nginx 反向代理后面的 keycloak 保护 nodejs 中的路由的主要内容,如果未能解决你的问题,请参考以下文章

在反向代理后面使用 Keycloak:无法打开管理员登录页面,因为内容混合

keycloak 无效参数:redirect_uri 在反向代理后面

如何使用反向代理修复 Keycloak 中的“我们很抱歉需要 HTTPS”?

Apache反向代理背后的Keycloak

反向代理背后的密钥斗篷

Keycloak NGINX 反向代理问题