2

引言

进公司实习后,分配给我的一个任务是要实现等等登录的一个功能,这篇文章将记录我实现的这个功能的具体步骤。望本文章指能帮助你实现用户登录第三方网站(扫码或账密方式)。

一. 确定实现思路

官方文档,确定一下大体的实现思路。官方文档提供了两种方法:
1 是使用钉钉提供的页面登录授权
2 是在应用的登录页面内嵌二维码方式登录授权

本文是使用钉钉提供的页面登录授权实现的。

两者之间的区别:

钉钉提供的页面登录授权:用户会被重定向到钉钉的官方登录页面进行授权,页面风格与钉钉一致。钉钉通过调用钉钉的OAuth2.0接口,将用户重定向到钉钉的授权页面,用户登录并授权后,钉钉将重定向回应用指定的回调地址,并附带授权码。
image.png

内嵌二维码方式登录授权:在页面中嵌入钉钉提供的二维码,用户扫码后,钉钉会将授权结果通过回调通知应用。内嵌二维码方式需要处理二维码生成和回调,页面登录授权则相对简单。

两者就是二维码提供的形式不一样,其他都类型,都是利用的OAuth登录授权。OAuth 2.0授权的流程如下图所示:
image.png

二. 准备工作

需要到钉钉开发者后台创建并配置应用。
创建项目后在基础信息栏可查看client id 和client secret。
image.png
设置需要重定向的url, 只有在这里设置后的URL,才能成功进行重定向
image.png

二. 核心步骤

(1) 对应上面流程图中的步骤2,构造第三方网站访问地址。当用户选择使用钉钉登录时,页面跳转到https://login.dingtalk.com/oauth2/auth这个地址,并携带redirect_uri,response_type等参数。

https://login.dingtalk.com/oauth2/auth?
redirect_uri=https%3A%2F%2Fwww.aaaaa.com%2Fauth
&response_type=code
&client_id=dingxxxxxxx   //应用的AppKey 
&scope=openid   //此处的openId保持不变
&prompt=consent

以上示例中携带的参数都是必填参数,参数说明:
redirect_uri 授权成功后的重定向的url (注:需要对url进行urlencode)
response_type 固定值为code
Client_id 应用的appkey

scope 授权范围,当前只支持两种输入

            openid:授权后可获得用户userid
            openid corpid:授权后可获得用户id和登录过程中用户选择的组织id,空格分隔。注意url编码。

Prompt 值为consent时,会进入授权确认页。
(2)对应流程图中的步骤5,用户进行授权后会向上面redirect_uri设置的URL进行跳转,并携带authCode
image.png
(3)对应流程图中的步骤6,获取authCode并把authCode传到后端
(4)后端获取到authCode后,用authCode换取用户个人的token

/**
     * 获取用户的token
     * @param authCode
     * @return
     */
    @Override
    public ContentModel<String> dtkLogin(String authCode) throws Exception {
        com.aliyun.dingtalkoauth2_1_0.Client client = authClient();
        GetUserTokenRequest getUserTokenRequest = new GetUserTokenRequest()
                .setClientId(dingTalkProperties.getClientId())
                .setClientSecret(dingTalkProperties.getSecret())
                .setCode(authCode)
                .setGrantType("authorization_code");          
             //  获取用户个人token
            GetUserTokenResponse getUserTokenResponse = client.getUserToken(getUserTokenRequest);
            GetUserTokenResponseBody getUserTokenResponseBody = getUserTokenResponse.getBody();
            if (getUserTokenResponseBody == null) {
                return ContentModel.error("获取用户个人token时,getUserTokenResponseBody为null");
            }
            String token = getUserTokenResponseBody.getAccessToken();

    /**
     * 创建一个用于访问钉钉(DingTalk)OAuth 2.0 API的客户端实例
     * @return config
     */
    public static com.aliyun.dingtalkoauth2_1_0.Client authClient() {
        Config config = new Config();
        config.protocol = "https";
        config.regionId = "central";
        try {
            return new com.aliyun.dingtalkoauth2_1_0.Client(config);
        } catch (Exception e) {
            throw new ServiceException("创建com.aliyun.dingtalkoauth2_1_0.Client失败", e);
        }
    }

(5)有了用户的token可通过token换取用户的unionid, 但是用unionid并不能直接获取到用户的个人信息,如姓名,电话号码等。需要企业的access_token和unionid或者企业的access_token和用户的token才能获取到用户的userid,有了userid就可以直接获取到用户的基本信息,也就可以操起其它需要使用用户信息的业务操作。

    /**
     * 获取企业内部的accessToken
     * @return accessToken
     */
    public String getAccessToken() {
        com.aliyun.dingtalkoauth2_1_0.Client client = authClient();
        GetAccessTokenRequest getAccessTokenRequest = new GetAccessTokenRequest()
                .setAppKey(dingTalkProperties.getClientId())
                .setAppSecret(dingTalkProperties.getSecret());
        try {
            GetAccessTokenResponse getAccessTokenResponse = client.getAccessToken(getAccessTokenRequest);
            GetAccessTokenResponseBody getAccessTokenResponseBody = getAccessTokenResponse.getBody();
            if (getAccessTokenResponseBody.getAccessToken() != null && !getAccessTokenResponseBody.getAccessToken().isEmpty()) {
                return getAccessTokenResponseBody.getAccessToken();
            }
        } catch (TeaException err) {
            if (!com.aliyun.teautil.Common.empty(err.code) && !com.aliyun.teautil.Common.empty(err.message)) {
                throw new ServiceException("获取系统内部accessToken失败", err.code, err.message);
            }
        } catch (Exception _err) {
            TeaException err = new TeaException(_err.getMessage(), _err);
            if (!com.aliyun.teautil.Common.empty(err.code) && !com.aliyun.teautil.Common.empty(err.message)) {
                throw new ServiceException("获取系统内部accessToken失败", err.code, err.message);
            }
        }
        return null;
    }

(6)获取unionid 和企业内部的accessToken后,可用uinonid 和企业内部的accessToken获取userid

**
     * 获取userid
     * @param token 用户个人的token
     * @param unionId
     * @return userid
     */
    public OapiUserGetbyunionidResponse.UserGetByUnionIdResponse userGetByUnionId(String token, String unionId) {
        try {
            DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/topapi/user/getbyunionid");
            OapiUserGetbyunionidRequest req = new OapiUserGetbyunionidRequest();
            req.setUnionid(unionId);
            req.setHttpMethod("POST");
            OapiUserGetbyunionidResponse rsp = client.execute(req, token);
            if (rsp != null && rsp.getResult() != null) {
                return rsp.getResult();
            }
        } catch (ApiException e) {
            log.error("获取用户userid发生了错误", e);
        }
        return null;
    }

(7)用userid 获取用户的信息

/**
     * 获取用户信息
     * @param accessToken
     * @param userId
     * @return UserGetResponse
     */
    public OapiV2UserGetResponse.UserGetResponse userGetByUserId(String accessToken, String userId) {
        try {
            DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/topapi/v2/user/get");
            OapiV2UserGetRequest req = new OapiV2UserGetRequest();
            req.setUserid(userId);
            req.setLanguage("zh_CN");
            OapiV2UserGetResponse rsp = client.execute(req, accessToken);
            return rsp.getResult();
        } catch (ApiException e) {
            log.error("用userid获取用户信息失败", e);
            return null;
        }
    }

三. Maven依赖

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

总结

根据上面的操作,就完成了使用钉钉进行登录的功能。上面涉及的方法基本在钉钉开放平台中都有讲解。我在实现这个功能的过程中,在获取userid的过程中被卡住过,因为当时我是使用的用户个人的token和unionId去获取userid, 会报错未在白名单内,没有权限的错误,导致接口请求失败。最后,希望这篇文章能够帮助您快速完成这个功能,谢谢!


吴季分
400 声望13 粉丝