概述
Node.js授权微信公众号服务器访问本地(验证微信公众号IP地址)
1 概述
最近公司使用node.js进行微信公众号服务器端开发,到了接收公众号发送的事件请求并回复的模块,需要验证微信服务器地址进而增加安全性。微信公众号也为我们提供了获取IP地址列表或者IP网段信息的接口。具体文档信息如下:
如果公众号基于安全等考虑,需要获知微信服务器的IP地址列表,以便进行相关限制,可以通过该接口获得微信服务器IP地址列表或者IP网段信息。
接口调用请求说明:
请求方式:
https://api.weixin.qq.com/cgi-bin/getcallbackip?access_token=ACCESS_TOKEN
参数说明:
access_token:公众号的access_token
返回说明:
正常情况下,微信会返回下述JSON数据包给公众号:
{
"ip_list": ["127.0.0.1",
"127.0.0.2",
"101.226.103.0/25"]
}
参数说明:
ip_list:微信服务器IP地址列表
错误时微信会返回错误码等信息,JSON数据包示例如下(该示例为AppID无效错误):
{"errcode":40013,"errmsg":"invalid appid"}
根据微信接口得知要获取IP地址列表就需要先获取ACCESS_TOKEN(如何获取这里不做讲述,请见微信公众平台API文档关于如何获取access_token部分)。然后发送get请求到微信服务器,获取IP地址列表。
2 处理返回的IP地址列表
微信返回的IP地址列表如下:
{ ip_list:
[ '101.226.62.77', '101.226.62.78', '101.226.62.79', '101.226.62.80', '101.226.62.81', '101.226.62.82', '101.226.62.83', '101.226.62.84', '101.226.62.85', '101.226.62.86', '101.226.103.59', '101.226.103.60', '101.226.103.61', '101.226.103.62', '101.226.103.63', '101.226.103.69', '101.226.103.70', '101.226.103.71', '101.226.103.72', '101.226.103.73', '140.207.54.73', '140.207.54.74', '140.207.54.75', '140.207.54.76', '140.207.54.77', '140.207.54.78', '140.207.54.79', '140.207.54.80', '182.254.11.203', '182.254.11.202', '182.254.11.201', '182.254.11.200', '182.254.11.199', '182.254.11.198', '59.37.97.100', '59.37.97.101', '59.37.97.102', '59.37.97.103', '59.37.97.104', '59.37.97.105', '59.37.97.106', '59.37.97.107', '59.37.97.108', '59.37.97.109', '59.37.97.110', '59.37.97.111', '59.37.97.112', '59.37.97.113', '59.37.97.114', '59.37.97.115', '59.37.97.116', '59.37.97.117', '59.37.97.118', '112.90.78.158', '112.90.78.159', '112.90.78.160', '112.90.78.161', '112.90.78.162', '112.90.78.163', '112.90.78.164', '112.90.78.165', '112.90.78.166', '112.90.78.167', '140.207.54.19', '140.207.54.76', '140.207.54.77', '140.207.54.78', '140.207.54.79', '140.207.54.80', '180.163.15.149', '180.163.15.151', '180.163.15.152', '180.163.15.153', '180.163.15.154', '180.163.15.155', '180.163.15.156', '180.163.15.157', '180.163.15.158', '180.163.15.159', '180.163.15.160', '180.163.15.161', '180.163.15.162', '180.163.15.163', '180.163.15.164', '180.163.15.165', '180.163.15.166', '180.163.15.167', '180.163.15.168', '180.163.15.169', '180.163.15.170', '101.226.103.0/25', '101.226.233.128/25', '58.247.206.128/25', '182.254.86.128/25', '103.7.30.21', '103.7.30.64/26', '58.251.80.32/27', '183.3.234.32/27', '121.51.130.64/27' ] }
获取到IP地址列表后进行比对就行了,那么如何获取请求的真实IP地址呢?
2.1 获取请求的真实IP地址
首先我们使用方法util.inspect(req.headers)打印出请求的头端所有信息如下:
{
'user-agent': 'Mozilla/4.0',
accept: '*/*',
host: 'www.xxxxxx.cn',
pragma: 'no-cache',
'content-length': '346',
'content-type': 'text/xml',
'x-real-ip': '103.7.30.68',
'x-forwarded-for': '103.7.30.68, 218.145.64.217',
proxyagent: 'oray phfw 22057'
}
其中” x-real-ip”属性就是真实的请求IP了,“x-forwarded-for”代理服务器IP,其他不做概述。使用req.header('x-real-ip')或者req.headers['x-real-ip']都可以获取到IP地址的字符串。
那么这时就用获取到的请求IP地址,和微信给我们提供的IP地址列表,进行逐一比对不就可以了么?答案是否定的。我们获取到的IP地址是:103.7.30.68然而微信返回给我们的IP地址列表里没有这个IP,但是有一个挺像的IP:103.7.30.64/26。它们有什么关系么?肯定有关系,下面我就来阐述它们的关系和如何判断。
细心的朋友可能会发现,微信为我们提供的IP地址分成了两部分,一部分是正常的IP(101.226.62.77),另外一部分是带有掩码的IP地址(103.7.30.64/26)。
2.2 什么是带掩码的IP地址
对于网络IP地址的划分非常熟悉的朋友请略过这一部分,我也是刚刚学习完成,做一下笔记。
2.2.1 IP地址
IP地址是由32位(bit)的二进制数字组成。那么划分的时候就是从032~232个主机(43个亿)。由于运算过于复杂所以划分成了4个小节,使用“.”作为区分。这样便于计算。
IP地址我们一般划分为A、B、C、D、E,5类。
类别 | 十进制 | 二进制 |
| ||
起始 | 结束 | 起始 | 结束 | 解释 | |
主类 | 0.0.0.1 | 0.255.255.254 | 00000000. 00000000. 00000000. 00000001 | 00000000.11111111.11111111.11111110 | 用来区分分类的IP地址 |
A类 | 1.0.0.1 | 126.255.255.254 | 00000001. 00000000. 00000000. 00000001 | 01111110. 11111111. 11111111. 11111110 | A类IP地址主要用于政府机关 |
A类 | 127.0.0.1 | 127.255.255.254 | 01111111. 00000000. 00000000. 00000001 | 01111111. 11111111. 11111111. 11111110 | 特殊的A类,为操作系统保留,又叫做环回地址。 |
B类 | 128.0.0.1 | 191.255.255.254 | 10000000. 00000000. 00000000. 00000001 | 10111111. 11111111. 11111111. 11111110 | B类作为大型企业使用 |
C类 | 192.0.0.1 | 223.255.255.254 | 11000000. 00000000. 00000000. 00000001 | 11011111. 11111111. 11111111. 11111110 | C类为个人用户使用 |
D类 | 224.0.0.1 | 239.255.255.254 | 11100000. 00000000. 00000000. 00000001 | 11101111. 11111111. 11111111. 11111110 | D类用于组播,路由协议 |
E类 | 240.0.0.1 | 255.255.255.254 | 11110000. 00000000. 00000000. 00000001 | 11111111. 11111111. 11111111. 11111110 | E类用于科研 |
2.2.2 子网掩码
什么是子网掩码?子网掩码又可以叫做子网掩盖码,从字面的意思来理解就是,用于区分哪些网络是子网的,只要没被遮住的部分组合起来就是一个网络。
百度的解释是子网掩码(subnet mask)又叫网络掩码、地址掩码、子网络遮罩,它是一种用来指明一个IP地址的哪些位标识的是主机所在的子网,以及哪些位标识的是主机的位掩码。子网掩码不能单独存在,它必须结合IP地址一起使用。子网掩码只有一个作用,就是将某个IP地址划分成网络地址和主机地址两部分。子网掩码的1必须是连续的。例如:
IP地址: 192. 168. 0. 0
子网掩码: 255. 255. 255. 0
那么它的IP地址网络位是:192.168.0.0
主机位是:1~254(0和255预留广播位)
准换成2进制:
IP地址: 11000000. 10101000. 00000000. 00000000 192.168.0.0
子网掩码: 11111111. 11111111. 11111111. 00000000 255.255.255.0
那么红色的是网络部分,黑色的是主机部分。这样,区分后的黑色部分IP地址就属于一个段内,就可以互相访问了。我们一般这样表示:192.168.0.1/24。
24代表了:有多少个1将IP地址掩盖住,盖住的部分属于一个网段。
那么如果换成192.168.0.1/25呢?
例如一(分成2个网段)192.168.0.1/25就是有25个1进行掩盖。:
IP地址: 11000000.10101000.00000000.00000001 192.168.0.1
子网掩码: 11111111.11111111.11111111.10000000 255.255.255.128
这样的IP地址和子网掩码代表了以10000000位分界线进行分割,将192.168.0.0段分成了2段网络。
从 11000000.10101000.00000000.00000001(192.168.0.1)到
11000000.10101000.00000000.01111111(192.168.0.127)属于一个网段。
从 11000000.10101000.00000000.10000000(192.168.0.128)到
11000000.10101000.00000000.11111110(192.168.0.254)属于另外一个网段。
一个段的两头不能使用,被系统占据作为广播。也就是说192.168.0.1~192.168.0.126属于一个网段,可以正常使用。192.168.0.129~192.168.0.254属于一个网段,能够正常使用。
例如二(分成4个网段)192.168.0.1/26就是有26个1进行掩盖:
IP地址: 11000000.10101000.00000000.00000001 192.168.0.1
子网掩码: 11111111.11111111.11111111.11000000 255.255.255.192
这样的子网掩码把最后一个分段遮住了2位,代表了以11000000为分界线进行分割,将192.168.0.0段分成了4段网络。
从 11000000.10101000.00000000.00000001(192.168.0.1)到
11000000.10101000.00000000.00111111(192.168.0.63)
从 11000000.10101000.00000000.01000000(192.168.0.64)到
11000000.10101000.00000000.01111111(192.168.0.127)
从 11000000.10101000.00000000.10000000(192.168.0.128)到
11000000.10101000.00000000.10111111(192.168.0.191)
从 11000000.10101000.00000000.11000000(192.168.0.192)到
11000000.10101000.00000000.11111110(192.168.0.254)
一个网络的两头不能使用,被系统占据,所以这个例子中能使用的网络地址有:
192.168.0.1~192.168.0.62;
192.168.0.65~192.168.0.126;
192.168.0.129~192.168.0.190;
192.168.0.193~192.168.0.254。
从以上两个例子中不难看出子网掩码的作用。现在我们回到微信给我们带子网掩码的IP地址意义何在?
2.3 分析微信返回带掩码的IP地址
好了,我们知道了微信返回给我们的IP地址为什么后面要跟一个/26了,那么就把刚才的请求拿出来进行分析。
微信给我们返回回来的地址列表里是这样的:103.7.30.64/26。使用刚才的方法进行拆分得出IP地址。
IP地址: 01100111.00000111.00011110.01000000 103.7.30.64
子网掩码: 11111111.11111111.11111111.11000000 255.255.255.192
从 01100111.00000111.00011110.00000000 (103.7.30.0)到
01100111.00000111.00011110.00111111 (103.7.30.63)
从 01100111.00000111.00011110.01000000 (103.7.30.64)到
01100111.00000111.00011110.01111111 (103.7.30.127)
从 01100111.00000111.00011110.10000000 (103.7.30.128)到
01100111.00000111.00011110.10111111 (103.7.30.191)
从 01100111.00000111.00011110.11000000 (103.7.30.192)到
01100111.00000111.00011110.11111111 (103.7.30.255)
所以能够使用的网络有:
103.7.30.1~103.7.30.62
103.7.30.65~103.7.30.126
103.7.30.129~103.7.30.190
103.7.30.193~103.7.30.254
那么微信发给我们的地址是103.7.30.64/26代表的是一个网络,只要是103.7.30.65~103.7.30.126都是微信的服务器IP地址。
得到结果请求IP为103.7.30.68属于103.7.30.65~103.7.30.126范畴之中,所以请求是微信服务器发送过来的。
3 代码的实现
上面说了那么多都是理论上的东西,实际体现到代码上与刚才阐述的理论还有些不同,具体如何实现请看下面:
3.1 得到微信服务器和请求地址的与运算
刚才已经看到了IP地址列表为:103.7.30.64/26。将他转换为2进制:
IP地址: 01100111.00000111.00011110.01000000 103.7.30.64/26
子网掩码: 11111111.11111111.11111111.11000000 255.255.255.192
将它进行“与”运算(“与”运算又叫AND运算,1与1得1,其他情况都的0):
01100111.00000111.00011110.01000000 103.7.30.64/26
与运算 11111111.11111111.11111111.11000000 255.255.255.192
01100111.00000111.00011110.01000000 103.7.30.64
请求的真实IP地址是:103.7.30.68那么它的子网掩码也是26。
01100111.00000111.00011110.01000100 103.7.30.68/26
与运算 11111111.11111111.11111111.11000000 255.255.255.192
01100111.00000111.00011110.01000000 103.7.30.64
它们两个结果是相等的,那么请求就是来自于微信服务器。
3.2 代码的实现
3.2.1 Node.js的实现
/**
* 用于判断发送的请求是否来自于微信服务器。
* 这个方法主要是验证微信服务器带子网掩码的IP地址
* 判断目标ip是否属于某个ip子网
* subIp: 带有子网掩码的ip
* ip:目标ip
*/
exports.isSubnet = function(subIp, ip) {
let subs = subIp.split("/");
let subnetMask = getSubnetMask(parseInt(subs[1]));
let ipArray = getIpv4Array(subs[0]);
let subnet = getSubnet(subnetMask, ipArray);
let ipArray2 = getIpv4Array(ip);
let subnet2 = getSubnet(subnetMask, ipArray2);
if (subnet == subnet2)
return true;
return false;
};
/**
* 通过子网掩码数组和ip地址数组获取主机地址
*/
function getSubnet(masks, ipArray) {
let subnet = "";
if (masks.length != ipArray.length) {
console.log("子网掩码长度和ip地址长度不一样");
}
for (let i =0; i < ipArray.length; i++) {
let and = masks[i] & ipArray[i];
subnet = subnet + and;
if (i < ipArray.length - 1)
subnet = subnet + ".";
}
return subnet;
};
/**
* 获取子网掩码数组
*/
function getSubnetMask(num) {
let masks = new Array(4);
let index = parseInt(num / 8);
let remainder = num % 8;
let remainderToBinary = "";
for (let i =0; i < index; i++)
masks[i] = 255;
for (let i =0; i < 8; i++) {
let j = 0;
if (i < remainder)
j = 1;
remainderToBinary = remainderToBinary + j;
}
let sublast = parseInt(remainderToBinary, 2);
masks[index] = sublast;
if (index < masks.length - 1) {
for (let i = index + 1; i < masks.length; i++) {
masks[i] = 0;
}
}
return masks;
};
/**
* 分解ip地址
*/
function getIpv4Array(ip) {
let ipArray = newArray(4);
let ips = ip.split(".");
for (let i =0; i < ips.length; i++) {
ipArray[i] = parseInt(ips[i]);
}
return ipArray;
};
只需要调用isSubnet方法,将两个IP地址传入就可以了。
3.2.2 JAVA的实现
首先非常感谢“雪谷里的暖风”(https://blog.csdn.net/rilaohn),我是通过看他的JAVA代码理解的地址计算,然后通过学习网络地址转换写出的这篇文章(有点懒,COPY他的代码变量名和方法名都没换)。非常感谢!!!
package testProject;
import java.util.Arrays;
public class test {
public static void main(String[] args) {
booleanbl = isSubnet("103.7.30.64/26","103.7.30.68");
System.out.println(bl);
}
/**
* 判断目标ip是否属于某个ip子网
* @param subIp 带有子网掩码的ip
* @param ip 目标ip
* @return 成功true,失败false
*/
private static boolean isSubnet(StringsubIp, String ip) {
String[] subs = subIp.split("/");
System.out.println("subs:"+Arrays.toString(subs));
int[] subnetMask = getSubnetMask(Integer.parseInt(subs[1]));
System.out.println("subnetMask:"+Arrays.toString(subnetMask));
int[] ipArray = getIpv4Array(subs[0]);
System.out.println("ipArray:"+Arrays.toString(ipArray));
String subnet = getSubnet(subnetMask,ipArray);
System.out.println("subnet:"+subnet);
int[] ipArray2 = getIpv4Array(ip);
System.out.println("ipArray2:"+Arrays.toString(ipArray2));
String subnet2 = getSubnet(subnetMask,ipArray2);
System.out.println("subnet2:"+subnet2);
if (subnet.equals(subnet2))
return true;
return false;
}
/**
* 获取子网掩码数组
* @param num 子网掩码长(暨带子网掩码的ip/后面的数字)
* @return 长度为4的int数组
*/
private static int[] getSubnetMask(intnum) {
int[] masks = new int[4];
int index = num / 8;
int remainder = num % 8;
String remainderToBinary = "";
for (inti = 0; i < index; i++) //循环3次
masks[i] = 255;
for (inti = 0; i < 8;i++) {
int j = 0;
if (i <remainder)
j = 1;
remainderToBinary = remainderToBinary + j;
}
int sublast = Integer.valueOf(remainderToBinary, 2);
masks[index] =sublast;
System.out.println("masks:"+Arrays.toString(masks));
if (index <masks.length - 1) {
for (inti = index + 1;i < masks.length;i++)
masks[i] = 0;
}
return masks;
}
/**
* 分解ip地址
* @param ip ip地址
* @return 长度为4的int数组
*/
private static int[] getIpv4Array(Stringip) {
int[] ipArray = new int[4];
String[] ips = ip.split("\.");
for (inti = 0; i < ips.length; i++)
ipArray[i] = Integer.parseInt(ips[i]);
return ipArray;
}
/**
* 通过子网掩码数组和ip地址数组获取主机地址
* @param masks 子网掩码数组
* @param ipArray ip数组
* @return 主机地址
*/
private static String getSubnet(int[]masks, int[]ipArray) {
String subnet = "";
if (masks.length !=ipArray.length) {
try {
throw new Exception("子网掩码长度和ip地址长度不一样");
} catch (Exceptione) {
e.printStackTrace();
}
}
for (inti = 0; i < ipArray.length; i++) {
int and = masks[i] &ipArray[i];
subnet = subnet + and;
if (i <ipArray.length -1)
subnet = subnet + ".";
}
return subnet;
}
}
4 总结
好了,今天的地址转换就写到这里了。这里牵扯的技术点估计以后用的时候并不会多,放到这里供大家学习,也为以后自己再用的时候能立马翻出来掌握。如果有错的地方还请大家指正。
最后
以上就是柔弱月饼为你收集整理的Node.js授权微信公众号服务器访问本地(验证微信公众号IP地址)1 概述2 处理返回的IP地址列表3 代码的实现4 总结的全部内容,希望文章能够帮你解决Node.js授权微信公众号服务器访问本地(验证微信公众号IP地址)1 概述2 处理返回的IP地址列表3 代码的实现4 总结所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复