Google OAuth Client Library for Java

2 分钟读完

记录一下Google OAuth Java Client的学习过程,这套Lib可以很方便的集成第三方OAuth接口,如果对方没有提供SDK,这将是一个非常不错的选择。不止针对Google,任何第OAuth API都可以使用。该Lib包括了OAuth授权的流程,与Token的过期自动刷新功能。

安装

添加依赖管理器

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>com.google.cloud</groupId>
            <artifactId>libraries-bom</artifactId>
            <version>2.2.0</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

添加依赖,这里使用了最新的Apache HTTP Client,否则无法支持PATCH METHOD

<dependency>
    <groupId>com.google.oauth-client</groupId>
    <artifactId>google-oauth-client</artifactId>
    <version>1.30.4</version>
</dependency>
<dependency>
    <groupId>com.google.api-client</groupId>
    <artifactId>google-api-client-jackson2</artifactId>
    <version>1.30.4</version>
</dependency>
<dependency>
    <groupId>com.google.http-client</groupId>
    <artifactId>google-http-client-apache-v2</artifactId>
    <version>1.35.0</version>
</dependency>
<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.5.12</version>
</dependency>

初始化OAuthFlow

初始化所需参数

val httpTransport by lazy { ApacheHttpTransport() } //此处使用ApacheHttpTransport替代了默认的实现
val jacksonFactory: JacksonFactory = JacksonFactory.getDefaultInstance()
val clientId: String = "Client Id"
val clientSecret: String = "Client Secret"
val tokenServerUrl: String = "Token Server URL"
val authServerUrl: String = "Auth Server URL"
val callbackUrl: String = "Call Back URL"

初始化OAuthFlow

AuthorizationCodeFlow.Builder(
            BearerToken.authorizationHeaderAccessMethod(),
            httpTransport, jacksonFactory, GenericUrl(tokenServerUrl),
            BasicAuthentication(clientId, clientSecret), 
            clientId, authServerUrl)
            .setCredentialDataStore(Global.getBean(CredentialDataStore::class.java))  //此处从SpringContent中获取了持久化令牌的实现,稍后会展示其代码。 
            .build()

授权流程

授权流程第一步,获取授权跳转连接

val authUrl = oauthFlow.newAuthorizationUrl()
authUrl.state = UUID.randomUUID().toString()  //此处state用于跟踪授权请求,视情况添加,一般会存储state与userId的映射,并为第二步准备。
authUrl.redirectUri = callbackUrl   //指定回调地址
return authUrl

授权流程第二步,使用回调返回的state和code获取AccessToken与RefreshToken

val authReq = oauthFlow.newTokenRequest(authorizationCode)  // 此处参数为回调返回的code
authReq.redirectUri = callbackUrl
val tokenResponse = authReq.execute()
return oauthFlow.createAndStoreCredential(tokenResponse, userId)  //此处的userId可以通过第一步存储的state与userId映射获取,这一步会调用DataStore中的方法存储令牌信息。

准备请求工厂与持久化组件

定义令牌初始化类

class CredentialInitializer(private val credential: Credential) : HttpRequestInitializer {
    override fun initialize(request: HttpRequest) {
        credential.initialize(request)
        request.parser = JsonObjectParser(jacksonFactory)
    }
}

通过userId获取请求工厂

/*
 * 从DataStore中加载指定userId的令牌
 */
fun loadCredential(userId: String): Credential? {
    return oauthFlow.loadCredential(userId)
}

fun getRequestFactory(userId: String): HttpRequestFactory? {
    val credential = loadCredential(userId)?: return null
    return httpTransport.createRequestFactory(CredentialInitializer(credential))
}

定义DataStoreFactory,此处没有写实现,因为此处DataStoreFactory只用于OAuth

class CredentialDataStoreFactory : AbstractDataStoreFactory() {
    override fun <V : Serializable?> createDataStore(id: String?): DataStore<V> = throw UnsupportedOperationException()
}

定义DataStore实现,Google提供了一些默认的实现,只用于存储在内存或者文件中,视情况使用

import com.google.api.client.auth.oauth2.StoredCredential
import com.google.api.client.util.store.AbstractDataStore
import com.google.api.client.util.store.DataStore

@Component
class CredentialDataStore : AbstractDataStore<StoredCredential>(CredentialDataStoreFactory(),
        StoredCredential.DEFAULT_DATA_STORE_ID) {

    override fun get(key: String): StoredCredential? {
        // 此处可以自己选择使用什么方式持久化令牌。
    }

    override fun set(key: String, value: StoredCredential): DataStore<StoredCredential> {
        // 此处可以自己选择使用什么方式持久化令牌。
    }

    override fun delete(key: String): DataStore<StoredCredential> {
        // 此处可以自己选择使用什么方式持久化令牌。
    }

    override fun clear(): DataStore<StoredCredential> = throw UnsupportedOperationException()

    override fun values(): MutableCollection<StoredCredential> = throw UnsupportedOperationException()

    override fun keySet(): MutableSet<String> = throw UnsupportedOperationException()
}

使用

定义URL, GenericUrl类可自动根据子类添加了@Key注解的属性生成带有queryParam的URL,您可根据具体的第三方API定义子类,此处PageableUrl模拟了添加特定的分页queryParam


abstract class PageableUrl  (
        url: String,
        @Key("page_size") val pageSize: Int = 300,
        @Key("page_number") var pageNumber: Int = 1
) : GenericUrl(url)

class UserUrl(@Key val status: String) : PageableUrl("https://api.test.com/v2/users")

例如:UserUrl("active")生成的URL为:

https://api.test.com/v2/users?page_number=1&page_size=300&status=active

实体定义, 所有需要进行序列化操作的字段都要添加@Key注解

data class User (
        @Key var id: String? = null,
        @Key("first_name") var firstName: String? = null,
        @Key("last_name") var lastName: String? = null
)

生成GET请求

val getUserRequest = requestFactory.buildGetRequest(userUrl)

分享一个自动获取所有分页数据的代码

fun <D: Any, T: PageResp<D>> getAllData(req: HttpRequest, kClass: KClass<T>, filter: ((D) -> Boolean)? = null): List<D> {
    val genericUrl = req.url
    val mutableList = mutableListOf<D>()
    return if(genericUrl is PageableUrl){
        var pageNum = 0
        var pageCount: Int
        do {
            pageNum ++
            genericUrl.pageNumber = pageNum
            val resp = req.execute().parseAs(kClass.java)  //此处会自动将返回的JSON反序列化
            mutableList.addAll(resp.getData().run {
                filter?.let { this.filter { filter(it) } }?:this
            })
            pageCount = resp.pageCount
        } while(pageCount < pageNum)
         mutableList.toList()
    } else throw IllegalArgumentException("Url Not Pageable Url")
}

与自动获取所有分页数据代码配套的类

abstract class PageResp<T> (
        @Key("page_count") var pageCount: Int = 1
) : DataResp<T>

interface DataResp<T> {
    fun getData(): List<T>
}

GitHub 地址:

https://github.com/googleapis/google-oauth-java-client

Google帮助文档(GitHub Wiki中也有帮助文档):

https://developers.google.cn/api-client-library/java/google-api-java-client/oauth2?hl=zh-cn