User-Agent Client Hints의 도입, UA 프리징을 대비하라
User-Agent Client Hints의 도입, UA 프리징을 대비하라 관련
이 글에서는 클라이언트 입장에서 User-Agent Client Hints를 다룹니다.
지금까지 웹 서비스는 User-Agent HTTP 헤더에 포함된 User-Agent string에서 브라우저, OS, 사용자의 기기 정보 등 사용자 에이전트 정보를 얻을 수 있었습니다. User-Agent string을 이용하는 이유는 주로 다음과 같습니다.
- 특정 버전의 버그
- OS의 동작 차이
- 버전에 따른 동작 차이
- 사용자 에이전트에 따라 보여줄 콘텐츠 협상
우리는 위와 같은 다양한 이유로 User-Agent string을 사용해 왔지만 User-Agent string에는 많은 엔트로피(정보량)가 담겨 있어 개인정보 침해 문제가 있을 수 있습니다.
그래서 Chrome은 개인정보 보호를 위한 샌드박스 프로젝트 중 Client Hints 도입을 시도했습니다. Client Hints를 쉽게 말하자면 클라이언트 및 에이전트의 정보라고 할 수 있으며 가져올 정보를 명시합니다. 그렇기 때문에 사용자는 서버 및 클라이언트가 어떤 정보를 요구하는지 알 수 있습니다. 지금까지의 Client Hints 관련 Chrome 릴리즈 사항은 다음과 같습니다.
- Chrome 46 - HTTP Client Hints 실험적 도입
- Chrome 77 - Freeze-User-Agent 플래그 추가
- Chrome 82, 83 (2020.05) - User-Agent Client Hints 실험적 도입
- Chrome 84 (2020.07) - User-Agent Client Hints 인터페이스 변경
이 글에서는 이에 따라 앞으로 어떤 변화가 있을지, 이 변화에 어떻게 대응해야 할지 알아보겠습니다.
UA 프리징
Chrome 83부터 User-Agent Client Hints가 실험적으로 도입되었고 가장 큰 이슈는 바로 User-Agent string 프리징(이하 UA 프리징)이다. UA 프리징으로 달라지는 점은 다음과 같다.
- 다음 속성값이 고정된다.
navigator.userAgent
navigator.appVersion
navigator.platform
(Android Chrome에서는 Linux armv8l으로 고정)navigator.productSub
navigator.vendor
- Chrome에서 안드로이드를 제외한 모든 운영체제는 윈도우 10으로 변한다.
- 동기 방식으로 OS 이름, OS 버전, 모델명을 알 수 없다.
navigator.userAgent
대신navigator.userAgentData
을 사용해야 한다.- 브라우저 버전은 메이저 버전만 나타난다.
- OS 이름, 버전, 모델명, 브라우저의 풀버전은 비동기 방식으로 알 수 있다.
UA 프리징은 다음 플래그를 활성화하면 테스트할 수 있다.
chrome://flags/#enable-experimental-web-platform-features
chrome://flags/#freeze-user-agent
UA 프리징 테스트 결과 navigator.userAgent
를 사용하여 얻은 User-Agent string 값은 다음과 같다.
- Galaxy Z Flip, Android 10, Chrome 85.0.4183.81
- 전:
Mozila/5.0 (Linux; Android 10; SM-F700N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.81 Mobile Safari/537.36
- 후:
Mozila/5.0 (Linux; Android 9; Unspecified Device) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.0.0 Mobile Safari/537.36
- 전:
- Mac OS X 10.15.4, Chrome Canary 87.0.4243.0
- 전:
Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4243.0 Safari/537.36
- 후:
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.0.0 Safari/537.36
- 전:
UA 프리징 결과 기기명, OS의 정보, 브라우저의 버전이 불분명해진 것을 확인할 수 있다. 앞으로 User-Agent string을 고정된 값으로 제공하기 위해 값은 점점 더 간소해질 수 있다.
UA 프리징 대응 방안
User-Agent Data
UA 프리징의 대응 방안으로 User-Agent string을 세분화해 Object 형식으로 나타낸 User-Agent Data를 사용할 수 있다. 인터페이스는 다음과 같다.
dictionary NavigatorUABrandVersion {
DOMString brand;
DOMString version;
};
dictionary UADataValues {
DOMString platform;
DOMString platformVersion;
DOMString architecture;
DOMString model;
DOMString uaFullVersion;
};
[Exposed=(Window,Worker)]
interface NavigatorUAData {
readonly attribute FrozenArray>NavigatorUABrandVersion> brands;
readonly attribute boolean mobile;
Promise>UADataValues> getHighEntropyValues(sequence>DOMString> hints);
};
interface mixin NavigatorUA {
[SecureContext] readonly attribute NavigatorUAData userAgentData;
};
Navigator includes NavigatorUA;
WorkerNavigator includes NavigatorUA;
navigator.userAgentData
에 접근하면 brands
(84 이전 버전에서는 uaList
)과 mobile
속성이 있으며 getHighEntropyValues
메서드를 사용할 수 있다.
![그림 Chrome Canary 87.0.4243.0의](/images/content/d2.naver.com/6532276/1.png)
navigator.userAgentData
![그림 Edge 85.0.564.44의](/images/content/d2.naver.com/6532276/1.png)
navigator.userAgentData
brands
는 사용자 에이전트의 상업명과 버전의 이름이 담긴 리스트이다. 87 버전을 기준으로 Chromium을 사용하는 브라우저에서는 브라우저 브랜드, Chromium 브랜드, GREASE 브랜드 3개의 값이 임의의 순서로 있다.mobile
은 사용자 에이전트의 기기가 모바일인지를 나타내는 값이다.getHighEntropyValues
메서드는 높은 엔트로피에 해당하는 값을 가져온다.brands
(브라우저 이름과 메이저 버전)와mobile
은 낮은 엔트로피로 분류되기 때문에 동기 방식으로 값을 가져올 수 있지만 그 외 정보는 높은 엔트로피로 분류되어getHighEntropyValues
메서드로 가져와야 한다. 또한 User-Agent string처럼 모든 정보를 가져오는 게 아니라 자신에게 필요한 정보만 가져올 수 있다.
navigator.userAgentData.getHighEntropyValues([
"architecture",
"model",
"platform",
"platformVersion",
"uaFullVersion",
]).then(<span class="hljs-function"><span class="hljs-params">info => {
<span class="hljs-built_in">console.log(info);
});
![<FontIcon icon="iconfont icon-macos"/>Mac OS X 10.15.4, Chrome Canary 87.0.4243.0의 호출 결과](/images/content/d2.naver.com/6532276/3.png)
navigator.userAgentData.getHighEntropyValues
호출 결과아직은 Editor's draft 상태이며 Chrome 85에서는 권한 요청 프롬프트가 나타나지 않는다. 하지만 사용자 권한이 필요한 메서드이기 때문에 추후에는 권한 요청 프롬프트가 나타날 수 있고 사용자들은 이 사이트에서 어떤 정보를 수집하는지 알 수 있게 된다.
navigator.userAgentData.getHighEntropyValues
를 통해 비동기 방식으로 정보를 가져오면 기존 User-Agent string으로 얻은 정보와 동일한 정보를 얻을 수 있다. 하지만 모든 코드를 비동기 방식으로 바꾸기 어려울뿐더러 다음 코드와 같이 상수로 사용하는 사람도 있을 것이다.
const userAgent = navigator.userAgent;
// check Mac
export const isMac = userAgent.indexOf("Mac") > -1;
// check iOS
export const isIOS = (isMac || userAgent.indexOf("iPhone") > -1 || userAgent.indexOf("iPad") > -1) && !!navigator.maxTouchPoints && navigator.maxTouchPoints > 0;
// check Chrome
export const isChrome = userAgent.indexOf("Chrome") > -1;
네이버 서비스가 사용하고 있는 agent 모듈 @egjs/agent
(naver/egjs-agent
)도 navigator.userAgent
를 통해 브라우저 및 OS 정보를 가져오고 있다. 그렇기 때문에 UA 프리징에 대한 대비가 필요했다. @egjs/agent
에서는 navigator.userAgentData
를 이용해 어떻게 문제에 대응했는지 알아보겠다.
동기 방식의 대응 방안
먼저 동기 방식으로 해결할 수 있다면 기존 코드와 크게 달라지지 않기 때문에 최선의 해결책일 것이다. 그럼에도 불구하고 동기 방식으로 해결할 수 없는 문제라면 비동기 방식으로 전환을 고려해야 한다.
동기 방식으로 확인할 수 있는 정보는 다음과 같다.
navigator.userAgentData.brands
: 브라우저의 이름과 메이저 버전, Chromium 정보navigator.userAgentData.mobile
: 모바일 여부navigator.platform
: Android Chrome(Linux armv8l) 여부- User-Agent Client Hints를 지원하지 않는 브라우저의 OS, OS 버전(Chrome 84의 지원 범위: Android 5.0 이상, Mac OS X 10.10 이상, 윈도우 7 이상)
다음은 동기 방식으로 사용자 에이전트 정보를 확인하는 예시 코드이다.
import getAgent from "@egjs/agent";
const agent = getAgent();
// Android 확인
export const IS_ANDROID = agent.os.name === "android";
// 브라우저 IE11 확인
export const IS_IE11 = agent.browser.name === "ie" && agent.browser.majorVersion === 11;
// User-Agent Client Hints를 지원하지 않는 Android 4 확인
export const IS_ANDROID4 = IS_ANDROID && agent.os.majorVersion === 4;
// User-Agent Client Hints를 지원하지 않는 iOS 10 확인
export const IS_IOS10 = agent.os.name === "ios" && agent.os.majorVersion === 10;
Safari 추측
.Safari(Safari 14, AppleWebkit 605 기준)는 아직 User-Agent Client Hints를 지원하지 않기 때문에 brand 리스트에 무슨 값이 있을지 알 수 없다. 또한 iOS 정책상 문제로 다른 브라우저 엔진을 사용하고 있는 기존의 브라우저(Chrome, Whale, Edge, Firefox)는 iOS 버전에서는 Safari를 사용한다. 그럼 Safari에서 User-Agent Client Hints가 도입된다면 brand 리스트에는 어떤 값이 있을까? iOS Chrome을 기준으로 다음과 같이 추측할 수 있다.
- [Safari, Chrome]
- [Safari, CriOS]
- [AppleWebkit, Chrome]
- [AppleWebkit, CriOS]
- Safari 기반이고 mobile이면 iOS 이다.
- Safari 기반이고 mobile이 아니면 Mac 이다.
즉, 다음과 같이 사용자 에이전트 정보를 확인할 수 있을 것이다.
import getAgent from "@egjs/agent";
const agent = getAgent();
// iOS 추측 가능
export const IS_IOS = agent.os.name === "ios";
// MAC Safari 추측 가능
export const IS_MAC_SAFARI = agent.os.name === "mac" && agent.browser.name === "safari";
비동기 방식으로 전환
동기 방식으로 확인할 수 없는 정보는 비동기 방식으로 에이전트 값을 얻은 후에 코드가 실행되어야 한다. 동기 방식으로 확인할 수 없는 정보는 다음과 같다.
- Android, iOS, Mac Safari를 제외한 OS 정보
- 특정 OS 버전
- 브라우저의 풀버전
import getAgent from "@egjs/agent";
const agent = getAgent();
// Windows를 확인할 수 없다.
export const IS_WINDOWS = agent.os.name === "window";
// Safari를 제외한 mac OS를 확인할 수 없다.
export const IS_MAC = agent.os.name === "mac";
다음은 navigator.userAgentData.getHighEntropyValues
메서드로 정확한 agent 값을 얻을 수 있도록 만든 getAccurateAgent
비동기 함수이다.
import { getAccurateAgent } from "@egjs/agent";
async <span class="hljs-function">function <span class="hljs-title">start(<span class="hljs-params">) {
const agent = await getAccurateAgent();
const isWindows = agent.os.name === "window";
const isMac = agent.os.name === "mac";
}
마치며
매일 3천만 명 이상이 네이버 서비스를 이용하고 있으며 모든 서비스에서 @egjs/agent
또는 navigator.userAgent
를 사용하고 있기 때문에 네이버는 User-Agent Client Hints 도입에 주목하고 있다. 갑자기 User-Agent Client Hints가 도입된다면 서비스뿐만 아니라 agent 모듈을 사용하고 있는 Component들도 큰 타격을 입을 것이므로 네이버 FE Platform 팀은 User-Agent Client Hints를 리서치하고 @egjs/agent
를 통해 지원하기로 했다.
User-Agent Client Hints는 아직 Editor's Draft 상태(2020.09.18)이며 언제든지 인터페이스 및 동작이 바뀔 수 있기 때문에 계속 주시하고 있고 Chromium뿐만 아니라 Webkit 코드도 확인하면서 빠르게 대응하고자 한다. 여러분도 함께 준비하는 데 이 글이 도움이 되길 바란다.