추가 HTTP 요청 헤더 추가

HTTP 요청에는 User-Agent 또는 Content-Type과 같은 헤더가 포함됩니다. 브라우저에서 연결한 헤더 외에도 Android 앱은 EXTRA_HEADERS 인텐트 추가를 통해 쿠키 또는 리퍼러와 같은 추가 헤더를 추가할 수 있습니다. 보안상의 이유로 Chrome은 인텐트가 실행되는 방식과 위치에 따라 일부 추가 헤더를 필터링합니다.

교차 출처 요청은 클라이언트와 서버가 동일한 당사자의 소유가 아니므로 추가 보안 계층이 필요합니다. 이 가이드에서는 Chrome 맞춤 탭을 통해 이러한 요청을 실행하는 방법을 설명합니다. 즉, 브라우저 탭에서 URL을 여는 앱에서 실행되는 인텐트입니다. Chrome 83 이전에는 개발자가 맞춤 탭을 실행할 때 헤더를 추가할 수 있었습니다. 버전 83부터 Chrome은 승인 목록에 없는 헤더가 보안 위험을 야기하므로 승인 목록에 추가된 교차 출처 헤더를 제외한 모든 헤더를 필터링하기 시작했습니다. Chrome 86부터 서버와 클라이언트가 디지털 애셋 링크를 사용하여 연결된 경우 승인 목록에 없는 헤더를 교차 출처 요청에 연결할 수 있습니다. 이 동작은 다음 표에 요약되어 있습니다.

Chrome 버전 허용된 CORS 헤더
Chrome 83 이전 승인 목록에 추가됨, 승인 목록에 추가되지 않음
Chrome 83~85 승인 목록에 추가됨
Chrome 86부터 디지털 애셋 링크가 설정된 경우 승인 목록에 추가됨, 승인 목록에 추가되지 않음

표 1: 승인 목록에 없는 CORS 헤더 필터링

이 도움말에서는 서버와 클라이언트 간에 인증된 연결을 설정하고 이를 사용하여 승인 목록에 추가된 HTTP 헤더와 승인 목록에 추가되지 않은 HTTP 헤더를 전송하는 방법을 보여줍니다. 코드는 맞춤 탭 인텐트에 추가 헤더 추가를 참고하세요.

배경

승인된 CORS 요청 헤더와 승인되지 않은 CORS 요청 헤더

교차 출처 리소스 공유 (CORS)를 사용하면 한 출처의 웹 애플리케이션이 다른 출처의 리소스를 요청할 수 있습니다. CORS-approvelisted 헤더 목록은 HTML 표준에 유지됩니다. 승인 목록에 추가된 헤더의 예는 다음 표를 참고하세요.

헤더 설명
accept-language 클라이언트가 이해하는 자연어를 광고합니다.
content-language 현재 대상에 적합한 언어를 설명합니다.
content-type 리소스의 미디어 유형을 나타냅니다.

표 2.: 승인 목록에 추가된 CORS 헤더의 예입니다.

승인된 헤더는 민감한 사용자 정보를 포함하지 않으며 서버가 잠재적으로 손상을 입힐 수 있는 작업을 실행할 가능성이 낮으므로 안전한 것으로 간주됩니다.

승인 목록에 없는 헤더의 예는 다음 표에 나와 있습니다.

헤더 설명
bearer-token 서버에서 클라이언트를 인증합니다.
origin 요청의 출처를 나타냅니다.
쿠키 서버에서 설정한 쿠키를 포함합니다.

표 3.: 승인 목록에 없는 CORS 헤더의 예입니다.

승인 목록에 없는 헤더를 CORS 요청에 연결하는 것은 HTML 표준에서 권장하지 않으며 서버는 교차 출처 요청에 승인 목록에 있는 헤더만 포함되어 있다고 가정합니다. 교차 출처 도메인에서 승인되지 않은 헤더를 전송하면 악성 서드 파티 앱이 Chrome (또는 다른 브라우저)이 저장하고 요청에 첨부하는 사용자 쿠키를 오용하는 헤더를 작성할 수 있습니다. 쿠키는 그렇지 않으면 불가능한 악성 서버 트랜잭션을 인증할 수 있습니다.

CORS 승인 목록에 추가된 헤더를 맞춤 탭 요청에 연결

맞춤 탭은 맞춤설정된 브라우저 탭에서 웹페이지를 실행하는 특별한 방법입니다. CustomTabsIntent.Builder()를 사용하여 맞춤 탭 인텐트를 만들 수 있습니다. Browser.EXTRA_HEADERS 플래그와 함께 Bundle를 사용하여 이러한 인텐트에 헤더를 첨부할 수도 있습니다.

CustomTabsIntent intent = new CustomTabsIntent.Builder(session).build();

Bundle headers = new Bundle();
headers.putString("bearer-token", "Some token");
headers.putString("redirect-url", "Some redirect url");   
intent.intent.putExtra(Browser.EXTRA_HEADERS, headers);

intent.launchUrl(Activity.this, Uri.parse("http://www.google.com"));

승인 목록에 추가된 헤더는 항상 맞춤 탭 CORS 요청에 연결할 수 있습니다. 하지만 Chrome은 기본적으로 승인 목록에 없는 헤더를 필터링합니다. 다른 브라우저의 동작은 다를 수 있지만 개발자는 일반적으로 승인 목록에 없는 헤더가 차단될 것으로 예상해야 합니다.

맞춤 탭에 승인 목록에 없는 헤더를 포함하는 지원되는 방법은 디지털 액세스 링크를 사용하여 교차 출처 연결을 먼저 확인하는 것입니다. 다음 섹션에서는 이를 설정하고 필수 헤더를 사용하여 맞춤 탭 인텐트를 실행하는 방법을 보여줍니다.

맞춤 탭 인텐트에 추가 헤더 추가

승인 목록에 없는 헤더가 맞춤 탭 인텐트를 통해 전달되도록 하려면 작성자가 두 애플리케이션을 모두 소유하고 있음을 확인하는 Android와 웹 애플리케이션 간 디지털 애셋 링크를 설정해야 합니다.

공식 가이드에 따라 디지털 애셋 링크를 설정합니다. 링크가 확인되면 두 앱이 동일한 출처에 속함을 나타내는 'delegate_permission/common.use_as_origin' 링크 관계를 사용합니다.

추가 헤더를 사용하여 맞춤 탭 인텐트 만들기

맞춤 탭 인텐트를 만드는 방법에는 여러 가지가 있습니다. 빌드 종속 항목에 라이브러리를 추가하여 AndroidX에서 제공되는 빌더를 사용할 수 있습니다.

implementation 'androidx.browser:browser:1.2.0'

인텐트를 빌드하고 추가 헤더를 추가합니다.

CustomTabsIntent constructExtraHeadersIntent(CustomTabsSession session) {
    CustomTabsIntent intent = new CustomTabsIntent.Builder(session).build();

    // Example non-cors-approvelisted headers.
    Bundle headers = new Bundle();
    headers.putString("bearer-token", "Some token");
    headers.putString("redirect-url", "Some redirect url");
    intent.intent.putExtra(Browser.EXTRA_HEADERS, headers);
    return intent;
}

맞춤 탭 연결은 앱과 Chrome 탭 간에 CustomTabsSession를 설정하는 데 사용됩니다. 앱과 웹 앱이 동일한 출처에 속하는지 확인하려면 세션이 필요합니다. 디지털 애셋 링크가 올바르게 설정된 경우에만 인증이 통과됩니다.

CustomTabsClient.warmup()를 호출하는 것이 좋습니다. 이를 통해 브라우저 애플리케이션이 백그라운드에서 사전 초기화되고 URL 열기 프로세스가 빨라집니다.

// Set up a connection that warms up and validates a session.
CustomTabsServiceConnection connection = new CustomTabsServiceConnection() {
    @Override
    public void onCustomTabsServiceConnected(@NonNull ComponentName name, 
        @NonNull CustomTabsClient client) {
        // Create session after service connected.
        mSession = client.newSession(callback);
        client.warmup(0);
        // Validate the session as the same origin to allow cross origin headers.
        mSession.validateRelationship(CustomTabsService.RELATION_USE_AS_ORIGIN, 
            Uri.parse(url), null);
    }
    @Override
    public void onServiceDisconnected(ComponentName componentName) { }
};

유효성 검사 후 인텐트를 실행하는 콜백 설정

CustomTabsCallback가 세션에 전달되었습니다. 출처 확인이 성공하면 이전에 생성된 CustomTabsIntent를 실행하도록 onRelationshipValidationResult()를 설정합니다.

// Set up a callback that launches the intent after session validated.
CustomTabsCallback callback = new CustomTabsCallback() {
    @Override
    public void onRelationshipValidationResult(int relation, @NonNull Uri requestedOrigin, 
        boolean result, @Nullable Bundle extras) {
        // Launch custom tabs intent after session was validated as the same origin.
        CustomTabsIntent intent = constructExtraHeadersIntent(mSession);
        intent.launchUrl(MainActivity.this, Uri.parse(url));
    }
};

맞춤 탭 서비스 연결 바인딩

서비스를 바인딩하면 서비스가 실행되고 연결의 onCustomTabsServiceConnected()가 결국 호출됩니다. 서비스를 적절하게 바인드 해제하는 것을 잊지 마세요. 바인딩 및 바인딩 해제는 일반적으로 onStart() 및 onStop() 활동 수명 주기 메서드에서 실행됩니다.

// Bind the custom tabs service connection.
// Call this in onStart()
CustomTabsClient.bindCustomTabsService(this,
    CustomTabsClient.getPackageName(MainActivity.this, null), connection);

// …
// Unbind the custom tabs service.
// Call this in onStop().
unbindService(connection);

데모 애플리케이션 코드

맞춤 탭 서비스에 대한 자세한 내용은 여기를 참고하세요. 작동하는 예시 앱은 android-browser-helper GitHub 저장소를 참고하세요.

요약

이 가이드에서는 임의의 헤더를 맞춤 탭 CORS 요청에 추가하는 방법을 보여줍니다. 승인된 헤더는 모든 맞춤 탭 CORS 요청에 연결할 수 있습니다. 승인 목록에 없는 헤더는 일반적으로 CORS 요청에서 안전하지 않은 것으로 간주되며 Chrome에서 기본적으로 필터링합니다. 이러한 파일을 첨부하는 것은 디지털 애셋 링크로 인증된 동일한 출처의 클라이언트와 서버에만 허용됩니다.