1. 문제상황
끼리끼리를 업데이트하려고 구글플레이 콘솔에 들어갔더니 이런 경고가 표시됨
그래서 안전하지 않은 WebView SSL 오류 핸들러 구현 이 무엇인지 찾아봄
@Composable
fun LegalScreen(navHostController: NavHostController) {
LegalContent(navHostController)
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun LegalContent(navHostController: NavHostController) {
val webView = rememberWebView()
Scaffold(
modifier = Modifier
.fillMaxSize(),
topBar = {
TopAppBar(
title = "개인정보처리방침/이용약관",
navigationIconImage = ImageVector.vectorResource(id = R.drawable.arrow_back_ios_24px),
navigationIconOnClick = {
navHostController.popBackStack(MainScreenName.SCREEN_LEGAL.name, true)
},
isDivider = false
)
}
) { innerPadding ->
Column(
modifier = Modifier
.padding(innerPadding)
) {
AndroidView(
factory = { webView },
modifier = Modifier.fillMaxSize()
)
}
}
}
@SuppressLint("SetJavaScriptEnabled")
@Composable
fun rememberWebView(): WebView {
val context = LocalContext.current
val webView = remember {
WebView(context).apply {
settings.apply {
javaScriptEnabled = true
domStorageEnabled = true // localStorage 등을 위해 필요
mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW // HTTPS/HTTP 혼합 컨텐츠 허용
}
webViewClient = object : WebViewClient() {
override fun onReceivedSslError(
view: WebView?,
handler: SslErrorHandler?,
error: SslError?
) {
handler?.proceed() // SSL 에러 무시하고 진행
}
}
loadUrl("앱의 개인정보처리방침,이용약관을 담은 구글사이트")
}
}
return webView
}
여기서
webViewClient = object : WebViewClient() {
override fun onReceivedSslError(
view: WebView?,
handler: SslErrorHandler?,
error: SslError?
) {
handler?.proceed() // SSL 에러 무시하고 진행
}
}
이 부분이 문제였던것이다
SSL에러를 무시하고 웹을 표시할경우 구글의 " 기기 및 네트워크 악용 정책" 위반한다고 한다
SSL오류가 무엇인지 바로 LLM에 물어봤다
✅ SSL이란?
SSL(Secure Sockets Layer)은 웹사이트와 사용자의 브라우저 사이의 데이터를 암호화하는 보안 프로토콜입니다.
지금은 TLS라는 이름으로 발전했지만, 흔히 SSL이라고 부릅니다.
📶 예시:
- 주소창에 🔒 자물쇠가 있는 웹사이트 → SSL 인증서를 가지고 있는 사이트
- 예: https://www.google.com
❗ SSL 오류란?
웹사이트의 SSL 인증서에 문제가 있을 때 발생하는 오류입니다.
🔸 주요 원인
- 만료된 인증서
- 신뢰할 수 없는 인증서 발급기관(CA)
- 도메인 이름과 인증서가 불일치
- 자체 서명된 인증서(공식 CA 아닌 경우)
🧨 결과
브라우저나 앱이 경고창을 띄우며 이 웹사이트는 안전하지 않음을 표시합니다.
SSL 오류가 있으면 **중간자 공격(Man-in-the-Middle)**에 노출될 위험이 있습니다.
핵심은 해킹위협에 사용자가 노출될수있다는것이다
그래서 위 코드를 아래와같이 수정했다
@Composable
fun LegalScreen(navHostController: NavHostController) {
LegalContent(navHostController)
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun LegalContent(navHostController: NavHostController) {
val context = LocalContext.current
val showDialog = remember { mutableStateOf(false) }
val pendingSslHandler = remember { mutableStateOf<SslErrorHandler?>(null) }
val webView = remember {
WebView(context).apply {
settings.apply {
javaScriptEnabled = true
domStorageEnabled = true
mixedContentMode = WebSettings.MIXED_CONTENT_COMPATIBILITY_MODE
}
webViewClient = object : WebViewClient() {
override fun onReceivedSslError(
view: WebView?,
handler: SslErrorHandler?,
error: SslError?
) {
// 다이얼로그 표시 요청
pendingSslHandler.value = handler
showDialog.value = true
}
}
loadUrl("https://sites.google.com/view/ggiriggiri-information")
}
}
// 다이얼로그 컴포저블
if (showDialog.value) {
CustomAlertDialog(
onDismiss = {
showDialog.value = false
pendingSslHandler.value?.cancel() // 취소 시 로딩 중지
},
onConfirmation = {
showDialog.value = false
pendingSslHandler.value?.proceed() // 확인 시 계속 진행
},
icon = Icons.Default.Info,
dialogTitle = "SSL 인증서 경고",
dialogText = "이 페이지는 보안 인증서에 문제가 있습니다.\n그래도 계속 진행하시겠습니까?"
)
}
Scaffold(
modifier = Modifier.fillMaxSize(),
topBar = {
TopAppBar(
title = "개인정보처리방침/이용약관",
navigationIconImage = ImageVector.vectorResource(id = R.drawable.arrow_back_ios_24px),
navigationIconOnClick = {
navHostController.popBackStack(MainScreenName.SCREEN_LEGAL.name, true)
},
isDivider = false
)
}
) { innerPadding ->
Column(
modifier = Modifier.padding(innerPadding)
) {
AndroidView(
factory = { webView },
modifier = Modifier.fillMaxSize()
)
}
}
}