概述
第三方接口其实比较简单,按照文档来操作即可,代码也就那点,最费时间的反而是在对接系统的账号的申请上,不建议个人申请很麻烦,还是让公司运维申请企业账号。
作为一名合格的开人人员,不仅仅是把第三方接口调通拿到你想要的数据就行,对接第三方接口需要注意的反而实他们的接口限制条件,这些不注意的反而会成为系统的卡点(系统用户量很少并发很少可以忽略)。
比如并发量和请求量,这些限制条件是否满足系统需求,接口防刷也需要考虑,第三方的接口请求量一般是有限制的,不注意的话一下子把你和个月的请求数给刷完了。
主要步骤就是:
1、授权
前端页面获取AppId和回调地址组装二维码,用户去微信/钉钉/qq扫码授权页面,用户授权后会自动跳转到我们的回调地址并携带授权码。
2、获取openId
前端传入授权码调用后端接口,后端根据授权码调用微信/qq/钉钉的接口获取用户信息中的openId
3、关联用户完成登录操作
根据openId查询用户授权绑定表,如果存在,直接后台生成对应用户的我们系统的token给前端,完成登录操作。
如果没有,可能有两种情况需要区分,第一种是用户不存在,第二种是用户存在,但是没有绑定过,我们不知道openId对应我们系统的哪个用户,需要页面再次引导用户输入手机号信息判断用户手否存在,如果不存在让用户去注册页面注册,实现注册并绑定,如果存在就登录并绑定。
很久之前第二步获取钉钉/微信/qq用户信息的时候,是可以拿到手机号的,但为了安全性和隐私性早就不会返回这种信息了,所以我们只能依赖openId或者unionId来绑定用户。
数据库设计:
CREATE TABLE `user_auth_bind` ( `unid` varchar(50) NOT NULL COMMENT '主键id',
`user_code` varchar(50) NOT NULL COMMENT '用户id',
`auth_unid` varchar(255) NOT NULL COMMENT '授权方唯一id',
`auth_type` varchar(10) NOT NULL COMMENT '授权类型,wx:微信;dd:钉钉;qq: QQ ',
`business_type` varchar(10) NOT NULL COMMENT '业务类型,区分多个系统使用',
PRIMARY KEY (`unid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='第三方登录授权绑定表'
后端核心代码实现:
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>dingtalk</artifactId>
<version>1.5.58</version>
</dependency>
代码:
String unionId = "";
try {
if (AuthTypeEnum.WECHAT.getType().equals(authType)) {
// 微信小程序
if (AppKey.APPLET.equals(appKey)) {
unionId = getWeChatAppletUnionId(appId, appSecret, code);
}else {
unionId = getWeChatUnionId(appId, appSecret, code);
}
}
if (AuthTypeEnum.DINGTALK.getType().equals(authType)) {
unionId = getDingTalkUnionId(appId, appSecret, code);
}
if (AuthTypeEnum.QQ.getType().equals(authType)) {
// QQ的回调地址必填
AuthAppResponseDto authAppInfo = getAuthAppInfo(sid, authType);
unionId = getQQUnionId(appId, appSecret, authAppInfo.getRedirectUrl(), code);
}
} catch (Exception e) {
logger.error("获取第三方授权登录用户信息异常,errMsg= {}", e.getMessage());
throw new BizServiceException(AuthErrorCode.AUTH_LOGIN_APP_GET_USER_INFO_ERROR);
}
/**
* 获取微信小程序唯一id
* @param appId 应用id
* @param appSecret 秘钥
* @param code 授权码
* @return 唯一id
*/
private String getWeChatAppletUnionId(String appId, String appSecret, String code) {
String getAccessTokenUrl = "https://api.weixin.qq.com/sns/jscode2session?appid=" + appId + "&secret=" + appSecret + "&js_code=" + code + "&grant_type=authorization_code";
String response = restTemplate.getForObject(getAccessTokenUrl, String.class);
JSONObject accessTokenJson = JSONObject.parseObject(response);
logger.debug("微信小程序获取access_token返回结果:response={}", accessTokenJson);
if (!ObjectUtils.isEmpty(accessTokenJson)) {
if (accessTokenJson.containsKey(AuthLoginKey.UNION_ID) && StringUtils.isNotBlank(accessTokenJson.getString(AuthLoginKey.UNION_ID))) {
return accessTokenJson.getString(AuthLoginKey.UNION_ID);
}else {
// 获取不到unionid就直接获取openid
return accessTokenJson.getString("openid");
}
}
return "";
}
/**
* 获取微信用户唯一id
*
* @param appId 应用id
* @param appSecret 应用秘钥
* @param code 授权码
* @return 唯一id
*/
private String getWeChatUnionId(String appId, String appSecret, String code) {
String getAccessTokenUrl = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=" + appId + "&secret=" + appSecret + "&code=" + code + "&grant_type=authorization_code";
String response = restTemplate.getForObject(getAccessTokenUrl, String.class);
JSONObject accessTokenJson = JSONObject.parseObject(response);
logger.debug("微信获取access_token返回结果:response={}", accessTokenJson);
if (!ObjectUtils.isEmpty(accessTokenJson)) {
if (accessTokenJson.containsKey(AuthLoginKey.UNION_ID) && StringUtils.isNotBlank(accessTokenJson.getString(AuthLoginKey.UNION_ID))) {
return accessTokenJson.getString(AuthLoginKey.UNION_ID);
}
if (accessTokenJson.containsKey("access_token")) {
String accessToken = accessTokenJson.getString("access_token");
String openId = accessTokenJson.getString("openid");
String getUserInfoUrl = "https://api.weixin.qq.com/sns/userinfo?access_token=" + accessToken + "&openid=" + openId + "&lang=zh_CN";
String getUserInfoResponse = restTemplate.getForObject(getUserInfoUrl, String.class);
JSONObject jsonObject = JSONObject.parseObject(getUserInfoResponse);
logger.debug("微信获取用户信息返回结果:response={}", jsonObject);
return jsonObject.getString(AuthLoginKey.UNION_ID);
}
}
return "";
}
/**
* 获取QQ用户唯一id
*
* @param appId 应用id
* @param appSecret 应用秘钥
* @param redirectUri 回调地址 QQ的必填
* @param code 授权码
* @return 唯一id
*/
private String getQQUnionId(String appId, String appSecret, String redirectUri, String code) {
String getAccessTokenUrl = "https://graph.qq.com/oauth2.0/token" +
"?grant_type=authorization_code" +
"&fmt=json" +
"&need_openid=1" +
"&client_id=" + appId +
"&client_secret=" + appSecret +
"&redirect_uri=" + URLEncodeUtil.encode(redirectUri) +
"&code=" + code;
String response = restTemplate.getForObject(getAccessTokenUrl, String.class);
JSONObject accessTokenJson = JSONObject.parseObject(response);
logger.debug("QQ获取access_token返回结果:response={}", accessTokenJson);
if (!ObjectUtils.isEmpty(accessTokenJson)) {
if (accessTokenJson.containsKey(AuthLoginKey.OPENN_ID) && StringUtils.isNotBlank(accessTokenJson.getString(AuthLoginKey.OPENN_ID))) {
return accessTokenJson.getString(AuthLoginKey.OPENN_ID);
}
}
return "";
}
/**
* 获取钉钉用户唯一id
*
* @param appId 应用id
* @param appSecret 应用秘钥
* @param code 授权码
* @return 唯一id
*/
private String getDingTalkUnionId(String appId, String appSecret, String code) throws Exception {
com.aliyun.dingtalkoauth2_1_0.Client client = authClient();
GetUserTokenRequest getUserTokenRequest = new GetUserTokenRequest()
.setClientId(appId)
.setClientSecret(appSecret)
.setCode(code)
.setGrantType("authorization_code");
GetUserTokenResponse getUserTokenResponse = client.getUserToken(getUserTokenRequest);
logger.debug("钉钉获取access_token返回结果:response={}", getUserTokenResponse);
if (!ObjectUtils.isEmpty(getUserTokenResponse.getBody()) && !ObjectUtils.isEmpty(getUserTokenResponse.getBody().accessToken)) {
String accessToken = getUserTokenResponse.getBody().getAccessToken();
String userInfo = getUserInfo(accessToken);
JSONObject jsonObject = JSONObject.parseObject(userInfo);
logger.debug("钉钉获取用户信息返回结果:response={}", jsonObject);
return jsonObject.getString("unionId");
}
return "";
}
/**
* 获取钉钉用户个人信息
*
* @param accessToken 令牌
* @return 用户信息
*/
private String getUserInfo(String accessToken) throws Exception {
com.aliyun.dingtalkcontact_1_0.Client client = contactClient();
GetUserHeaders getUserHeaders = new GetUserHeaders();
getUserHeaders.xAcsDingtalkAccessToken = accessToken;
// 获取用户个人信息,如需获取当前授权人的信息,unionId参数必须传me
return JSON.toJSONString(client.getUserWithOptions("me", getUserHeaders, new RuntimeOptions()).getBody());
}
private static com.aliyun.dingtalkoauth2_1_0.Client authClient() throws Exception {
Config config = new Config();
config.protocol = "https";
config.regionId = "central";
return new com.aliyun.dingtalkoauth2_1_0.Client(config);
}
private static com.aliyun.dingtalkcontact_1_0.Client contactClient() throws Exception {
Config config = new Config();
config.protocol = "https";
config.regionId = "central";
return new com.aliyun.dingtalkcontact_1_0.Client(config);
}