获取访问者唯一标识

项目有需求,登录前有匿名举报功能,为了避免恶意举报,需标识出访问者进行控制或封禁。首先想到的是获取mac地址,网上资料很多,大体有这几种方案:

获取mac地址

  1. 通过浏览器获取

    浏览器或是利用ActiveX,目前只有IE支持,谷歌和火狐不支持(谷歌和火狐好像有另外的插件可以支持,但没有成熟应用广泛的插件)

  2. 服务器获取,基本思路是先获取ip,根据ip调用nbtstat(响应有点慢) 或 arp命令,示例代码如下:

    public class GetIpAndMac extends HttpServlet{
    	protected void service(HttpServletRequest request,HttpServletResponse res) throws IOException {		
    		try {
    			String clientIp = getIpAddr( request);
    			System.out.println("客户端ip为:"+clientIp);
    			String clientMac = getMACAddress(clientIp);
    			System.out.println("客户端mac为:"+clientMac);
    		} catch (Exception e) {
    			// TODO Auto-generated catch block
    			e.printStackTrace();
    		}
    	}
    
    	 /**
    	 * 通过HttpServletRequest返回IP地址
    	 * @param request HttpServletRequest
    	 * @return ip String
    	 * @throws Exception
    	 */
    	public String getIpAddr(HttpServletRequest request) throws Exception {
    	    String ip = request.getHeader("x-forwarded-for");
    	    if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
    	        ip = request.getHeader("Proxy-Client-IP");
    	    }
    	    if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
    	        ip = request.getHeader("WL-Proxy-Client-IP");
    	    }
    	    if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
    	        ip = request.getHeader("HTTP_CLIENT_IP");
    	    }
    	    if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
    	        ip = request.getHeader("HTTP_X_FORWARDED_FOR");
    	    }
    	    if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
    	        ip = request.getRemoteAddr();
    	    }
    	    return ip;
    	}
    	 
    	 
    	 
    	 /**
    	 * 通过IP地址获取MAC地址
    	 * @param ip String,127.0.0.1格式
    	 * @return mac String
    	 * @throws Exception
    	 */
    	public String getMACAddress(String ip) throws Exception {
    	    String line = "";
    	    String macAddress = "";
    	    final String MAC_ADDRESS_PREFIX = "MAC Address = ";
    	    final String MAC_ADDRESS_PREFIX1 = "MAC";
    	    final String LOOPBACK_ADDRESS = "127.0.0.1";
    	    //如果为127.0.0.1,则获取本地MAC地址。
    	    if (LOOPBACK_ADDRESS.equals(ip)) {
    	        InetAddress inetAddress = InetAddress.getLocalHost();
    	        //貌似此方法需要JDK1.6。
    	        byte[] mac = NetworkInterface.getByInetAddress(inetAddress).getHardwareAddress();
    	        //下面代码是把mac地址拼装成String
    	        StringBuilder sb = new StringBuilder();
    	        for (int i = 0; i < mac.length; i++) {
    	            if (i != 0) {
    	                sb.append("-");
    	            }
    	            //mac[i] & 0xFF 是为了把byte转化为正整数
    	            String s = Integer.toHexString(mac[i] & 0xFF);
    	            sb.append(s.length() == 1 ? 0 + s : s);
    	        }
    	        //把字符串所有小写字母改为大写成为正规的mac地址并返回
    	        macAddress = sb.toString().trim().toUpperCase();
    	        return macAddress;
    	    }
    	    //获取非本地IP的MAC地址
    	    try {
    	        //Process p = Runtime.getRuntime().exec("nbtstat -A " + ip);
    	        Process p = Runtime.getRuntime().exec("arp -a " + ip);
    	        InputStreamReader isr = new InputStreamReader(p.getInputStream(),"GBK");
    	        BufferedReader br = new BufferedReader(isr);
    	        while ((line = br.readLine()) != null) {
    	            if (line != null) {
    	                /*int index = line.indexOf(MAC_ADDRESS_PREFIX1);
    	                if (index != -1) {
    	                    //macAddress = line.substring(index + MAC_ADDRESS_PREFIX.length()).trim().toUpperCase();
    	                	macAddress = line.substring(line.indexOf("=")+1).trim().toUpperCase();
    	                }*/
    	            	int index = line.indexOf(ip);
    	                if (index != -1) {
    	                    //macAddress = line.substring(index + MAC_ADDRESS_PREFIX.length()).trim().toUpperCase();
    	                	macAddress = line.split("\\s+")[1].trim().toUpperCase();
    	                }
    	            }
    	        }
    	        br.close();
    	    } catch (IOException e) {
    	        e.printStackTrace(System.out);
    	    }
    	    return macAddress;
    	}
    }
    

    这个方案起初让我看到了曙光,而且在我本地测试没问题。但现实狠狠打了脸,这个方案有下面两个弊端:

    • 提需求的人提醒了我,他们使用NAT接入互联网,NAT???好尴尬,一直都没注意网络方面的知识,查了一下才知道,简单来说就是局域网的电脑对外都只暴露了一个公有ip,那么这个局域网内电脑访问的所有请求得到的ip都是一个,ip都不准确了,mac地址即使获取到有什么意义

    • nbtstat命令只支持windows arp命令也只能在局域网中使用

      所以这个方案,如果你的系统可以保证只在内网使用,那可以尝试,如果是互联网使用,根本不能使用

浏览器唯一标识

​ 由于mac地址那条路走不通了,所以我想如果浏览器带唯一标识就好了,有了这个思路后就想着随便查查,结果查到了浏览器指纹(也有叫用户指纹),这也是业内比较热门的研究领域,真是意外之喜,如果单纯只是为了标识访问者,进行控制的话,这个就可以了。

​ 浏览器指纹是指仅通过浏览器的各种信息,如系统字体、屏幕分辨率、浏览器插件,无需 cookie 等技术,就能近乎绝对定位一个用户,就算使用浏览器的隐私窗口模式,也无法匿名。这是一个被动的识别方式。也就是说,理论上你访问了某一个网站,那么这个网站就能识别到你,虽然不知道你是谁,但你有一个唯一的指纹,将来无论是广告投放、精准推送,还是其他一些关于隐私的事情,都非常方便。详细了解可参考以下链接,本文只是针对FingerPrint2的使用解释,对浏览器指纹不做深入研究。

  1. https://blog.csdn.net/qq_32447301/article/details/88381534
  2. https://www.zhihu.com/question/51410927
  3. https://www.jianshu.com/p/6c41658f9cb7

目前的方案是结合上面第3个链接的方案,cookie+FingerPrint2js实现

  1. 引入FingerPrint2js

    <script src="https://cdn.jsdelivr.net/npm/fingerprintjs2@2.0.6/dist/fingerprint2.min.js"></script>
    
function getBrowerUUID(){
	var murmur;
	/**userAgent:浏览器升级会改变,进而影响计算结果
	 * plugins:用户安装插件可能性比较大
	 * fonts:字体基本都相似,但字体很多也会影响速率**/
	var options = {excludes: {userAgent: true,plugins:true,fonts:true}}
	var currentBrowserUUID = getcookie('browserUUID');
	console.log('currentBrowserUUID:'+currentBrowserUUID);
	if (window.requestIdleCallback) {
		requestIdleCallback(function () {
			Fingerprint2.get(options,function (components) {
				console.log(components) // an array of components: {key: ..., value: ...}
				var values = components.map(function (component) { return component.value })
				murmur = Fingerprint2.x64hash128(values.join(''), 31)
				if(!$chk(currentBrowserUUID)){
					//如果当前浏览器cookie中没有browserUUID才设置cookie值,如果有还用原来的,减少浏览器属性更改对标识的影响概率
					setCookie('browserUUID',murmur);
				}
			})
		})
	} else {
		setTimeout(function () {
			Fingerprint2.get(options,function (components) {
				console.log(components) // an array of components: {key: ..., value: ...}
				var values = components.map(function (component) { return component.value })
				murmur = Fingerprint2.x64hash128(values.join(''), 31)
				if(!$chk(currentBrowserUUID)){
					setCookie('browserUUID',murmur);
				}
			})
		}, 500)
	}
}

其中为了提高计算准确率,避免用户更新浏览器,安装插件等因素影响计算结果,我把userAgent、plugins、fonts三项排除掉了,代码中有注释说明。计算的值压入cookie中,使用时获取就好。但FingerPrint2js并没有实现跨浏览器,而且也不能溯源,但对于控制恶意提交够用了,官方说明准确率达到99%,这个还没有实践,有待考证。

附录:指纹组件说明

名称解释:

取决于浏览器:当用户在同一设备上使用不同的浏览器时,某些组件不会更改,从而使设备可以进行指纹识别

稳定:每次刷新页面时,某些组件都会更改(“不稳定”)

组件取决于浏览器稳定
userAgent(用户代理)
language(语言)否(大部分时间)
colorDepth(颜色深度)没有
deviceMemory(设备内存)没有
pixelRatio(像素比)没有
hardwareConcurrency(设备并发线程数)否(但IE不支持)是(但IE不支持)
screenResolution(屏幕分辨率)没有
availableScreenResolution(屏幕有效分辨率)没有
timezoneOffset(时区偏移)没有
timezone(时区)没有
sessionStorage
localStorage(本地存储)
indexedDb(索引数据库)
addBehavior(是否支持Behavior,IE5的属性)
openDatabase(是否支持调用本地数据库)
cpuClass(所在系统的CPU等级)没有
platform(客户端操作系统)否(大部分时间)
doNotTrack(不跟踪)
plugins
canvas(帆布)是的,在实践中是(大多数时候)
webgl是的,在实践中是(大多数时候)
webglVendorAndRenderer否(大部分时间)
adBlock(广告阻止)是(但可能取决于时间)
hasLiedLanguages(用户是否篡改了语言)没有
hasLiedResolution(用户是否篡改了屏幕分辨率)没有
hasLiedOs(.用户是否篡改了操作系统)没有
hasLiedBrowser(用户是否篡改了浏览器)没有
touchSupport(触摸屏支持)没有
customEntropyFunction(自定义方法)
fonts是的(大多数时候)
audio
enumerateDevices?-见#498
  • 5
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在很多移动应用开发中,我们需要获取手机设备的唯一标识,以便于做一些数据统计、用户识别等操作。在H5开发中,我们同样需要获取设备的唯一标识,这时候我们可以利用HTML5提供的一些API来实现。 一、使用HTML5的Web Storage 其中最常见的方式就是利用Web Storage。我们可以使用localStorage来存储一个设备唯一标识的值,当用户首次访问网站时创建一个唯一标识,在以后的访问中直接读取该值。 例如,我们可以在用户首次打开网站时生成一个UUID(Universally Unique Identifier,通用唯一识别码),并将其存入localStorage,然后用户以后的所有访问中直接读取这个值。 但是要注意的是,如果用户清除了浏览器缓存或者使用了不同的设备访问网站,那么就无法获取到之前生成的唯一标识,所以这种方式并不是完全可靠的。 二、使用HTML5的设备指纹 此外,HTML5还提供了一些硬件信息和软件信息,在浏览器中能够生成唯一标识信息成为“设备指纹”,这些信息包括操作系统、浏览器、系统语言、屏幕分辨率、显示器尺寸、字体、插件等,利用这些信息可以生成唯一标识。 但是设备指纹不能百分之百的标识用户,因为不同的用户的设备指纹也可能很相似。 三、使用第三方API 还有一种方式是使用第三方提供的API,例如Google的Firebase Cloud Messaging API,该API可以生成设备唯一标识,并且可以跨平台使用。 总而言之,获取设备唯一标识并不是一件可以百分之百准确的事情,我们需要根据实际需求来选择合适的获取方式,以及结合其他信息来进行用户识别。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值