Android WebView에서 이미지나 스크립트가 안 나올 때 (Mixed Content, Cleartext Traffic 오류 해결)
Android WebView로 하이브리드 앱을 만들거나 기존 웹 서비스를 앱으로 감싸서 운영하다 보면 꼭 한 번씩 겪는 문제가 있습니다.
"PC나 모바일 브라우저에서는 잘 열리는데, 앱 WebView 안에서만 이미지나 스크립트가 안 보입니다." 하는 상황입니다.
저의 경우도 예전에 운영 서버와 테스트 서버를 오가며 세팅하다가 이 문제로 한참 헤맨 적이 있어서, 나중에도 참고하려고 깔끔하게 정리해 둡니다. 단순 화면 깨짐으로 넘기기에는 보안상 중요한 부분이라 제대로 짚고 넘어가야 합니다.
원인 및 차이점 간단 정리
원인은 크게 Mixed Content(혼합 콘텐츠)와 Cleartext Traffic(일반 텍스트 트래픽) 두 가지로 나뉩니다. 둘 다 HTTP 통신 보안 정책 때문에 발생하는 문제인데, 확인해야 하는 위치가 다릅니다.
Mixed Content: 메인 페이지는
https://로 안전하게 열렸는데, 그 안에서 불러오는 이미지, JS, CSS, 동영상 등의 주소가http://로 되어 있는 경우입니다. WebView가 보안상 이 리소스 로드를 차단해 버립니다. (WebView 설정 영역)Cleartext Traffic: 앱이 암호화되지 않은 생짜
http://통신 자체를 시도하는 것을 말합니다. Android 9 (API 28) 이상부터는 기본적으로 이 통신이 전부 차단됩니다. (Network Security Config, Manifest 영역)
가장 좋은 해결책은 당연히 모든 리소스를 HTTPS로 바꾸는 것입니다. 하지만 개발 중이거나 부득이한 예외 상황이 있다면 아래처럼 안전하게 도메인을 제한해서 풀어야 합니다.
안전한 WebView 설정 (Kotlin 코드)
화면이 깨진다고 해서 운영 앱의 WebView 설정을 MIXED_CONTENT_ALWAYS_ALLOW로 막 열어버리면 절대 안 됩니다. 중간에서 데이터가 변조될 위험이 크고 구글 플레이 심사에서도 거절 사유가 될 수 있습니다.
운영 앱에서는 MIXED_CONTENT_NEVER_ALLOW를 기본으로 두고, 아래처럼 안전하게 필요한 검증을 거치도록 세팅하는 것이 정답입니다.
class SecureMixedContentWebView(
private val allowedHosts: Set<String>,
) {
fun configure(webView: WebView) {
webView.settings.apply {
javaScriptEnabled = true
domStorageEnabled = true
// 운영 앱에서는 무조건 NEVER_ALLOW가 안전합니다.
mixedContentMode = WebSettings.MIXED_CONTENT_NEVER_ALLOW
allowFileAccess = false
allowContentAccess = false
}
webView.webViewClient = object : WebViewClient() {
override fun shouldOverrideUrlLoading(
view: WebView,
request: WebResourceRequest,
): Boolean {
val uri = request.url
val scheme = uri.scheme ?: return true
val host = uri.host ?: return true
// 허용된 도메인의 HTTPS 주소만 WebView 내부에서 처리
if (scheme == "https" && allowedHosts.contains(host)) {
return false
}
// HTTP 주소는 내부에서 열지 않고 차단하거나 외부 브라우저로 토스
if (scheme == "http") {
Toast.makeText(
view.context,
"안전하지 않은 링크는 열 수 없습니다.",
Toast.LENGTH_SHORT,
).show()
return true
}
openExternal(view.context, uri)
return true
}
override fun onReceivedSslError(
view: WebView,
handler: SslErrorHandler,
error: SslError,
) {
// 주의: 당장 화면 띄우겠다고 handler.proceed() 쓰면 절대 안 됩니다.
// 인증서 오류는 신뢰가 깨진 신호이므로 취소하는 게 맞습니다.
handler.cancel()
}
}
}
private fun openExternal(context: Context, uri: Uri) {
val intent = Intent(Intent.ACTION_VIEW, uri)
if (intent.resolveActivity(context.packageManager) != null) {
context.startActivity(intent)
}
}
}
로컬/테스트 서버 예외 처리 방법 (Network Security Config)
개발하다 보면 로컬 컴퓨터나 사내 테스트 서버(http://...)에 앱을 붙여야 할 때가 있습니다.
이때 귀찮다고 AndroidManifest.xml에 android:usesCleartextTraffic="true"를 넣어 앱 전체의 보안을 풀어버리는 실수를 자주 합니다. 이렇게 하면 안 되고, 딱 필요한 테스트 도메인만 지정해서 예외를 둬야 합니다.
우선 res/xml/network_security_config.xml 파일을 만들고 아래처럼 세팅합니다.
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="false" />
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">insecure-dev.example.com</domain>
</domain-config>
</network-security-config>
그다음 AndroidManifest.xml에 해당 설정을 연결해 줍니다. 여기서는 당연히 usesCleartextTraffic을 false로 잡거나 아예 지워주는 게 안전합니다.
<application
android:networkSecurityConfig="@xml/network_security_config"
android:usesCleartextTraffic="false">
</application>
이러면 운영 도메인은 안전하게 HTTPS로 돌리면서, 테스트 도메인만 예외로 HTTP 통신을 열어줄 수 있습니다. 더 안전하게 하려면 이 설정 파일 자체를 debug 소스 세트 폴더에만 넣어두고 release 빌드에서는 아예 빠지도록 분리하는 편이 좋습니다.
실무 적용 시 주의할 점 (체크리스트)
이래저래 서비스를 운영하다 보면 코드가 아니라 콘텐츠나 API 응답 때문에 문제가 생기는 경우가 많습니다. WebView 옵션을 건드리기 전에 아래 항목들을 먼저 체크해 보세요.
HTML 내부의 정적 리소스 URL 확인: 페이지 내부의 이미지, CSS, 스크립트 경로가
http://로 하드코딩 되어 있는지 검색해 봅니다. (CDN이나 정적 파일 서버 설정을 HTTPS로 바꾸는 게 근본적인 해결책입니다.)API 응답 값 확인: 백엔드 API가 리턴해 주는 이미지 경로 파일 주소에
http://가 섞여 들어오지 않는지 확인합니다.SSL 오류 발생 시 무조건 통과 금지: 인증서 만료나 사설 인증서 때문에 에러가 날 때
handler.proceed()로 대충 우회해 두면, 당장은 잘 작동하는 것처럼 보여도 중간자 공격(MitM)에 취약해집니다. 운영 앱에서는 무조건 서버 인증서 구성을 고쳐야 합니다.실제 도메인 노출 주의: 블로그나 깃허브에 예제 코드를 올리거나 로그를 남길 때는 내부 운영 서버 주소나 API 키가 쌩으로 노출되지 않도록
example.com같은 예시 도메인으로 치환하는 버릇을 들여야 합니다.
마무리
결론적으로 WebView에서 화면이 깨지거나 리소스가 안 나올 때, 앱 설정을 낮춰서 편하게 우회하는 건 임시방편일 뿐입니다.
MIXED_CONTENT_NEVER_ALLOW와 cleartext 비허용을 뼈대로 잡고, HTTP로 나오는 소스들을 찾아서 HTTPS로 마이그레이션하는 방향으로 가야 운영 관점에서 뒤탈이 없습니다. 하이브리드 앱 개발할 때 자꾸 까먹는 포인트라 기록해 두니 필요할 때 참고하시기 바랍니다.
댓글
댓글 쓰기