Java判断请求来自win10还是win11竟然这么复杂 202
1. 问题背景
按我们的常识,判断一个请求来自win10还是win11,一般是通过user-agent
这个请求头。对于Linux系统或者Mac系统,这个方法是没有问题的,但对于win10和win11,这个方法失效了。
2. 问题来源
我们的系统里有一个记录用户请求来源的操作系统字段,如下:
![](https://img.haomeiwen.com/i297930/1037e073c679ae97.png)
我发现虽然自己是win11系统,但记录里还是Windows 10。找到代码所在地:
UserAgent userAgent = UserAgent.parseUserAgentString(ServletUtils.getRequest().getHeader("User-Agent"));
String ip = IpUtils.getIpAddr(ServletUtils.getRequest());
loginUser.setIpaddr(ip);
loginUser.setLoginLocation(AddressUtils.getRealAddressByIP(ip));
loginUser.setBrowser(userAgent.getBrowser().getName());
loginUser.setOs(userAgent.getOperatingSystem().getName());
其中UserAgent
来自这个开源包:
<dependency>
<groupId>eu.bitwalker</groupId>
<artifactId>UserAgentUtils</artifactId>
<version>1.21</version>
</dependency>
再去看看win11系统浏览器请求的user-agent
值为Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36
,这个值和win10是一样的。
3. win10和win11的user-agent
通过微软官网,发现
https://learn.microsoft.com/zh-cn/microsoft-edge/web-platform/how-to-detect-win11
Windows 11 不再支持通过 User-Agent 来区分。
User-Agent字符串不会更新为区分Windows 11和Windows 10,也不会区分 CPU 体系结构。 建议不要使用User-Agent字符串来检索用户代理数据。 不支持User-Agent客户端提示的浏览器将无法区分Windows 11和Windows 10,也无法区分 CPU 体系结构。
盲猜一下是当年win10发布的时候说,以后Windows版本会一直保持win10,但后来自己打脸了,win11出来了。索性就不通过User-Agent来区分了,因为他们值一样,区分不了。
官网提供了解决方法,可以通过 Header 里的 Sec-CH-UA-Platform-Version
这个字段来区分
![](https://img.haomeiwen.com/i297930/e868625246118604.png)
并且给出了支持的浏览器
![](https://img.haomeiwen.com/i297930/1a6960a76c33652b.png)
还给出了前端javascript
的代码
navigator.userAgentData.getHighEntropyValues(["platformVersion"])
.then(ua => {
if (navigator.userAgentData.platform === "Windows") {
const majorPlatformVersion = parseInt(ua.platformVersion.split('.')[0]);
if (majorPlatformVersion >= 13) {
console.log("Windows 11 or later");
}
else if (majorPlatformVersion > 0) {
console.log("Windows 10");
}
else {
console.log("Before Windows 10");
}
}
else {
console.log("Not running on Windows");
}
});
4. Java获取 Sec-CH-UA-Platform-Version 这个请求头
当通过Java获取这个请求头,我发现取不到值
String platform = ServletUtils.getRequest().getHeader("Sec-Ch-Ua-Platform");
String platformVersion = ServletUtils.getRequest().getHeader("Sec-CH-UA-Platform-Version");
还以为是我浏览器的问题,使用Edge
浏览器仍然不行
![](https://img.haomeiwen.com/i297930/7dc13da8104cad3f.png)
再看看Edge
浏览器的请求值
![](https://img.haomeiwen.com/i297930/bcb8b191d084891e.png)
仍然没有这个Sec-CH-UA-Platform-Version
请求头,只有Sec-Ch-Ua-Platform
这个请求头。
难道微软官方给的方法不可行。
5. Sec-CH-UA-Platform-Version深入挖掘
深入挖掘,Chrome浏览器给出这样提示:
https://chromestatus.com/feature/5995832180473856
![](https://img.haomeiwen.com/i297930/2d21706150a67636.png)
意思未来要废弃 Sec-CH-UA-*
,替代使用User-Agent
,感觉又回到了原点。
6. 获取Sec-CH-UA-Platform-Version的正确方法
继续深入,看这篇文章给出了获取 Sec-CH-UA-Platform-Version
请求头的方法
https://51degrees.com/blog/implementing-user-agent-client-hints
6.1 Accept-CH方法
为了流量或安全考虑,浏览器默认只传这三个请求头:
Sec-CH-UA, Sec-CH-UA-Mobile, and Sec-CH-UA-Platform
这三个请求头称之为 low entropy hints.
如果想要获取更多的 UA-CH
类型请求头,则需要服务端明确,如下图所示:
![](https://img.haomeiwen.com/i297930/71203d62f4346600.png)
所以,需要在服务端返回时加上两个请求头,如下:
response.addHeader("Accept-CH", "Sec-CH-UA, Sec-CH-UA-Full-Version, Sec-CH-UA-Full-Version-List, Sec-CH-UA-Platform-Version");
response.addHeader("Vary", "Sec-CH-UA, Sec-CH-UA-Full-Version, Sec-CH-UA-Full-Version-List, Sec-CH-UA-Platform-Version");
回到浏览器,我们发现加成功了:
![](https://img.haomeiwen.com/i297930/fa8dd2a35501a0de.png)
但是,在后端仍无法获得 Sec-CH-UA-Platform-Version
值,那是因为 Accept-CH方法
这方法只能应用于同源请求里。
The browser then sends these additional UA-CH headers to any URL of the first-party domain in subsequent requests.
为什么会这样,因为我自己的应用是Vue3应用,在dev环境下它的架构是这样的
![](https://img.haomeiwen.com/i297930/b8b5105340b164e6.png)
在开发环境下,API服务器与Node服务器非同源,Node服务器做了一层代理。
6.2 Delegate-CH方法
上面第一种方法解决不了,再转到第二种方法,Delegate-CH:
Delegate-CH allows for delegation of high entropy hints in a single meta tag and it tells the browser that it should include the specified Client Hints headers when it makes requests to that domain.
这个方法需要在html文件的meta标签里加下以下
<meta http-equiv="Delegate-CH" content="sec-ch-ua-full-version-list http://localhost:8090; sec-ch-ua-model http://localhost:8090; sec-ch-ua-platform http://localhost:8090; sec-ch-ua-platform-version http://localhost:8090"/>
对于Vue3应用,加在index.html文件里
![](https://img.haomeiwen.com/i297930/2de45c9cd46044ab.png)
这里注意填的域名为 http://localhost:8090
为前端node的地址。
这Java后端可以获取到 Sec-CH-UA-Platform-Version
这个header值,如下图所示
![](https://img.haomeiwen.com/i297930/8cb08cd10524c175.png)
前端请求也带了这个值
![](https://img.haomeiwen.com/i297930/73b02087da0ca5f3.png)
6.3 Permissions-Policy方法
Delegate-CH
方法有个问题,需要改前端代码,比较麻烦,接下来第三种方法:Permissions-Policy
A Permissions Policy is a feature that gives third-party domains selective access to browser data, enabling cross-origin User-Agent Client Hints delegation. The Permissions-Policy header will do the same job as Delegate-CH but uses HTTP response headers rather than HTML.
看描述是针对跨域请求的,服务端返回的时候,增加这个header
response.addHeader("Accept-CH","Sec-CH-UA-Platform-Version, DPR");
response.addHeader("Permissions-Policy",
"ch-ua-platform-version=(self \"localhost:8090\"), ch-dpr=(self \"localhost:8080\")");