1、场景描述

我们开发了一个钉钉应用,但是客户要求手机端要使用微信。这样的话,我们给客户的解决方案是后台管理功能监控功能使用的钉钉(主要是依托于钉钉的人员,部门,角色的管理,我们应用本身就没有做相关的维护页面),手机端使用公众号。

这样的话,就带来了一个问题,由于我们应用是依托于钉钉的,没有自己的账号体系。所以就必须使用钉钉的账号来登录。

2、开发环境

1、前后端分离。前端使用的vue

2、后端是springboot 工程

3、实际开发

(1)技术选型经过

1、首先我们是想用微信小程序来开发,但是发现小程序的web-view没法实现,尤其是我们还是用的钉钉,光是跳转域名的审核就没法通过,审核需要的验证文件我们根本没办法放到钉钉的域名下。所以就选择普通的H5工程。

2、找到钉钉官方文档,这里有两种方式,扫码和账号密码。

这里由于我们本就是为了手机端做的,扫码登录这个就不考虑了。选用账号密码。

(2)步骤

接下来是步骤,基本上是按照官方文档来的,不过有些地方我加上了截图

1、首先是登录开发者后台,选择应用开发 > 移动接入应用 > 登录,然后单击创建扫码登录应用授权,创建用于免登过程中验证身份的appId及appSecret,创建后即可看到appId和appSecret。

位置:

python adb 输入钉钉密码 钉钉用密码登录_扫码登录

单击“创建扫码登录应用授权”按钮,弹出以下对话框

python adb 输入钉钉密码 钉钉用密码登录_扫码登录_02

加完之后,能够得到AppId和appSecret。

注:所有的字段中,只有描述没有实际的用途,其他的字段都很重要

python adb 输入钉钉密码 钉钉用密码登录_python adb 输入钉钉密码_03

 

2、构造要跳转的链接

构造如下跳转链接,此链接处理成功后,会重定向跳转到指定的redirect_uri,并向url追加临时授权码code及state两个参数。

1

https://oapi.dingtalk.com/connect/oauth2/sns_authorize?appid=APPID&response_type=code&scope=snsapi_login&state=STATE&redirect_uri=REDIRECT_URI

根据我上面的参数,构造的url是

1

https://oapi.dingtalk.com/connect/oauth2/sns_authorize?appid=dingoaddugp123ueic122dbpxf6v2wq71yn81&response_type=code&scope=snsapi_login&state=STATE&&redirect_uri=http://192.168.0.16:8080/#/

其中需要注意的是:

redirect_uri 的参数值必须是申请扫码登录授权应用的回调url。如果一直但是还是有问题,看看是否是没有urlencode编码导致的。

在浏览器中查看构造好的url

如果错了的话,提示

python adb 输入钉钉密码 钉钉用密码登录_扫码登录_04

如果正确的话,就能够跳转到第三方登录页面。图片因为用的百度的,是403 (Forbidden)了。这个换成可用的图片url就可以了。

python adb 输入钉钉密码 钉钉用密码登录_扫码登录_05

单击登录账号,

python adb 输入钉钉密码 钉钉用密码登录_python adb 输入钉钉密码_06

输入手机号和密码,单击授权登录,就会跳转到我们指定的回调地址上并添加上临时授权码

我们的工程是vue,所以我的回调地址直接就是前段的地址。

这里前端从url中拿到临时授权码,用这个授权码请求我的后端,获取用户的信息,完成整个登录过程

python adb 输入钉钉密码 钉钉用密码登录_python adb 输入钉钉密码_07

后端代码

controller

@ApiOperation("第三方网站使用钉钉账号登录")
    @RequestMapping(value = "/third-party/login", method = RequestMethod.GET)
    public ResponseMessage<DingUserDto> thirdPartyLogin(@ApiParam("临时授权码") @RequestParam("code") @NotBlank(message = "授权Code 不能为空") String code) throws Exception {
        return ResponseMessage.ok(dingUserService.thirdPartyLogin(code));
    }

service

获取用户信息的步骤,首先是unionId。

这个unionId是用户在钉钉的唯一标识。我的理解是手机号的替代。因为手机号虽然也具有唯一性,但是信息太敏感,所以给用户添加了一个unionId来唯一区分各个用户。

我们在进行应用开发的时候还有一个概念叫userId。这个userId是针对组织而言的。也就是,同样一个用户,不同的组织当中userId是不一样的。

举例子

我用手机号(13512341234)申请了一个钉钉账号,我会获得一个unionId,比如dingunion1234

后来我加入了公司A,那么我的unionId依然还是dingunion1234,我在这个公司的urserId是dinguser987654

我同时又加入了公司B,那么我的unionId依然还是dingunion1234,但是我在公司B的urserId就变成了dinguser29387928

这样的话其实就可以看到,unionId + 企业corpId就可以唯一确定userId

后面也是这么获取用户信息的,只不过企业coprId要换成企业accessToken

1、通过临时授权码(一次性的,用过就失效)获取用户的unionId。

public OapiSnsGetuserinfoBycodeResponse getUserInfoByCode(String code) {
        DefaultDingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/sns/getuserinfo_bycode");
        OapiSnsGetuserinfoBycodeRequest reqBycodeRequest = new OapiSnsGetuserinfoBycodeRequest();
        // code临时授权码
        reqBycodeRequest.setTmpAuthCode(code);
        OapiSnsGetuserinfoBycodeResponse response;
        try {
            response = client.execute(reqBycodeRequest, dingSnsPropertiesConfig.getAppId(), dingSnsPropertiesConfig.getAppSecret());
        } catch (ApiException e) {
            log.info(e.toString(), e);
            return null;
        }
        if (response == null || !response.isSuccess()) {
            return null;
        }
        return response;
    }

2、获取企业accessToken

public static OapiServiceGetCorpTokenResponse getCorpTokenUtil(String corpId, String suiteTicket, String suiteKey, String suiteSecret) {
        long timestamp = System.currentTimeMillis();
        String signature = DingTalkSignatureUtil.computeSignature(suiteSecret, DingTalkSignatureUtil.getCanonicalStringForIsv(timestamp, suiteTicket));
        Map<String, String> params = new LinkedHashMap<>();
        params.put("timestamp", "" + timestamp);
        params.put("suiteTicket", suiteTicket);
        params.put("accessKey", suiteKey);
        params.put("signature", signature);
        String queryString = DingTalkSignatureUtil.paramToQueryString(params, "utf-8");
        DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/service/get_corp_token?" + queryString);
        OapiServiceGetCorpTokenRequest request = new OapiServiceGetCorpTokenRequest();
        request.setAuthCorpid(corpId);
        try {
            return client.execute(request, suiteKey, suiteSecret, suiteTicket);
        } catch (ApiException e) {
            e.printStackTrace();
            throw new DingApiException("获取企业 token 异常,详情:" + e.getErrCode() + "-" + e.getErrMsg() + ":" + e.getSubErrCode() + "-" + e.getSubErrMsg());
        } finally {
            log.info("GetCorpTokenUtil 耗时:{}", System.currentTimeMillis() - timestamp);
        }
    }

3、根据企业的accessToken和unionId获取用户信息

public OapiUserGetbyunionidResponse getByUnionId(String unionId, String accessToken) {
        DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/topapi/user/getbyunionid");
        OapiUserGetbyunionidRequest req = new OapiUserGetbyunionidRequest();
        req.setUnionid(unionId);
        OapiUserGetbyunionidResponse response;
        try {
            response = client.execute(req, accessToken);
        } catch (ApiException e) {
            log.info(e.toString(), e);
            return null;
        }
        if (response == null || !response.isSuccess()) {
            return null;
        }
        return response;
    }

到此为止,我们获取到了该用户的userid,名称,角色,corpId等信息。

剩下的根据自己的业务做处理就行了。