天天看点

钉钉扫码登录实现准备内容请求调用接口图后端前端

准备内容

钉钉开放平台地址:https://open.dingtalk.com

  1. 登录钉钉开放平台,需要有开发者权限
    钉钉扫码登录实现准备内容请求调用接口图后端前端
  2. 进入到“应用开发”页面,本次展示的为企业内部应用,三方应用对接流程基本一直,只是使用的key id 不同外,还有调用的接口有一些不同,其他流程基本上没问题
    钉钉扫码登录实现准备内容请求调用接口图后端前端
  3. 我们当前的系统为web方式的,需要在钉钉应用中,创建“H5微应用”
    钉钉扫码登录实现准备内容请求调用接口图后端前端
    钉钉扫码登录实现准备内容请求调用接口图后端前端
  4. 进入创建的应用中,应用信息中就可以拿到appkey和AppSecret的信息
    钉钉扫码登录实现准备内容请求调用接口图后端前端
  5. 设置完后,还需要设置一个请求的回调
    钉钉扫码登录实现准备内容请求调用接口图后端前端
    钉钉扫码登录实现准备内容请求调用接口图后端前端

请求调用接口图

钉钉扫码登录实现准备内容请求调用接口图后端前端

后端

我们的后端采用SpringBoot的方式
三方登录框架JustAuth
           
  1. 创建SpringBoot工程,自行创建即可
  2. 引入JustAuth相关依赖
<dependency>
            <groupId>com.xkcoding.justauth</groupId>
            <artifactId>justauth-spring-boot-starter</artifactId>
            <version>1.4.0</version>
        </dependency>
           
  1. 配置文件相关
justauth:
  enabled: true
  cache:
    type: default
  type:
    DINGTALK:
      corp-id: {你自己的}
      client-id: {你自己的}
      client-secret: {你自己的}
      redirect-uri: {你自己的重定向地址,和钉钉开放平台设置的一致}
      # 获取企业的token接口
      gettoken-url: https://oapi.dingtalk.com/gettoken
      # 获取用户信息的接口
      get-user-info-url: https://api.dingtalk.com/v1.0/oauth2/ssoUserInfo
      # 根据unionid查询用户id
      get-userid-by-unionid: https://oapi.dingtalk.com/user/getUseridByUnionid
      # 根据用户id查询用户信息的接口
      get-userinfo-by-userid: https://oapi.dingtalk.com/user/get
           
  1. 获取钉钉二维码接口
@Autowired
    private  AuthRequestFactory factory;

    @ApiOperation("获取钉钉登录二维码")
    @GetMapping("/getDingtalkQrcode")
    public Result<Map> getDingtalkQrcode() {
        AuthRequest authRequest = factory.get("DINGTALK");
//        唯一标识
        String state = AuthStateUtils.createState();
        String url = authRequest.authorize(state);
        return Result.success(Map.of("url", url, "uuid", state));
    }
           
  1. 登录回调的接口
@ApiOperation("钉钉登录")
    @PostMapping("/dingtalkLogin")
    public Result<String> dingtalkLogin(AuthCallback callback) {
    // 获取钉钉用户信息
        DingtalkUserDO dingtalkUser = getDingtalkUser(callback);
//        获取钉钉用户的邮箱 也可以拿到手机号
        String mEmail = dingtalkUser.getEmail();
 //   这里处理业务逻辑啥的,然后生成token
        return Result.success("token" );
    }
    private DingtalkUserDO getDingtalkUser(AuthCallback callback) {
        //        获取用户信息 v1
        AuthRequest authRequest = factory.get(DingtalkRequestUtil.TYPE);
        AuthResponse authResponse = authRequest.login(callback);

        dingtalkRequestUtil.checkRequestIsSuccess(authResponse, "钉钉登录异常");

        AuthUser authUser = (AuthUser) authResponse.getData();
        String unionId = authUser.getToken().getUnionId();

        String token = dingtalkRequestUtil.getToken();

        String userId = dingtalkRequestUtil.getUserId(token, unionId);

        return dingtalkRequestUtil.getUserInfo(token, userId);
    }
           
/**
登录请求工具类	
*/
@Component
@Slf4j
public class DingtalkRequestUtil {

    //     类型为  钉钉
    public static final String TYPE = "DINGTALK";

    //     钉钉token
    public static final String TOKEN = "TOKEN";

    //    成功状态
    public static final Integer SUCCESS_STATUS_1 = 2000;
    public static final Integer SUCCESS_STATUS_2 = 0;

    private final RestTemplate restTemplate;
    private final MongCacheUtil mongCacheUtil;

    @Value("${justauth.type.DINGTALK.client-id}")
    private String appkey;
    @Value("${justauth.type.DINGTALK.client-secret}")
    private String appsecret;
    @Value("${justauth.type.DINGTALK.gettoken-url}")
    private String gettokenUrl;
    @Value("${justauth.type.DINGTALK.get-user-info-url}")
    private String getUserInfoUrl;
    @Value("${justauth.type.DINGTALK.get-userid-by-unionid}")
    private String getUseridByUnionid;
    @Value("${justauth.type.DINGTALK.get-userinfo-by-userid}")
    private String getUserinfoByUserid;

    @Autowired
    public DingtalkRequestUtil(RestTemplate restTemplate, MongCacheUtil mongCacheUtil) {
        this.restTemplate = restTemplate;
        this.mongCacheUtil = mongCacheUtil;
    }

    /**
     * token 的过期时间,默认为两个小时
     *
     * @return
     */
    public String getToken() {
//        从缓存中查询当前的token
        String cacheKey = TYPE + "_" + TOKEN;
        Result<String> cache = mongCacheUtil.get(cacheKey);
        if (Objects.equals(cache.getCode(), ResultCode.SUCCESS.getCode())) {
            return cache.getData();
        } else {
            String url = gettokenUrl + "?appkey=" + appkey + "&appsecret=" + appsecret;
            Map forObject = restTemplate.getForObject(url, Map.class);
            checkRequestIsSuccess(forObject, "获取钉钉token异常", forObject);
            String accessToken = forObject.get("access_token").toString();
//         设置缓存
            mongCacheUtil.put(cacheKey, accessToken, Integer.parseInt(forObject.get("expires_in").toString()), 900);
            return accessToken;
        }
    }


    /**
     * 检查请求是否成功
     *
     * @param resp
     */
    public void checkRequestIsSuccess(Object resp, String errMessage, Object... errObjects) {
        boolean error = false;
        if (resp instanceof AuthResponse) {
            AuthResponse authResponse = (AuthResponse) resp;
            if (!Objects.equals(authResponse.getCode(), SUCCESS_STATUS_1)) {
                error = true;
            }
        } else if (resp instanceof Map) {
            Map<String, String> resMap = (Map<String, String>) resp;
            if (!resMap.containsKey("errcode") || !Objects.equals(String.valueOf(resMap.get("errcode")), SUCCESS_STATUS_2.toString())) {
                error = true;
                errMessage = resMap.get("errmsg");
            }
        }
        if (error) {
            log.error(JSON.toJSONString(errObjects));
            throw new DingtalkRequestException(errMessage);
        }
    }

    public String getUserId(String token, String unionId) {
        String url = getUseridByUnionid + "?access_token=" + token + "&unionid=" + unionId;
        Map respMap = restTemplate.getForObject(url, Map.class);
        System.out.println(respMap);
        checkRequestIsSuccess(respMap, "获取用户id异常", respMap);
//        校验是否成功
        return respMap.get("userid").toString();
    }

    public DingtalkUserDO getUserInfo(String token, String userId) {
        String url = getUserinfoByUserid + "?access_token=" + token + "&userid=" + userId;
        Map respMap = restTemplate.getForObject(url, Map.class);
        checkRequestIsSuccess(respMap, "获取用户信息异常");
//      转换为自己的实体 DingtalkUserDO
        return com.alibaba.fastjson.JSON.parseObject(JSON.toJSONString(respMap), DingtalkUserDO.class);
    }


    /**
     * 钉钉异常内部类
     */
    class DingtalkRequestException extends RuntimeException {
        public DingtalkRequestException(String message) {
            super(message);
        }
    }
}

           
/**
 * 钉钉用户
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class DingtalkUserDO {
    private Boolean active;
    private String avatar;
    private List<Long> department;
    private String email;
    private Long errcode;
    private String errmsg;
    private Boolean exclusiveAccount;
    private Boolean isAdmin;
    private Boolean isBoss;
    private Boolean isHide;
    private String isLeaderInDepts;
    private Boolean isSenior;
    private String jobnumber;
    private String managerUserid;
    private String name;
    private String openId;
    private String orderInDepts;
    private String position;
    private Boolean realAuthed;
    private String remark;
    private List<DingtalkRoleDO> roles;
    private String tel;
    private String unionid;
    private String userid;
    private String workPlace;
}

           
/**
 * 钉钉角色
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class DingtalkRoleDO {
    private String groupName;
    private Long id;
    private String name;
    private Long type;
}

           
后端部分完成
           

前端

前端我们使用的VUE
           
  1. 在public/index.html文件中添加ddLogin.js配置文件
# 引入方式,ddLogin 名字自己定义
   <script src="<%= BASE_URL %>js/ddLogin.js"></script>
           
!function (window, document) {
  function d(a) {
    var e, c = document.createElement("iframe"),
      d = "https://login.dingtalk.com/login/qrcode.htm?goto=" + a.goto ;
    d += a.style ? "&style=" + encodeURIComponent(a.style) : "",
      d += a.href ? "&href=" + a.href : "",
      c.src = d,
      c.frameBorder = "0",
      c.allowTransparency = "true",
      c.scrolling = "no",
      c.width =  a.width ? a.width + 'px' : "365px",
      c.height = a.height ? a.height + 'px' : "400px",
      e = document.getElementById(a.id),
      e.innerHTML = "",
      e.appendChild(c)
  }
  window.DDLogin = d
}(window, document);

           
  1. 编写html代码

定义一个div

编写初始化二维码的js代码

ddLoginInit() {
        //  获取钉钉登录验证码
        getDingtalkOauthLoginQrcode()
          .then(res => {
            this.ddLoginUrl = res.data.url

            this.state = res.data.uuid
            // 钉钉自己的url 修改上面俩,不需要动这个
            let goto = encodeURIComponent(this.ddLoginUrl)
            let obj = DDLogin({
              id: 'login_container',//这里需要你在自己的页面定义一个HTML标签并设置id,例如<div id="login_container"></div>或<span id="login_container"></span>
              goto: goto, //请参考注释里的方式
              style: 'border:none;background-color:#FFFFFF;',
              width: '100%',//官方参数 365
              height: '300'//官方参数 400
            })
            let that = this
            var handleMessage = function(event) {
              var origin = event.origin
              if (origin == 'https://login.dingtalk.com') { //判断是否来自ddLogin扫码事件。
                var loginTmpCode = event.data
                //获取到loginTmpCode后就可以在这里构造跳转链接进行跳转了
                let url = 'https://oapi.dingtalk.com/connect/oauth2/sns_authorize?appid=' + that.appid + '&response_type=code&scope=snsapi_login&state=' + that.state + '&redirect_uri=' + that.redirect_uri + '&loginTmpCode=' + loginTmpCode

                window.location.href = url
                /*          window.location.href = url
                          if (this.timer !== undefined) {
                            clearInterval(that.timer)
                          }
                          //  启动定时任务
                          that.timer = setInterval(function() {
                            console.log('定时任务')
                          }, 1000)*/
              }
            }
            if (typeof window.addEventListener != 'undefined') {
              window.addEventListener('message', handleMessage, false)
            } else if (typeof window.attachEvent != 'undefined') {
              window.attachEvent('onmessage', handleMessage)
            }
          })
          .catch(err => {
            this.$message.error('跳转钉钉登录异常')
          })
      },
           

vue created事件的定义代码

created() {
      //   获取路径是否携带参数
      let code = this.$route.query.code
      let state = this.$route.query.state
      if (code && state) {
        let codeForm = {
          code: code,
          state:state
        }
        // 调用钉钉登录  ,这个接口就是我们自己定义的登录接口,需要传入两个参数
       this.dingtalkLogin(codeForm)
      } else {
      // 这里执行的是其他方式的登录逻辑
      }
    }
           
前端代码构建也完成