如何在 Kotlin 中使用 PKCE 实现 Spotify 授权码

How to Impement Spotify's Authorization Code With PKCE in Kotlin

所以我想使用 Spotify 的 Web API。我已经阅读了一些您需要使用 PKCE 实现身份验证代码的文档。我不是 100% 确定如何做到这一点,可以寻求一些帮助。

其中一种方法是使用 Spotify 授权库。 在开始之前将以下依赖项添加到您的 Android 项目中:

// Spotify authorization
implementation 'com.spotify.android:auth:1.2.5'

然后按照 Authorization Guide:

中的步骤开始编码

1.创建代码验证器并挑战

helpful article 有助于处理授权流程的初始加密部分。您可以快速阅读一下。

编码的第一步是创建一个 companion object,我们将在其中存储诸如 CLIENT_ID 或代码验证器和代码挑战之类的内容:

companion object {
        const val CLIENT_ID = "your_client_id"
        const val REDIRECT_URI = "https://com.company.app/callback"

        val CODE_VERIFIER = getCodeVerifier()

        private fun getCodeVerifier(): String {
            val secureRandom = SecureRandom()
            val code = ByteArray(64)
            secureRandom.nextBytes(code)
            return Base64.encodeToString(
                code,
                Base64.URL_SAFE or Base64.NO_WRAP or Base64.NO_PADDING
            )
        }

        fun getCodeChallenge(verifier: String): String {
            val bytes = verifier.toByteArray()
            val messageDigest = MessageDigest.getInstance("SHA-256")
            messageDigest.update(bytes, 0, bytes.size)
            val digest = messageDigest.digest()
            return Base64.encodeToString(
                digest,
                Base64.URL_SAFE or Base64.NO_WRAP or Base64.NO_PADDING
            )
        }
    }

2。构造授权URI

这一步归结为使用 AuthorizationRequest.BuilderAuthorizationClient 为 Spotify 身份验证创建意图 activity。

在那里您将提供指南中的所有必要参数:

fun getLoginActivityCodeIntent(): Intent =
        AuthorizationClient.createLoginActivityIntent(
            activity,
            AuthorizationRequest.Builder(CLIENT_ID, AuthorizationResponse.Type.CODE, REDIRECT_URI)
                .setScopes(
                    arrayOf(
                        "user-library-read", "user-library-modify",
                        "app-remote-control", "user-read-currently-playing"
                    )
                )
                .setCustomParam("code_challenge_method", "S256")
                .setCustomParam("code_challenge", getCodeChallenge(CODE_VERIFIER))
                .build()
        )

3。您的应用将用户重定向到授权 URI

在这里您可以为授权结果注册一个回调activity,它将使用我们在上一步中创建的意图:

private val showLoginActivityCode = registerForActivityResult(
        ActivityResultContracts.StartActivityForResult()
    ) { result: ActivityResult ->

        val authorizationResponse = AuthorizationClient.getResponse(result.resultCode, result.data)

        when (authorizationResponse.type) {
            AuthorizationResponse.Type.CODE ->
                // Here You will get the authorization code which you
                // can get with authorizationResponse.code
            AuthorizationResponse.Type.ERROR ->
                // Handle the Error
            else ->
                // Probably interruption
        }
    }


// Usage:
showLoginActivityCode.launch(getLoginActivityCodeIntent())

在那里您可以访问授权码 - authorizationResponse.code。下一步会用到。

4。您的应用将代码交换为访问令牌

这里我们必须为 Spotify 身份验证创建另一个意图 activity。这与步骤 2 中的代码非常相似。在 getLoginActivityTokenIntent 中,您必须提供从上一步中检索到的代码:

fun getLoginActivityTokenIntent(code: String): Intent =
        AuthorizationClient.createLoginActivityIntent(
            activity,
            AuthorizationRequest.Builder(CLIENT_ID, AuthorizationResponse.Type.TOKEN, REDIRECT_URI)
                .setCustomParam("grant_type", "authorization_code")
                .setCustomParam("code", code)
                .setCustomParam("code_verifier", CODE_VERIFIER)
                .build()
        )

然后创建回调:

private val showLoginActivityToken = registerForActivityResult(
        ActivityResultContracts.StartActivityForResult()
    ) { result: ActivityResult ->

        val authorizationResponse = AuthorizationClient.getResponse(result.resultCode, result.data)

        when (authorizationResponse.type) {
            AuthorizationResponse.Type.TOKEN -> {
                // Here You can get access to the authorization token
                // with authorizationResponse.token
            }
            AuthorizationResponse.Type.ERROR ->
                // Handle Error
            else ->
                // Probably interruption
        }
    }


// Usage:
showLoginActivityToken.launch(getLoginActivityTokenIntent(authorizationCode))

授权部分到此结束 - 您已经获得授权令牌 - authorizationResponse.token。保存它,它将用于创建对 Spotify Web 的请求 API.

5.使用访问令牌访问 Spotify Web API

您可以开始使用API。使用 Retrofit 的简单预览示例:

interface SpotifyApi {

    companion object {
        const val BASE_URL = "https://api.spotify.com/v1/"
    }

    @GET("me")
    suspend fun getMe(@Header("Authorization") bearerWithToken: String): User
}

请注意 bearerWithToken 参数应如下所示:"Bearer {your_access_token}".

我认为公认的解决方案实际上不是使用 PKCE 的授权代码流。我相信 @sweak's proposed solution is that at first you start Authorization Code Flow but then you abandon it and initiate new Implicit Grant Flow. That is why it does not support the refresh token as @sweak himself mentioned .

发生的事情

问题是,为了在授权代码流(有或没有 PKCE)中交换令牌代码,您应该请求 /api/token 端点,但是 Spotify Android SDK 的登录 activity请求 /authorize 端点,我猜它只是忽略了自定义参数,例如 grant_type.

我目前正在努力使 PKCE 也与 Android SDK 一起工作。如果我设法做到这一点,我会更新答案。