从CloudFront获取客户端真实IP地址、并使用CloudFront内置的IP地址库获取访问者所在国家

本文更新于2024年3月。

一、背景

CloudFront CDN服务是七层代理服务,接受最终用户的客户端连接和访问,并终结客户端的网络访问通道。然后,CloudFront会通过互联网回源站获取要分发的内容。此时,在后端的源站是无法获知客户端的真实IP地址的,源站只能看到CloudFront的公网IP地址。这样对于源站应用而言,不能准确的判断来访者所在地区和国家,无法实行不同国家的运营策略。本文介绍在CloudFront背后的源站如何能获取客户端的真实IP地址、以及访问者所在国家。

实现源站获取真实地址和国家的方式是,在CloudFront上配置Origin Request策略,要求CloudFront向源站发送请求时候,添加而外的Header来提供客户端的信息。这两个Header分别是 HTTP_CLOUDFRONT_VIEWER_ADDRESS代表客户端IP地址,以及CloudFront-Viewer-Country代表客户端所在国家。

此处需要注意,CloudFront给出的IP地址对应的国家信息是由CloudFront内置的IP地址和国家数据库进行映射转换的,准确度是很高的,可以适应大部分场景。但是,这没有绝对的承诺,1是因为用户可能使用VPN和代理服务器,以隐藏其真实所在地,这是IP地址可能会显示为VPN服务器和代理服务器所在地;2是因为全球IP地址的分配在变化中,各网络运营商都会定期更新IP地址,当然CloudFront服务内置的IP地址库是第三方的MaxMind提供的,IP地址库也会更新,但不能承诺没有时延数据一直100%准确。所以,如果您的场景特别依赖国家区分政策,还可以考虑开启CloudFront内置国家信息的情况下,再额外调用第三方IP地址库例如等来进行交叉对比,以确保访问策略的正确。当然这些第三方IP地址库您可能需要付费才能订阅他们的服务。

二、CloudFront配置

1、配置CloudFront的源站请求策略

进入CloudFront界面,在左侧找到Policy策略的按钮,然后进入Origin request标签页,新建一个源站请求规则。如下截图。

在新建的页面,输入名称client-ip-and-country,备注也输入这个名字,然后向下滚动屏幕。如下截图。

在下方几个配置中,分别配置如下:

  • Headers选项,选择All viewer headers and the following CloudFront headers,表示除了所有客户端请求的header之外,再额外追加CloudFront自己的Header。他们包括客户端IP地址CloudFront-Viewer-Address、以及客户端国家和区域信息CloudFront-Viewer-CountryCloudFront-Viewer-Country-NameCloudFront-Viewer-Country-RegionCloudFront-Viewer-Country-Region-Name等几个标签。
  • Query strings选项,选择All表示转发所有请求字符串。
  • Cookies选项,选择All表示转发所有请求Cookie。

如下截图。

2、修改现有发布点的策略

本文假设之前已经配置好了一个发布点,源站是ALB,或者是一台EC2,且工作正常。现在修改现有发布点的策略。

进入CloudFront的发布点界面,点击Behaviors行为标签页,找到源站选中之,然后点击编辑,如下截图。

向下滚动页面,来到Cache key and origin requests位置,选择Cache policy and origin request policy (recommended),然后在Origin request policy - optional下拉框中,选择上一步配置的源站请求策略client-ip-and-country。然后点击保存。如下截图。

至此CloudFront配置完成,响应的Header已经转发给源站。

三、在源站应用上识别CloudFront发来的Header

上文配置的CloudFront会将客户端IP地址和国家打入到HTTP Header中,任何一种开发语言都可以编写代码获取HTTP Header。本文以一个简单的PHP环境为例。

1、在创建EC2时候使用Userdata安装PHP环境并创建测试页

在EC2控制台上创建一个EC2,类型为ARM64架构,机型为t4g.micro,操作系统为Amazon Linux 2023。在创建向导中,点击高级设置,输入如下userdata脚本:

#! /bin/bash
yum update -y
yum install httpd php8.2 -y
echo "<?php print_r(getallheaders()); ?>" > /var/www/html/index.php
usermod -a -G apache ec2-user
chown -R apache:apache /var/www
chmod 2775 /var/www
find /var/www -type d -exec chmod 2775 {} \;
find /var/www -type f -exec chmod 0664 {} \;
systemctl start httpd
systemctl enable httpd
systemctl start php-fpm
systemctl enable php-fpm
reboot

如下截图。

在以上这段userdata中安装了Apache和PHP环境。等待EC2创建完毕后,创建如下如下测试文件index.php,并保存到/var/www/html目录下。其内容如下:

<?php
  print_r(getallheaders());
?>

这段代码将打印源站接收到CloudFront发来的所有HTTP Header。

本文以PHP语言为例,其他语言请相应替换程序,或者您可以咨询ChatGPT、Claude大模型,由大模型为您提供其他语言的代码示例。

2、确认本EC2是CloudFront源站(或CloudFront经过ALB分发到EC2亦可)

本文测试的方法,对于从CloudFront直接为EC2加速场景(源站是EC2),以及从CloudFront为ALB加速且ALB背后是EC2的场景均使用。

请确认CloudFront发布了正确的资源,即源站配置正确。

3、访问测试

然后使用浏览器通过CloudFront的网址访问刚才创建的index.php程序。如下截图。

也可以使用curl在命令行下访问。如下截图。

其中可以看到的客户端的IP地址、端口和国家信息:

[HTTP_CLOUDFRONT_VIEWER_ADDRESS] => 13.248.48.14:22199
[HTTP_CLOUDFRONT_VIEWER_COUNTRY] => HK

由此表示源站通过CloudFront添加的Header,正常的获取到了客户端真实IP和访问者所在国家。

如果您对国家和地区缩写代号有疑问,请参考CloudFront使用的标识国家和地区的ISO 3166-1 alpha-2标准

4、关于访问者是AWS网络将不输出特定Header的说明

以上配置中,我们还要求CloudFront的Origin Request源站请求包含更多信息,包括CloudFront-Viewer-Country-NameCloudFront-Viewer-Country-RegionCloudFront-Viewer-Country-Region-Name等。为什么在上述测试中没有打印出来?

这是由于,当模拟最终客户端的访问者来自AWS网络(意思是Amazon所拥有的IP地址)时候,这些信息将不会被打印。关于哪些字段信息不被打印,可查阅本文末尾的说明。

例如,源站是在东京的一台EC2,通过CloudFront全球发布。为了测试CloudFront打印IP地址和国家的Header这个功能,使用了新加坡的一台EC2模拟这个请求,这时候访问者的IP地址是AWS所属新加坡区域的IP。当这种情况下,CloudFront的Header将仅包含IP地址和国家标签,将不会提供省份、地址坐标(经度、维度)、邮编等。

当客户端的IP地址不属于Amazon时候,这些字段将被正常打印出来。如下截图。

四、参考文档

CloudFront对客户端IP地址的Header的支持:

https://aws.amazon.com/cn/about-aws/whats-new/2021/10/amazon-cloudfront-client-ip-address-connection-port-header/

AWS CloudFront关于转发Header的解释:

https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/using-cloudfront-headers.html#cloudfront-headers-viewer-location

如果是使用CloudFront Function,也可以参考这个函数:

https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/example-function-add-true-client-ip-header.html

CloudFront使用MaxMind GeoIP databases来判定IP地址

https://docs.aws.amazon.com/cloudfront/latest/APIReference/API_GeoRestriction.html#API_GeoRestriction_Contents