目的
项目中同一个帐号可以多个人同时登录,导致有些操作无法直接追溯到对应的责任人,因此需要获取到当前登录用户的IP地址(员工内部人员的内网地址)并收集上报。
思路
由于该项目基本都是部署在内网,所以本篇文章暂时只考虑获取内网IP,内网IP的获取相对比较复杂,主要是需要依赖 API:webRTC
WebRTC,名称源自网页即时通信(英语:Web Real-Time Communication)的缩写,是一个支持网页浏览器进行实时语音对话或视频对话的API。它于2011年6月1日开源并在Google、Mozilla、Opera支持下被纳入万维网联盟的W3C推荐标准。
webRTC 是HTML 5 的一个扩展,允许去获取当前客户端的IP地址
解决办法
/** 获取电脑本地IP地址 */
function getLocalIP(callback) {
let recode = {};
let RTCPeerConnection = window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection;
if (!RTCPeerConnection) {
let win = iframe.contentWindow;
RTCPeerConnection = win.RTCPeerConnection || win.mozRTCPeerConnection || win.webkitRTCPeerConnection;
}
//创建实例,生成连接
let pc = new RTCPeerConnection();
// 匹配字符串中符合ip地址的字段
function handleCandidate(candidate) {
let ip_regexp = /([0-9]{1,3}(\.[0-9]{1,3}){3}|([a-f0-9]{1,4}((:[a-f0-9]{1,4}){7}|:+[a-f0-9]{1,4}){6}))/;
let ip_isMatch = candidate.match(ip_regexp)[1];
if (!recode[ip_isMatch]) {
// 判断 IPV4
if (ip_isMatch.match(/^(192\.168\.|169\.254\.|10\.|172\.(1[6-9]|2\d|3[01]))/)) {
callback(ip_isMatch);
}
recode[ip_isMatch] = true;
}
}
//监听icecandidate事件
pc.onicecandidate = ice => {
if (ice.candidate) {
handleCandidate(ice.candidate.candidate);
}
};
//建立一个伪数据的通道
pc.createDataChannel("");
pc.createOffer(
res => {
pc.setLocalDescription(res);
},
() => {}
);
//延迟,让一切都能完成
setTimeout(() => {
let lines = pc.localDescription.sdp.split("\n");
lines.forEach(item => {
if (item.indexOf("a=candidate:") === 0) {
handleCandidate(item);
}
});
}, 1000);
}
问题
谷歌浏览器
如果使用 chrome 浏览器打开,此时可能会看到一串类似于: e87e041d-15e1-4662-adad-7a6601fca9fb.local
的机器码,这是因为chrome 默认是隐藏掉 内网IP地址的。
处理
可以通过修改 chrome 浏览器的配置更改此行为:
1、在chrome 浏览器地址栏中输入:chrome://flags/
2、搜索 #enable-webrtc-hide-local-ips-with-mdns 该配置 并将属性改为 disabled
推荐
可以看这位大佬的GitHub项目webrtc-ip,封装了外网 内网 获取方法
/*
* This file is the entire script combined in working order.
* Copyright 2021 © Joey Malvinni
* License: MIT
*/
// [---------------------------------------------------------------------------]
// File: ip_validator.js
/*
* This module validates the two types of IP addresses.
* Copyright 2021 © Joey Malvinni
* License: MIT
*/
// The function that checks if the given IPv4 address is valid.
function is_ipv4(ip){
return regex_v4.test(ip);
};
// Checks if the IPv6 address is valid.
function is_ipv6(ip){
return regex_v6.test(ip);
};
// Simple IP regex that works on both IPv6 and IPv4
var simpleIPRegex = /([0-9]{1,3}(\.[0-9]{1,3}){3}|[a-f0-9]{1,4}(:[a-f0-9]{1,4}){7})/g;
// IPv4 regex used to determine whether an IP is IPv4 or not.
let regex_v4 = /((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])/;
// The IPv6 regex used when determining an IPv6 address.
let regex_v6 = /((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))/;
// Exporting the two regexes in an array to be used in the main detector.
let ip_regex_array = [regex_v6, regex_v4]
// [---------------------------------------------------------------------------]
// File: peer_conn.js
/*
* This module provides the main WebRTC functions that return IP addresses from the STUN request.
* Copyright 2021 © Joey Malvinni
* License: MIT
*/
function peer(callback){
// Creating the peer connection.
var WebRTCPeerConnection = window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection;
// Initializing the connection.
var createdConnection;
// Main start function.
function start(){
// Creating the actual connection.
createConnection()
// Log the STUN request.
createStunRequest()
};
// Stop function to reset the connection.
function stop(){
// Checking if the connection has been created or not.
if (createdConnection) {
// Attempt to close it, if RTCPeerConnection.close() is not supported, remove the event listeners.
try {
createdConnection.close();
} finally {
createdConnection.onicecandidate = () => {};
createdConnection = null;
};
};
};
// Function that makes the connection request to Google's STUN server
function createConnection(){
let iceServers = [{
urls: 'stun:stun.l.google.com:19302'
}];
// Creating the connection with the STUN server.
createdConnection = new WebRTCPeerConnection({ iceServers });
// Handling the ICE candidate event.
createdConnection.onicecandidate = (data) => handleCandidates(data);
// Creation of the fake data channel.
createdConnection.createDataChannel('fake_data_channel');
};
// Function that creates the STUN request offer needed to get the IPs.
function createStunRequest(){
// Create the offer that exposes the IP addresses.
return createdConnection.createOffer().then(sdp => createdConnection.setLocalDescription(sdp));
};
// Handling the onIceCandidate event.
function handleCandidates(ice){
// Checking if the ICE candidate lines given are valid.
if (ice && ice.candidate && ice.candidate.candidate) {
// Returning the IPs to the main function.
callback(ice && ice.candidate && ice.candidate.candidate);
};
};
// Returning the main functions needed.
return {
start,
stop,
createConnection,
createStunRequest,
handleCandidates
};
};
// [---------------------------------------------------------------------------]
// File: public_ip.js
/*
* This module provides the worker functions that return the public IP addresses.
* Copyright 2021 © Joey Malvinni
* License: MIT
*/
function publicIPs(timer){
// Timing validation.
if(timer) if(timer < 100) throw new Error('Custom timeout cannot be under 100 milliseconds.');
// IPs is the final array of all valid IPs found.
var IPs = [];
// Creating the peer connection request while handling the callback event.
var peerConn = peer(handleIceCandidates);
function getIps(){
// Returning a promise.
return new Promise(function(resolve, reject) {
// Starting the peer connection.
peerConn.start();
// Setting the timer.
setTimeout(() => {
// Checking if the IP array exists.
if(!IPs || IPs === []){
// Rejecting the error
reject('No IP addresses were found.')
} else {
// Return the unique IP addresses in an array.
resolve(unique(IPs.flat(Infinity)))
};
// reset the peer connection.
reset();
// Set the Timeout to the custom timer, default to 500 milliseconds.
}, timer || 500);
});
};
function handleIceCandidates(ip){
var array = [];
// Looping over the two regexs for IPv6 and IPv4
for(let regex of ip_regex_array){
let arr = [];
// Lutting all of the strings that match either IP format in an array
let possible_ips_array = regex.exec(ip)
if(possible_ips_array){
// Looping over that array
for(let i = 0; i < possible_ips_array.length; i++){
// Checking if the "IP" is valid
if(is_ipv4(possible_ips_array[i]) || is_ipv6(possible_ips_array[i])){
arr.push(possible_ips_array[i])
};
};
array.push(arr);
};
};
// Final function that does more checks to determine the array's validity,
// Also flattens the array to remove extraneous sub-arrays.
push(array.flat(Infinity))
};
function push(ip){
// Checks if the IP addresses givin are already in the array.
if(!IPs.includes(ip)){
IPs.push(unique(ip.flat(Infinity)));
};
};
function reset(){
// Stops the peer connection to the STUN server.
peerConn.stop()
};
// Use this to only return unique IP addresses.
function unique(a) {
return Array.from(new Set(a));
};
return getIps();
};
// [---------------------------------------------------------------------------]
// File: index.js
/*
* This module combines all of the worker modules into the main functions that get exported.
* Copyright 2021 © Joey Malvinni
* License: MIT
*/
// Categorizes the IPs by IP, type, and IPv4.
function getIPTypes(timer){
// Returning the result as a promise.
return new Promise(function(resolve, reject) {
// Final array
let finalIpArray = []
// Getting the raw IPs in an array.
publicIPs(timer).then((ips)=>{
// Looping over each IP.
ips.forEach(ip => {
if (ip.match(/^(192\.168\.|169\.254\.|10\.|172\.(1[6-9]|2\d|3[01]))/)) {
// The IP is private.
finalIpArray.push({ ip: ip, type: 'private', IPv4: true })
} else if (ip.match(/((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))/)) {
// The IP is an IPv6 address.
finalIpArray.push({ ip: ip, type: 'IPv6', IPv4: false })
} else {
// Assume the IP is public.
finalIpArray.push({ ip: ip, type: 'public', IPv4: true })
}
})
// Resolving the promise.
resolve(finalIpArray)
}).catch(reject)
})
}
// Filters out IPv4 addresses.
function getIPv4(timer) {
return getIPTypes(timer).then(ips => {
// Filters the IP by IPv4.
const ip = ips.filter(ip => ip.IPv4);
// Loops over each object and extracts the IP.
for(let i = 0; i < ip.length; i++){
ip[i] = ip[i].ip
}
// Returns undefined if the array is empty.
return ip ? ip : '';
});
}
// Filters out IPv6 addresses.
function getIPv6(timer) {
// Getting the IPs by type.
return getIPTypes(timer).then(ips => {
// Filtering the IPs by IPv6.
const ip = ips.filter(ip => ip.type === 'IPv6');
// Extracting the IPs
for(let i = 0; i < ip.length; i++){
// Removing all other data from the object.
ip[i] = ip[i].ip
}
// Returning the IP or undefined.
return ip ? ip.ip : '';
});
}
// Returns all of the functions in an object, default to getting all of the IPs without any filtering applied.
function getIPs(timer){
return Object.assign(
publicIPs(timer), {
types: getIPTypes,
public: publicIPs,
IPv4: getIPv4,
IPv6: getIPv6,
}
)
};