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

Posted

技术标签:

【中文标题】如何在 Kotlin 中使用 PKCE 实现 Spotify 授权代码【英文标题】:How to Impement Spotify's Authorization Code With PKCE in Kotlin 【发布时间】:2021-10-15 09:36:16 【问题描述】:

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

【问题讨论】:

【参考方案1】:

其中一种方法是使用 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 身份验证活动创建意图。

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

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

您可以在此处为授权活动的结果注册一个回调,该活动将使用我们在上一步中创建的意图:

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 身份验证活动创建另一个意图。这与步骤 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"

【讨论】:

这一切都会在活动中进行吗? 可能是这样,但我已将getLoginActivityCodeIntentgetLoginActivityTokenIntentcompanion object 放在与授权流程相关的单独类中。 嘿@OEThe11,我的回答解决了你的问题吗? 是的,确实如此。欣赏它。【参考方案2】:

我认为接受的解决方案实际上不是带有 PKCE 的授权代码流。我相信@sweak 提出的解决方案中发生的情况是,首先您启动授权代码流程,但随后您放弃它并启动新的隐式授权流程。这就是为什么它不支持刷新令牌,因为@sweak 自己提到了here。

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

我目前正在努力使 PKCE 也可以与 Android SDK 一起使用。如果我能做到这一点,我会更新答案。

【讨论】:

以上是关于如何在 Kotlin 中使用 PKCE 实现 Spotify 授权代码的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 PKCE 为 React 单页应用程序实现 OAuth2 授权代码授予?

如何使用 PKCE for Spotify 实现授权代码

在带有 PKCE 的 OAuth 授权流中使用时如何在 Azure 应用注册中启用 CORS?

如何配置 keycloak-(nodejs-)connect 以使用 PKCE?

如何使用 PKCE 登录 Google.Apis.Drive.v3 for .NET?

使用 PKCE 的授权代码流如何比没有 client_secret 的授权代码流更安全