钉钉
第三方网站集成钉钉登录
相关概念
服务商:钉钉
用户:自己
第三方网站:开发的web应用
用户登入一些第三方网站时,可能是第一次登录需要繁琐的注册,可能距离上一次登录太久而忘记密码;
故为了让用户更便捷的使用第三方网站的功能,引入了OAuth授权登录。
OAuth授权登录
用户在服务商(拥有极其庞大的用户量)那里进行授权登录,第三方网站需要提前付费给服务商进行系统备案(获取AppID和appSecret)
服务商确认用户信息后,通过第三方提供的回调函数将相关数据(code、state)传递给第三方网站,第三方网站再通过该code去获取access token
四种获取令牌的方式
- 授权码
- 隐藏式
- 密码式
- 凭证式
实现逻辑(隐藏式)
1、前端通过访问后端接口来获取钉钉登录二维码url
2、用户扫码二维码并授权登录后,会根据相关回调地址进行接口调用,即钉钉会主动调用后端服务,并返回相关数据给后端
3、后端接收到钉钉的调用后,返回相关token给钉钉服务器
4、钉钉服务器将收到的token返回给前端
代码细节
相关依赖
钉钉相关操作
注册管理员账号
https://oa.dingtalk.com/register_new.htm#/
创建应用
获取Client ID、Client Secret
设置回调地址
后端逻辑
yml文件配置
#第三方登录
justauth:
enabled: true
type:
GITHUB:
client-id: ??
client-secret: ??
redirect-uri: http://sso.test.com:8080/jeecg-boot/sys/thirdLogin/github/callback
WECHAT_ENTERPRISE:
client-id: ??
client-secret: ??
redirect-uri: http://sso.test.com:8080/jeecg-boot/sys/thirdLogin/wechat_enterprise/callback
agent-id: ??
DINGTALK:
client-id: ??
client-secret: ??
redirect-uri: http://sso.test.com:8080/jeecg-boot/sys/thirdLogin/dingtalk/callback
WECHAT_OPEN:
client-id: ??
client-secret: ??
redirect-uri: http://sso.test.com:8080/jeecg-boot/sys/thirdLogin/wechat_open/callback
cache:
type: default
prefix: 'demo::'
timeout: 1h
生成二维码
- 至于二维码url的拼接,是justauth组件内部实现的
供前端调用,用以获取生成二维码的url
@Autowired
private AuthRequestFactory factory;
@RequestMapping("/render/{source}")
public void render(@PathVariable("source") String source, HttpServletResponse response) throws IOException {
log.info("第三方登录进入render:" + source);
AuthRequest authRequest = factory.get(source);
String authorizeUrl = authRequest.authorize(AuthStateUtils.createState());
log.info("第三方登录认证地址:" + authorizeUrl);
response.sendRedirect(authorizeUrl);
}
回调函数
- callback对象中包含了code、state
- 2000状态码表示登录成功
- AuthResponse对象的data属性包含着用户信息
供钉钉服务器调用
@RequestMapping("/{source}/callback")
public String loginThird(@PathVariable("source") String source, AuthCallback callback,ModelMap modelMap) {
log.info("第三方登录进入callback:" + source + " params:" + JSONObject.toJSONString(callback));
AuthRequest authRequest = factory.get(source);
AuthResponse response = authRequest.login(callback);
log.info(JSONObject.toJSONString(response));
Result<JSONObject> result = new Result<JSONObject>();
if(response.getCode()==2000) {
JSONObject data = JSONObject.parseObject(JSONObject.toJSONString(response.getData()));
String username = data.getString("username");
String avatar = data.getString("avatar");
String uuid = data.getString("uuid");
//构造第三方登录信息存储对象
ThirdLoginModel tlm = new ThirdLoginModel(source, uuid, username, avatar);
//判断有没有这个人
//update-begin-author:wangshuai date:20201118 for:修改成查询第三方账户表
LambdaQueryWrapper<SysThirdAccount> query = new LambdaQueryWrapper<SysThirdAccount>();
query.eq(SysThirdAccount::getThirdType, source);
//update-begin---author:wangshuai---date:2023-10-07---for:【QQYUN-6667】敲敲云,线上解绑重新绑定一直提示这个---
query.eq(SysThirdAccount::getTenantId, CommonConstant.TENANT_ID_DEFAULT_VALUE);
//update-end---author:wangshuai---date:2023-10-07---for:【QQYUN-6667】敲敲云,线上解绑重新绑定一直提示这个---
query.and(q -> q.eq(SysThirdAccount::getThirdUserUuid, uuid).or().eq(SysThirdAccount::getThirdUserId, uuid));
List<SysThirdAccount> thridList = sysThirdAccountService.list(query);
SysThirdAccount user = null;
if(thridList==null || thridList.size()==0) {
//否则直接创建新账号
user = sysThirdAccountService.saveThirdUser(tlm,CommonConstant.TENANT_ID_DEFAULT_VALUE);
}else {
//已存在 只设置用户名 不设置头像
user = thridList.get(0);
}
// 生成token
//update-begin-author:wangshuai date:20201118 for:从第三方登录查询是否存在用户id,不存在绑定手机号
if(oConvertUtils.isNotEmpty(user.getSysUserId())) {
String sysUserId = user.getSysUserId();
SysUser sysUser = sysUserService.getById(sysUserId);
String token = saveToken(sysUser);
modelMap.addAttribute("token", token);
}else{
modelMap.addAttribute("token", "绑定手机号,"+""+uuid);
}
//update-end-author:wangshuai date:20201118 for:从第三方登录查询是否存在用户id,不存在绑定手机号
//update-begin--Author:wangshuai Date:20200729 for:接口在签名校验失败时返回失败的标识码 issues#1441--------------------
}else{
modelMap.addAttribute("token", "登录失败");
}
//update-end--Author:wangshuai Date:20200729 for:接口在签名校验失败时返回失败的标识码 issues#1441--------------------
result.setSuccess(false);
result.setMessage("第三方登录异常,请联系管理员");
return "thirdLogin";
}
数据库表设计
- sys_third_account:用于存放钉钉扫码登录的用户信息
- sys_user:通过第三方登录唯一标识uuid及手机号唯一性,与sys_third_account表建立一对一的联系;设置third_id、third_type字段
- 主表还是sys_user,sys_third_account用作辅助
参考文章
【1】微信授权登录
【2】钉钉授权登录
相关数据对接API
核心
调用钉钉API(API Explorer,API参数介绍及相关代码示例)
1、创建应用,获取AppKey、AppSecret
2、在API Explorer 查询相关接口,使用access_token 进行调用
3、调用前需要先在钉钉开放平台开放相关权限
桌面版查看用户ID
OA审批
引入钉钉SDK
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>dingtalk</artifactId>
<version>2.1.21</version>
</dependency>
代码仓库
https://gitee.com/bruce6213/ding-talk-oa
总体感受
先说下为什么要集成钉钉:避免多系统切换,直接将钉钉平台的数据引入到原有系统,即所谓的同步操作
初看钉钉平台提供了非常多的API接口,但实际编码过程中会发现仍有很多障碍:只能根据已有的API进行编码、对外的接口中没有任何注释等等,导致需要在业务层面做很多逻辑操作才能满足实际需求,这时才深深地明白直接操作数据库是有多么的香!!!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步