

こんにちは。AndroidとOkhttp3についての質問です。

Okhttp3（3.10.0）と[PersistentCookieStore](https://gist.github.com/franmontiel/ed12a2295566b7076161)を用いてセッションログインを実装しているのですが、Android7では問題なくログインできるのですがAndroid10ではログインできません。（[ログイン先](https://x.mirm.jp/login)がOSによって違う動作をさせるということはありません。）

通信内容をStethoを用いて調べてみると、タイトルの通りHttpヘッダのSet-
CookieがAndroid10では動いていなかったようです。（※Android7および通常のJRE 11ではSet-
Cookieが動作してCookieとして返し、正しくログインできました。）  
以下に周辺のコードと通信のデバッグ内容を記載します。どなたか原因及び解決法のご教示をお願いいたします。

[code]

    compileSdkVersion 29
    buildToolsVersion "29.0.2"
    minSdkVersion 22
    targetSdkVersion 29
    
[/code]

[code]

    // 便宜上loginを実行する関数
    fun loginTest() {
        API.login("serverId", "my_password")
    }
    
[/code]

[code]

    object API {
        private val gson = Gson()
        var loggedIn = false
        var serverId = ""
    
        // LoginResponseおよびAbstractResponseはレスポンスのデータクラス
        fun login(serverId: String, password: String): LoginResponse {
            try {
                val response = Http.postXWwwFormUrlEncoded(
                    "https://x.mirm.jp/authenticate", mapOf(
                        "serverId" to URLEncoder.encode(serverId),
                        "password" to URLEncoder.encode(password),
                        "_csrf" to getCsrf() // ログインのため一度CSRFトークンを取得する
                    )
                ) ?: return LoginResponse(AbstractResponse.STATUS_ERROR, LoginResponse.LOGIN_STATUS_FAILED)
    
                if (response.contains("期限切れでサーバーが削除されました。")) return LoginResponse(AbstractResponse.STATUS_SUCCEEDED, LoginResponse.LOGIN_STATUS_DELETED_SERVER)
                if (response.contains("サーバー削除ボタンによって削除されています。")) return LoginResponse(AbstractResponse.STATUS_SUCCEEDED, LoginResponse.LOGIN_STATUS_USER_DELETED)
    
                return response.contains("MiRm | コントロールパネル").let {
                    if (it) {
                        this.loggedIn = true
                        this.serverId = serverId
                        return LoginResponse(AbstractResponse.STATUS_SUCCEEDED, LoginResponse.LOGIN_STATUS_SUCCEEDED)
    
                    } else {
                        return LoginResponse(AbstractResponse.STATUS_SUCCEEDED, LoginResponse.LOGIN_STATUS_FAILED)
                    }
                }
    
            } catch (e: MissingRequestException) {
                if (e.errorCode == 503) {
                    return LoginResponse(AbstractResponse.STATUS_OUT_OF_SERVICE, LoginResponse.LOGIN_STATUS_FAILED)
                }
    
            } catch (e: Exception) {
                e.printStackTrace()
            }
            return LoginResponse(AbstractResponse.STATUS_ERROR, LoginResponse.LOGIN_STATUS_FAILED)
        }
    
        private fun getCsrf(): String {
            val document = Jsoup.parse(Http.get("https://x.mirm.jp/login"))
            return document.select("input[name=_csrf]").attr("value") // CSRFトークンを返す
        }
    }
    
[/code]

[code]

    object Http {
    
        private const val USER_AGENT = "MiRmGo/0.0.1"
        private val client: OkHttpClient
        private val headers: Headers
    
        init {
            val cookieHandler = CookieManager(PersistentCookieStore(MyApplication.getApplication()), CookiePolicy.ACCEPT_ALL)
    
            client = OkHttpClient.Builder()
                .cookieJar(JavaNetCookieJar(cookieHandler))
                .addNetworkInterceptor(StethoInterceptor())
                .build()
    
            headers = Headers.Builder()
                .set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8")
                .set("User-agent", USER_AGENT)
                .set("Accept-Language", "ja,en-US;q=0.9,en;q=0.8")
                .set("Connection", "keep-alive")
                .set("Referer", "https://x.mirm.jp/login")
                .set("Origin", "https://x.mirm.jp")
                .build()
        }
    
        fun postXWwwFormUrlEncoded(url: String, data: Map<String, String>): String? {
            val postData = data.let {
                var str = ""
                it.forEach {
                    str += "${it.key}=${URLEncoder.encode(it.value, StandardCharsets.UTF_8.name())}&"
                }
                str.removeSuffix("&")
            }
    
            val requestBody = RequestBody.create(MediaType.parse("application/x-www-form-urlencoded"), postData)
            val request = Request.Builder()
                .url(url)
                .headers(headers)
                .header("Content-Length", postData.length.toString())
                .header("Content-Type", "application/x-www-form-urlencoded")
                .post(requestBody)
                .build()
            val response = client.newCall(request).execute()
    
            if (!response.isSuccessful) throw MissingRequestException(response.code())
            return response.body()?.string()
        }
    
        fun get(url: String, data: Map<String, String> = mapOf()): String? {
            var postUrl = url
    
            if (data.isNotEmpty()) {
                postUrl += "?"
                data.forEach {
                    postUrl += "${it.key}=${it.value}&"
                }
                postUrl = postUrl.removeSuffix("&")
            }
    
            val request = Request.Builder()
                .url(postUrl)
                .headers(headers)
                .build()
            val response = client.newCall(request).execute()
    
            if (!response.isSuccessful) throw MissingRequestException(response.code())
            return response.body()?.string()
        }
    }
    
[/code]

Android7 /login  
[![Android7
/login](https://i.stack.imgur.com/CPRBU.png)](https://i.stack.imgur.com/CPRBU.png)

Android7 /authenticate  
[![Android7
/authenticate](https://i.stack.imgur.com/ldIog.png)](https://i.stack.imgur.com/ldIog.png)

Android10 /login  
[![Android10
/login](https://i.stack.imgur.com/825z9.png)](https://i.stack.imgur.com/825z9.png)

Android10 /authenticate  
[![Android10
/authenticate](https://i.stack.imgur.com/FLAun.png)](https://i.stack.imgur.com/FLAun.png)


CC BY-SA 4.0
2020-04-25T15:48:48.947
65961
2020-04-26T03:59:01.000
2020-04-26T00:36:07.463
32986
30610
question
android http cookie
Android7ではSet-Cookieが動作するがAndroid10では動作しない


`Set-Cookie: (...) ; HttpOnly` 核心はこれでしょうね。

Android 9 (API28) から HTTP はデフォルトで無効化されます。[公式：フレームワーク
セキュリティの変更](https://developer.android.com/about/versions/pie/android-9.0-changes-28?hl=ja#framework-
security-changes)

従って、

  1. サーバー側の動作仕様を、HttpOnly の Cookie に依存しないように修正する

または

  2. クライアントアプリ（Android 9 以上）側では、デフォルトの動作を変更して、「[公式：アプリで特定のドメインのクリアテキストを有効にする](https://developer.android.com/training/articles/security-config?hl=ja)」要するに、特定サイトに対して例外的に HTTP を許可する

必要があります。


CC BY-SA 4.0
2020-04-26T03:59:01.000
65975
2020-04-26T03:59:01.000
7290
65961
answer