通过nginx访问keycloak时的Invalid token issuer问题

目录

1.解决问题:

 2.基础知识:

3.使用Nginx后如何在web应用中获取用户ip及原理解释

1.问题背景:

2.解决方案1 :X-real-ip $remote_addr;

3.解决方案2:proxy_set_header  X-Forwarded-For $proxy_add_x_forwarded_for

4.举个例子说明:


1.解决问题:

开发web应用的时候,使用nginx的动静分离,前端代码部署到nginx,利用反向代理(或者负载均衡)访问后端,同样利用反向代理访问keycloak。

前端在发送登录请求的时候,nginx会通过反向代理将请求转发到keycloak server,从而重定向到keycloak登录页面。https://localhost:8443/ 是nginx地址

登录后再获取access token,但此时token中的issuer地址是nginx地址,后端使用这个access token访问keycloak server的时候,会要求issuer地址是keycloak server地址,从而出现Invalid token issuer问题。

{"error": "invalid_token", "error_description": "Token invalid: Invalid token issuer. Expected 'https://10.76.8.130:8843/auth/realms/master', but was 'https://localhost:8443/auth/realms/master'"}

https://10.76.8.130:8843/ 是keycloak server地址, 而https://localhost:8443/是nginx地址,并且access token中的issuer是"https://localhost:8443/auth/realms/master"

需要让token中的issuer变成"https://10.76.8.130:8843/auth/realms/master",怎样解决这个问题呢? 需要通过配置nginx的反向代理。

    server {
        listen 8443 ssl;
        server_name  localhost;
		keepalive_timeout 5;
        ssl_certificate      C:/technology/SoftWare/nginx-1.19.1/lee.crt;
        ssl_certificate_key  C:/technology/SoftWare/nginx-1.19.1/lee.key;
        ssl_session_cache    shared:SSL:1m;
        ssl_session_timeout  5m;
        ssl_ciphers  HIGH:!aNULL:!MD5;
        ssl_prefer_server_ciphers  on;
        root  C:/technology/SoftWare/nginx-1.19.1/html/brucetest/;
        index index.html;
		
	#http://localhost/是静态请求,所有静态请求都由nginx处理
        location /static/ {
	    root C:/technology/SoftWare/nginx-1.19.1/html/brucetest/; #该目录下放html文件和静态资源
	    index index.html;
	    #try_files $uri $uri/ /index.html;
        }

        location /api/ {
            #rewrite /api/* /cctf/api/* break;
            proxy_pass https://10.76.8.130/cctf/api/;
        }
		
	location /auth { # Keycloak相关反向代理配置。 
            sub_filter '10.76.8.130:8843' '$host:443';
            sub_filter_once off;
            proxy_set_header    Host                 "10.76.8.130:8843";
            proxy_pass https://10.76.8.130:8843;
            proxy_redirect  https://10.76.8.130:8843 https://$host/;
	    proxy_redirect off;
        }
    }

配置好nginx后,重新在浏览器中输入nginx地址https://localhost:8443/,登录成功之后,拿到access token信息,解析一下token信息,可以看到access token中的"iss"是keycloak地址https://10.76.8.130:8843/auth/realms/master,问题解决。

 2.基础知识:

X-Forwarded-For和相关几个头部的理解

  • $remote_addr
    是nginx与客户端进行TCP连接过程中,获得的客户端真实地址. Remote Address 无法伪造,因为建立 TCP 连接需要三次握手,如果伪造了源 IP,无法建立 TCP 连接,更不会有后面的 HTTP 请求

  • X-Real-IP
    是一个自定义头。X-Real-Ip 通常被 HTTP 代理用来表示与它产生 TCP 连接的设备 IP,这个设备可能是其他代理,也可能是真正的请求端。需要注意的是,X-Real-Ip 目前并不属于任何标准,代理和 Web 应用之间可以约定用任何自定义头来传递这个信息

  • X-Forwarded-For
    X-Forwarded-For 是一个扩展头。HTTP/1.1(RFC 2616)协议并没有对它的定义,它最开始是由 Squid 这个缓存代理软件引入,用来表示 HTTP 请求端真实 IP,现在已经成为事实上的标准,被各大 HTTP 代理、负载均衡等转发服务广泛使用,并被写入 RFC 7239(Forwarded HTTP Extension)标准之中.

X-Forwarded-For请求头格式非常简单,就这样:

  X-Forwarded-For:client, proxy1, proxy2

可以看到,XFF 的内容由「英文逗号 + 空格」隔开的多个部分组成,最开始的是离服务端最远的设备 IP,然后是每一级代理设备的 IP。

如果一个 HTTP 请求到达服务器之前,经过了三个代理 Proxy1、Proxy2、Proxy3,IP 分别为 IP1、IP2、IP3,用户真实 IP 为 IP0,那么按照 XFF 标准,服务端最终会收到以下信息:

X-Forwarded-For: IP0, IP1, IP2

Proxy3 直连服务器,它会给 XFF 追加 IP2,表示它是在帮 Proxy2 转发请求。列表中并没有 IP3,IP3 可以在服务端通过 $remote_addr字段获取, 我们知道http连接来自 TCP 连接,http协议没有IP的概念,所以remote_address 来自 TCP 连接,表示与服务端建立 TCP 连接的设备 IP,在这个例子里就是 IP3。

详细分析一下,这样的结果是经过这样的流程而形成的:

  • 用户IP0---> 代理Proxy1(IP1),Proxy1记录用户IP0,并将请求转发给Proxy2时,带上一个Http Header:
    X-Forwarded-For: IP0
  • Proxy2收到请求后读取到请求有 X-Forwarded-For: IP0,然后proxy2 继续把连接上来的proxy1 的ip 追加 到 X-Forwarded-For 上面,构造出X-Forwarded-For: IP0, IP1,继续将请求转发给Proxy 3
  • 同理,Proxy3 按照第二步构造出 X-Forwarded-For: IP0, IP1, IP2,转发给真正的服务器,比如nginx,nginx收到了http请求,里面就是 X-Forwarded-For: IP0, IP1, IP2 这样的结果。所以Proxy 3 的IP3,不会出现在这里。
  • nginx 获取proxy3的IP 能通过$remote_addr获取到,因为$remote_addr就是真正建立TCP链接的IP,这个不能伪造,是直接产生连接的IP。$remote_address 无法伪造,因为建立 TCP 连接需要三次握手,如果伪造了源 IP,无法建立 TCP 连接,更不会有后面的 HTTP 请求。

3.使用Nginx后如何在web应用中获取用户ip及原理解释

1.问题背景:

在实际应用中,我们可能需要获取用户的ip地址,比如做异地登陆的判断,或者统计ip访问次数等,通常情况下我们使用request.getRemoteAddr()就可以获取到客户端ip,但是当我们使用了nginx作为反向代理后,使用request.getRemoteAddr()获取到的就一直是nginx服务器的ip的地址,那这时应该怎么办?

2.解决方案1 :X-real-ip $remote_addr;

“经过反向代理后,由于在客户端和web服务器之间增加了中间层,因此web服务器无法直接拿到客户端的ip,通过$remote_addr变量拿到的将是反向代理服务器的ip地址”。这句话的意思是说,当你使用了nginx反向服务器后,在web端使用request.getRemoteAddr()(本质上就是获取$remote_addr),取得的是nginx的地址,即$remote_addr变量中封装的是nginx的地址,当然是没法获得用户的真实ip的,但是,nginx是可以获得用户的真实ip的,也就是说nginx使用$remote_addr变量时获得的是用户的真实ip,如果我们想要在web端获得用户的真实ip,就必须在nginx这里作一个赋值操作,如下:

proxy_set_header    X-real-ip   $remote_addr;

其中这个X-real-ip是一个自定义的变量名,名字可以随意取,这样做完之后,用户的真实ip就被放在X-real-ip这个变量里了,然后,在web端可以这样获取:request.getAttribute("X-real-ip"),这样就明白了吧。

但是这种处理方式是有问题的,当一个请求通过多个代理服务器时,用户的IP将会被代理服务器IP覆盖

  • #在第一个代理服务器中设置

  • set x_real_ip=$remote_addr

  • #最后一个代理服务器中获取

  • $x_real_ip=IP1

3.解决方案2:proxy_set_header  X-Forwarded-For $proxy_add_x_forwarded_for

proxy_set_header    X-real-ip $remote_addr;

有了这句就可以在web服务器端获得客户端的IP(也就是直接与web服务器建立TCP连接的客户端的IP,如果有代理的话,就不是真正的客户端的IP地址,而是代理的IP)。但是,实际上要获得用户的真实ip,不是只有这一个方法,下面我们继续看。

$http_x_forwarded_for :

我们先看看这里有个X-Forwarded-For变量,这是一个squid开发的,用于识别通过HTTP代理或负载平衡器原始IP一个连接到Web服务器的客户机地址的非rfc标准,如果有做X-Forwarded-For设置的话,每次经过proxy转发都会有记录,格式就是client1, proxy1, proxy2,以逗号隔开各个地址,由于他是非rfc标准,所以默认是没有的,需要强制添加,在默认情况下经过proxy转发的请求,在后端看来远程地址$remote_addr都是proxy端的ip 。也就是说在默认情况下我们使用request.getHeader("X-Forwarded-For")获取不到用户的ip,如果我们想要通过这个变量获得用户的ip,我们需要自己在nginx代理(假设用nginx做代理)添加如下配置:

proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for:

意思是增加一个$proxy_add_x_forwarded_for到X-Forwarded-For里去,注意是增加,而不是覆盖,当然由于默认的X-Forwarded-For值是空的,所以我们总感觉X-Forwarded-For的值就等于$proxy_add_x_forwarded_for的值,实际上当你搭建两台nginx在不同的ip上,并且都使用了这段配置,那你会发现在web服务器端通过request.getHeader("X-Forwarded-For")获得的将会是客户端ip和第一台nginx的ip。

$proxy_add_x_forwarded_for变量包含客户端请求头中的"X-Forwarded-For"与$remote_addr两部分,他们之间用逗号分开。(这句话很重要)

举个例子,有一个web应用,在它之前通过了两个nginx转发, 即用户访问该web通过两台nginx。

在第一台nginx中,使用

proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

现在的$proxy_add_x_forwarded_for变量的"X-Forwarded-For"部分是空的,所以只有$remote_addr,而$remote_addr的值是用户的ip(真实的客户端),于是赋值以后,X-Forwarded-For变量的值就是用户的真实的ip地址了。(总结就是获得了客户端的IP)

到了第二台nginx,使用

proxy_set_header  X-Forwarded-For $proxy_add_x_forwarded_for;

现在的$proxy_add_x_forwarded_for变量中的X-Forwarded-For部分包含的是用户的真实ip$remote_addr部分的值是上一台nginx的ip地址,于是通过这个赋值以后现在的X-Forwarded-For的值就变成了“用户的真实ip,第一台nginx的ip”(总结就是第二台获取到了第一台IP和客户端的IP)




4.举个例子说明:

192.168.179.99(代理服务器1)->192.168.179.100(代理服务器2)->192.168.179.101(代理服务器3)->192.168.179.102(102为最后的服务端)

192.168.179.99配置(代理服务器1)
      location / {
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_pass http://192.168.179.100;
      }

192.168.179.100配置(代理服务器2)
      location / {
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_pass http://192.168.179.101;
       }

192.168.179.101配置(代理服务器3)
      location / {
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_pass http://192.168.179.102;
      }

192.168.179.102配置
在日志中设置打印$http_x_forwarded_for,进行观察
log_format  main  '$http_x_forwarded_for|$http_x_real_ip|$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

结果如下:

(1)客户端使用浏览器去访问192.168.179.99服务端192.168.179.102日志如下,可以看到获取到客户端的IP为192.168.179.4
192.168.179.4, 192.168.179.99, 192.168.179.100|-|192.168.179.101 - - [26/Apr/2020:10:57:22 +0800] "GET / HTTP/1.0" 304 0 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.122 Safari/537.36" "192.168.179.4, 192.168.179.99, 192.168.179.100"
 
(2)客户端192.168.179.103去访问192.168.179.99服务端192.168.179.102日志如下,可以看到获取到客户端的IP为192.168.179.103
192.168.179.103, 192.168.179.99, 192.168.179.100|-|192.168.179.101 - - [26/Apr/2020:10:57:32 +0800] "GET / HTTP/1.0" 200 4833 "-" "curl/7.29.0" "192.168.179.103, 192.168.179.99, 192.168.179.100"

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值