【新版API】实现第三方应用钉钉扫码登录

这篇文章默认你已经在钉钉创建好了H5微应用,同时对一些功能有一定了解。

首先,需要明白整个授权登录的流程,如下图所示:

【新版API】实现第三方应用钉钉扫码登录_第1张图片


下面的示例可用于前后端分离项目应用场景,后端以springboot,前端以html。

前端实现

内嵌二维码扫码登录,不走页面重定向。

DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>登录title>
    <script src="http://code.jquery.com/jquery-2.1.1.min.js">script>
    <script src="https://g.alicdn.com/dingding/h5-dingtalk-login/0.21.0/ddlogin.js">script>
    <style>
        /* STEP2:指定这个包裹容器元素的CSS样式,尤其注意宽高的设置 */
        .self-defined-classname {
            width: 300px;
            height: 300px;
            border: 1px #000 solid;
        }
    style>
head>
<body>
	
	<div id="self_defined_element" class="self-defined-classname">div>
body>
html>
<script>
    // STEP3:在需要的时候,调用 window.DTFrameLogin 方法构造登录二维码,并处理登录成功或失败的回调。
    window.DTFrameLogin(
        {
            id: 'self_defined_element',
            width: 300,
            height: 300,
        },
        {
        	// 回调地址:可以理解为用户扫码同意后钉钉会重定向到你指定的地址并把授权码authCode拼接到该地址参数92
            redirect_uri: encodeURIComponent('http://xxxxxxxxxxxxx'), 
            client_id: 'xxxxxxxxxxxxxxxxxxxx', // 你的企业内部应用appKey
            scope: 'openid',	// 保持不变
            response_type: 'code',	// 保持不变
            state: 'pc',	// 跟随authCode原样返回
            prompt: 'consent',	// 值为consent时,会进入授权确认页
        },
        (loginResult) => {
            const {redirectUrl, authCode, state} = loginResult;
            // 用户扫码成功后获取到重定向地址以及同意的授权码,这里不做重定向地址。
            // 通过拿到的授权码调用我们第三方应用的钉钉授权码登录接口换取用户凭证token
            $.ajax({
                url:`http://8cyif2.natappfree.cc/dingTalk/auth?authCode=${authCode}`,
                method:'get',
                contentType:'application/json',
                dataType:'json',
                success:function(data) {
                    if(data.code == 0) {
                    	// 获取token并缓存下来
                        window.localStorage.setItem('token', data.data.adminToken);
                        // 重定向到首页,这样钉钉扫码授权登录就全部完成了
                        window.location.href = 'http://8cyif2.natappfree.cc/oa2login/index'
                    }else {
                        alert(data.msg);
                        return;
                    }
                },
                error: function(err){
                    alert(err);
                }
            });
        },
        (errorMsg) => {
            // 这里一般需要展示登录失败的具体原因
            alert(`Login Error: ${errorMsg}`);
        },
    );
script>


后端实现

将钉钉二维码扫描得到的授权码authCode,提交到我们的第三方应用服务端授权接口。

/**
     * 钉钉授权码登录
     * @param authCode  授权码
     */
    @GetMapping("auth")
    @ApiOperation("钉钉授权码登录")
    public Result dingTalkLogin(@RequestParam(value = "authCode") String authCode) throws Exception {
        Client client = DingTalkAuthClient.oauth2Client();
        GetUserTokenRequest tokenRequest = new GetUserTokenRequest()
                .setClientId(appKey)
                .setClientSecret(appSecret)
                .setCode(authCode)
                .setGrantType("authorization_code");
        GetUserTokenResponse tokenResponse = client.getUserToken(tokenRequest);
        String accessToken = tokenResponse.getBody().getAccessToken();
        // 根据用户凭证调用钉钉开放接口获取用户信息
        GetUserResponseBody userInfo = DingTalkAuthClient.getUserInfo(accessToken);
        // TODO 这里获取钉钉绑定的手机号,当然你也可以进一步去获取unionid或userid(具体根据自己实际业务去实现)
        String mobile = userInfo.getMobile();
        if(mobile == null) {
            return Result.error(202, "该钉钉没有绑定手机号");
        }
        // TODO 这里根据绑定的钉钉手机号获取用户凭证token(具体根据自己实际业务去实现)
        return loginByMobile(mobile);
    }

上述 DingTalkAuthClient 是我自己封装的工具类,(包含一些其他方法)代码如下:

import com.aliyun.dingtalkcontact_1_0.models.GetUserHeaders;
import com.aliyun.dingtalkcontact_1_0.models.GetUserResponseBody;
import com.aliyun.dingtalkoauth2_1_0.Client;
import com.aliyun.dingtalkoauth2_1_0.models.GetAccessTokenRequest;
import com.aliyun.dingtalkoauth2_1_0.models.GetAccessTokenResponse;
import com.aliyun.teaopenapi.models.Config;
import com.aliyun.teautil.models.RuntimeOptions;
import com.dingtalk.api.DefaultDingTalkClient;
import com.dingtalk.api.request.OapiV2UserGetRequest;
import com.dingtalk.api.request.OapiV2UserGetuserinfoRequest;
import com.dingtalk.api.response.OapiV2UserGetResponse;
import com.dingtalk.api.response.OapiV2UserGetuserinfoResponse;
import com.xxx.core.base.exception.GlobalException;
import com.xxx.core.servlet.ApplicationContextHolder;
import com.taobao.api.ApiException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import java.util.concurrent.TimeUnit;

public class DingTalkAuthClient {

    public final static String DING_TALK_ACCESS_TOKEN = "dingTalk:accessToken";

    public static Client oauth2Client() throws Exception {
        Config config = new Config();
        config.protocol = "https";
        config.regionId = "central";
        return new Client(config);
    }

    public 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);
    }

    /**
     * 获取钉钉用户信息
     * @param accessToken 授权token
     * @return
     * @throws Exception
     */
    public static GetUserResponseBody getUserInfo(String accessToken) throws Exception {
        com.aliyun.dingtalkcontact_1_0.Client client = contactClient();
        GetUserHeaders userHeaders = new GetUserHeaders();
        userHeaders.xAcsDingtalkAccessToken = accessToken;
        GetUserResponseBody responseBody = client.getUserWithOptions("me", userHeaders, new RuntimeOptions()).getBody();
        return responseBody;
    }

    /**
     * 获取企业内部应用accessToken
     * - 已实现缓存机制(自动续期)
     * @param appKey
     * @param appSecret
     * @return
     * @throws Exception
     */
    public static String getAppAccessToken(String appKey, String appSecret) throws Exception {
        RedisTemplate redisTemplate = ApplicationContextHolder.getBean("redisTemplate");
        Long expire = redisTemplate.opsForValue().getOperations().getExpire(DING_TALK_ACCESS_TOKEN);
        if(expire > 120) {
            return (String) redisTemplate.opsForValue().get(DING_TALK_ACCESS_TOKEN);
        }
        Client client = oauth2Client();
        GetAccessTokenRequest accessTokenRequest = new GetAccessTokenRequest();
        accessTokenRequest.appKey = appKey;
        accessTokenRequest.appSecret = appSecret;
        GetAccessTokenResponse response = client.getAccessToken(accessTokenRequest);
        String accessToken = response.getBody().accessToken;
        Long expireIn = response.getBody().expireIn;
        redisTemplate.opsForValue().set(DING_TALK_ACCESS_TOKEN, accessToken, expireIn, TimeUnit.SECONDS);
        return accessToken;
    }

    /**
     * (钉钉应用内免密)通过accessToken
     * @param access_token    应用授权码
     * @param code  免密临时用户授权码
     * @return
     */
    public static String getUseridByAccessToken(String access_token, String code) throws ApiException {
        DefaultDingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/topapi/v2/user/getuserinfo");
        OapiV2UserGetuserinfoRequest userinfoRequest = new OapiV2UserGetuserinfoRequest();
        userinfoRequest.setCode(code);
        OapiV2UserGetuserinfoResponse response = client.execute(userinfoRequest, access_token);
        if(!response.isSuccess()) {
            log.error("调用钉钉接口失败,错误代码【{}】,错误详情:{}", response.getErrcode(), response.getErrmsg());
            throw new GlobalException("调用钉钉接口失败,错误原因:" + response.getErrmsg());
        }
        return response.getResult().getUserid();
    }

    /**
     * (钉钉应用内免密)获取钉钉用户信息
     * - "企业员工手机号信息权限"、“成员信息读权限”
     * @param appAccessToken    应用授权码
     * @param userid            钉钉userid
     * @return
     * @throws ApiException
     */
    public static OapiV2UserGetResponse.UserGetResponse getUserInfoByUserid(String appAccessToken, String userid) throws ApiException {
        DefaultDingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/topapi/v2/user/get");
        OapiV2UserGetRequest userGetRequest = new OapiV2UserGetRequest();
        userGetRequest.setUserid(userid);
        userGetRequest.setLanguage("zh_CN");
        OapiV2UserGetResponse response = client.execute(userGetRequest, appAccessToken);
        if(!response.isSuccess()) {
            log.error("调用钉钉接口失败,错误代码【{}】,错误详情:{}", response.getErrcode(), response.getErrmsg());
            throw new GlobalException("调用钉钉接口失败,错误原因:" + response.getErrmsg());
        }
        return response.getResult();
    }

}

你可能感兴趣的:(springboot,钉钉,扫码登录,第三方)