SalesForce Server-to-Server Flow

最近有个项目,需要对接把系统的master数据同步到salesforce。
第一次接触salesforce,认证这块儿也不是特别懂。
查阅了官方的文档,因为这次对接是服务器到服务器端,最后发现有下面两种方式可以采用:

Username-Password Flow的话,实现起来比较简单,像下面这样指定grant_type=password类型,然后附上其他相关参数就能获得sessionId,后面的通信中,在header 里设置 Authorization: Bearer sessionId就OK了。
但是,这个方式虽然简单,官方当前已经不推荐了。
因为把账号密码放在get request的parameter中的做法实在是风险太大了。万一泄漏,整个salesforce账号都很危险。

grant_type=password&
client_id=3MVG9lKcPoNINVBIPJjdw1J9LLM82HnFVVX19KY1uA5mu0QqEWhqKpoW3svG3XHrXDiCQjK1mdgAvhCscA9GE&
client_secret=1955279925675241571&
[email protected]&
password=mypassword

所以,最后还是决定使用更安全的Server-to-Server Flow。
这个用法相对来说也更加复杂一些。
具体实现大概有这么几个步骤:

1. 创建并上传签名证书

创建电子签名,主要需要以下几个步骤:
– 创建密钥
– 创建公钥
– 创建证书
以上都可以通过openssl命令实现:

# 创建密钥
$ openssl genrsa 2048 > private-key.pem
# 创建公钥
$ openssl rsa -in private-key.pem -pubout -out public-key.pem
# 创建证书签名要求(Certificate Signing Request)
$ openssl req -new -key private-key.pem -out myapp.csr
(..snip..)
# 创建签名证书
$ openssl x509 -req -days 365 -in myapp.csr -signkey private-key.pem -out myapp.crt

密钥的话,需要自己保存,只有自己有权限。打开文件的话,可以看到 —–BEGIN RSA PRIVATE KEY—– 这样的开头。
公钥后面也会提到,因为只用RSA密钥加密的形式已经不推荐了,所以这次也使用了公钥。打开公钥文件,可以看到—–BEGIN PUBLIC KEY—–的开头。
签名证书要求,这个在生成签名证书的时候需要。找公开机关去生成一般会产生费用。所以这里用了自己生成的。然后签名证书要求的文件是以—–BEGIN CERTIFICATE REQUEST—–为开头的。
最后,只要有了签名证书要求,在加上密钥,就可以生成签名证书,而且可以生成多次。这个证书发给第三方,他们就可以拿证书去验证秘文的有效性。签名证书是以—–BEGIN CERTIFICATE—–为开头的。示例中我们生成了一个有效期为365天的证书,也就是说,这个证书365天以后就失效了。到时候就需要更换新的证书。如果想让有效期更长的话,可以把-days 365换成-days 3650,这样证书的有效期就是10年。

2. 配置salesforce的访问权限

https://qiita.com/stomita/items/4542ce1b48e5fa849ef1
具体内容上面这篇文章中写的很全面,这里就不详细写了,主要就是要注意下面几个事项:

  • IP限制记得要放开
  • 用户权限,要么全部放开,要么设置成管理员承认的用户,但是记得在profile里面设置有权限的用户
  • OAuth的范围记得选上 refresh_token、offline_access

3. 生成jwt


private fun jwt(): String {
    val privatePem = String(ClassPathResource("salesforce/private-key.pem").file.readBytes())
        .replace("\n", "")
        .replace("-----BEGIN RSA PRIVATE KEY-----", "")
        .replace("-----END RSA PRIVATE KEY-----", "")
    val publicPem = String(ClassPathResource("salesforce/public-key.pem").file.readBytes())
        .replace("\n", "")
        .replace("-----BEGIN PUBLIC KEY-----", "")
        .replace("-----END PUBLIC KEY-----", "")
    
    val seq = DerInputStream(Base64.getDecoder().decode(privatePem)).getSequence(0)
    val spec = RSAPrivateCrtKeySpec(
        seq[1].bigInteger,
        seq[2].bigInteger,
        seq[3].bigInteger,
        seq[4].bigInteger,
        seq[5].bigInteger,
        seq[6].bigInteger,
        seq[7].bigInteger,
        seq[8].bigInteger
    )
    val privateKey = KeyFactory.getInstance("RSA").generatePrivate(spec) as RSAPrivateKey
    
    val publicKey = KeyFactory
        .getInstance("RSA")
        .generatePublic(X509EncodedKeySpec(Base64.getDecoder().decode(publicPem))) as RSAPublicKey
    
    return JWT
        .create()
        .withIssuer(property.clientId)
        .withSubject(property.subject)
        .withAudience("https://login.salesforce.com")
        .withExpiresAt(
            Calendar.getInstance().let {
                it.add(Calendar.MINUTE, 3)
                it.time
            }
        )
        .sign(Algorithm.RSA256(publicKey, privateKey))
}

4. 通过jwt拿到access_token

fun accessToken(): String {
    try {
        return RestTemplate().postForEntity(
            "https://login.salesforce.com/services/oauth2/token",
            HttpEntity<MultiValueMap<String, String>>(
                LinkedMultiValueMap<String, String>().also {
                    it.add("grant_type", "urn:ietf:params:oauth:grant-type:jwt-bearer")
                    it.add("assertion", jwt())
                },
                HttpHeaders().also { it.contentType = MediaType.APPLICATION_FORM_URLENCODED }
            ),
            TokenResponse::class.java
        ).body!!.accessToken
    } catch (e: Exception) {
        log.info("get access_token failure: ${e.message}")
        throw e
    }
}

参考:
https://qiita.com/kunichiko/items/12cbccaadcbf41c72735
https://qiita.com/a__i__r/items/6abef31f3ca2c1cfa54c

Related Posts

Leave a Reply

Your email address will not be published. Required fields are marked *

Close Bitnami banner
Bitnami