最近有个项目,需要对接把系统的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&
username=testuser@salesforce.com&
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