钉钉

第三方网站集成钉钉登录

相关概念

服务商:钉钉

用户:自己

第三方网站:开发的web应用

 

用户登入一些第三方网站时,可能是第一次登录需要繁琐的注册,可能距离上一次登录太久而忘记密码;

故为了让用户更便捷的使用第三方网站的功能,引入了OAuth授权登录。

 

OAuth授权登录

用户在服务商(拥有极其庞大的用户量)那里进行授权登录,第三方网站需要付费给服务商进行用户引流(获取AppID和appSecret)

服务商确认用户信息后,通过第三方提供的回调函数将相关数据(code、state)传递给第三方网站

 

实现逻辑

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】https://blog.csdn.net/swimming_in_IT_/article/details/82714578

【2】https://blog.csdn.net/dadawdawdadadw/article/details/128962167

 

 

相关数据对接API

核心

调用钉钉API(API Explorer,API参数介绍及相关代码示例)

1、创建应用,获取AppKey、AppSecret
2、在API Explorer 查询相关接口,使用access_token 进行调用
3、调用前需要先在钉钉开放平台开放相关权限

 

 

桌面版查看用户ID

 

 

OA审批

引入钉钉SDK

https://s01.oss.sonatype.org/?spm=openapi-amp.sdkpublish.0.0.78cefAyIfAyImO#nexus-search;quick~dingtalk

        <dependency>
            <groupId>com.aliyun</groupId>
            <artifactId>dingtalk</artifactId>
            <version>2.1.21</version>
        </dependency>

代码仓库

https://gitee.com/bruce6213/ding-talk-oa

总体感受

先说下为什么要集成钉钉:避免多系统切换,直接将钉钉平台的数据引入到原有系统,即所谓的同步操作

 

初看钉钉平台提供了非常多的API接口,但实际编码过程中会发现仍有很多障碍:只能根据已有的API进行编码、对外的接口中没有任何注释等等,导致需要在业务层面做很多逻辑操作才能满足实际需求,这时才深深地明白直接操作数据库是有多么的香!!!

 

posted @ 2024-05-14 11:39  先娶国王后取经  阅读(28)  评论(0编辑  收藏  举报