功能概述
扫码登录能力,指的是开发者可在移动应用内使用此能力,拉取二维码,用户使用微信客户端扫描二维码后可以登录此移动应用。此能力可被应用在多设备登录、智能硬件、电视盒子等场景。
第一步:
很简单,就是先创建应用,集成微信SDK。
第二步:
开始写代码,首先APP通过IDiffDevOAuth.auth()接口发起授权,然后在OAuthListener.onAuthGotQrcode()回调接口中获取二维码,在APP中展示二维码,最后用户通过微信扫码,授权。
lateinit var oauth: IDiffDevOAuth
override fun onCreate(savedInstanceState: Bundle?) {
//初始化oauth
oauth = DiffDevOAuthFactory.getDiffDevOAuth()
}
override fun onDestroy() {
super.onDestroyView()
oauth.removeAllListeners()
oauth.detach()
}
初始化完成后,就要掉接口了。
接口
IDiffDevOAuth
boolean auth(String appId, String scope, String noncestr, String timestamp, String signature, OAuthListener listener)
参数说明
参数 | 是否必须 | 说明 |
---|---|---|
appId | 是 | 应用唯一标识 |
scope | 是 | 应用授权作用域,拥有多个作用域用逗号(,)分隔,APP所拥有的scope |
noncestr | 是 | 一个随机的尽量不重复的字符串,用来使得每次的signature不同 |
timestamp | 是 | 时间戳 |
signature | 是 | 签名 |
listener | 是 | 授权流程,回调接口 |
appId:自己上应用上查看。
scope:填snsapi_userinfo,官方demo中的snsapi_login和snsapi_base没用,使用后会报一般错误。
noncestr:使用MD5加密一串随机值
private fun getNonceStr(): String {
val r = Random(System.currentTimeMillis())
return EncryptUtils.encryptMD5ToString((Constant.WXAPPID +
r.nextInt(10000) + System.currentTimeMillis()).toByteArray())
}
timestamp:获取当前时间戳就行
private fun getTimestamp(): String {
return System.currentTimeMillis().toString()
}
signature:官方说明:生成签名之前必须先获取对应的sdk_ticket。
sdk_ticket是用于生成签名的临时票据。正常情况下,sdk_ticket的有效期为7200秒,通过access_token来获取。由于获取sdk_ticket的api调用次数非常有限,频繁刷新sdk_ticket会导致api调用受限,影响自身业务,开发者需在自己的服务存储与更新sdk_ticket。
- 先获取token,grant_type参数固定填client_credential,另两个参数在微信开放平台应用上获取。
/**
* 微信获取Token
*/
@GET("https://api.weixin.qq.com/cgi-bin/token")
Observable getWXToken(
@Query("grant_type") String type,//固定值client_credential
@Query("appid") String appID,
@Query("secret") String appSecret
);
正常返回
{"access_token":"ACCESS_TOKEN","expires_in":7200}
错误返回(该示例为AppID无效错误)
{"errcode":40013,"errmsg":"invalid appid"}
官方地址
- 获取到token之后,再请求sdk_ticket,type固定值2
/**
* 微信获取Ticket
*/
@GET("https://api.weixin.qq.com/cgi-bin/ticket/getticket")
Observable getTicket(
@Query("access_token") String token,
@Query("type") int type
);
成功返回
{
"errcode":0,
"errmsg":"ok",
"ticket":"-p3A5zVP95IuafPhzA6lRR95_F9nZEBfJ_n4E9t8ZFWKJTDPOwccVQhHCwDBmvLkayF_jh-m9HOExhumOziDWA",
"expires_in":7200
}
- 成功拿到ticket后,按签名规则生成签名。
签名生成规则如下:
参与签名的字段包括第三方appid,noncestr(随机字符串), 有效的sdk_ticket, timestamp(时间戳) 。
对所有待签名参数按照字段名的ASCII 码从小到大排序(字典序)后,使用URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串string1。这里需要注意的是所有参数名均为小写字符。对string1作sha1加密,字段名和字段值都采用原始值,不进行URL 转义。即signature=sha1(string1)。
示例:
appid=appid
noncestr=noncestr
sdk_ticket=-p3A5zVP95IuafPhzA6lRR95_F9nZEBfJ_n4E9t8ZFWKJTDPOwccVQhHCwDBmvLkayF_jh-m9HOExhumOziDWA
timestamp=1417508194
1.对所有待签名参数按照字段名的ASCII 码从小到大排序(字典序)后,使用URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串string1:
appid=appid&noncestr=noncestr&sdk_ticket=-p3A5zVP95IuafPhzA6lRR95_F9nZEBfJ_n4E9t8ZFWKJTDPOwccVQhHCwDBmvLkayF_jh-m9HOExhumOziDWA×tamp=1417508194
2.对string1进行sha1签名,得到signature:
429eaaa13fd71efbc3fd344d0a9a9126835e7303
private fun getSignature(noncestr: String, timestamp: String, sdk_ticket: String): String {
val str = "appid=${Constant.WXAPPID}&noncestr=$noncestr&sdk_ticket=$sdk_ticket×tamp=$timestamp"
return EncryptUtils.encryptSHA1ToString(str)
}
注意这里是用SHA1加密的
- 最后调用接口
override fun auth(ticket: String) {
val noncestr = getNonceStr()
val timestamp = getTimestamp()
val signature = getSignature(noncestr, timestamp, ticket)
val authRet = oauth.auth(Constant.WXAPPID, "snsapi_userinfo", noncestr,
timestamp, signature, object : OAuthListener {
/**
* 用户点击授权后,回调改接口
*/
override fun onAuthFinish(errCode: OAuthErrCode?, authCode: String?) {
Timber.tag("OAuthListener").i("onAuthFinish,OAuthErrCode->$errCode ,authCode->$authCode")
val tips: String = when (errCode) {
OAuthErrCode.WechatAuth_Err_OK -> "登录成功,code=$authCode"
OAuthErrCode.WechatAuth_Err_NormalErr -> "登录失败,一般错误"
OAuthErrCode.WechatAuth_Err_NetworkErr -> "登录失败,网络错误"
OAuthErrCode.WechatAuth_Err_JsonDecodeErr -> "json解码失败"
OAuthErrCode.WechatAuth_Err_Cancel -> "用户取消"
OAuthErrCode.WechatAuth_Err_Timeout -> "登录失败,超时错误"
else -> ""
}
Timber.tag("OAuthListener").i(tips)
}
/**
* auth之后返回的二维码接口
*
* @param qrcodeImgPath 废弃
* @param imgBuf 二维码图片数据
*/
override fun onAuthGotQrcode(qrcodeImgPath: String?, imgBuf: ByteArray?) {
Timber.tag("OAuthListener").i("imgBuf->$imgBuf")
if (imgBuf != null) {
val bitmap = BitmapFactory.decodeByteArray(imgBuf, 0, imgBuf.size)
onUiThread {
img_wx.setImageBitmap(bitmap)
}
}
}
/**
* 用户扫描二维码之后,回调改接口
*/
override fun onQrcodeScanned() {
Timber.tag("OAuthListener").i("onQrcodeScanned")
}
})
Timber.tag("OAuthListener").i("authRet->$authRet")
}