Java钉钉开发_02_免登授权(身份验证)(附源码)

源码已上传GitHub: https://github.com/shirayner/DingTalk_Demo

一、本节要点

1.免登授权的流程

(1)签名校验

(2)获取code,并传到后台

(3)根据code获取userid

(4)根据userid获取用户信息,(此处可进行相应业务处理)

(5)将用户信息传到前端,前端拿到用户信息,并做相应处理

2.计算签名信息(signature)

2.1 待签名参数

ticket jsapi_ticket
nonceStr        随机字符串,随机生成
timeStamp 时间戳
url 当前网页的URL,不包含#及其后面部分

2.2签名流程

(1)字典序

将所有待签名参数按照字段名的ASCII 码从小到大排序(字典序)后,使用URL键值对的格式 (即 key1=value1&key2=value2…)拼接成字符串string1

如:String string1= "jsapi_ticket=" + jsTicket + "&noncestr=" + nonceStr + "&timestamp=" + timeStamp + "&url=" + url;

(2)SHA-1签名,得到 signature

/**
     * @desc : 3.生成签名的函数
     *
     * @param ticket jsticket
     * @param nonceStr 随机串,自己定义
     * @param timeStamp 生成签名用的时间戳
     * @param url 需要进行免登鉴权的页面地址,也就是执行dd.config的页面地址
     * @return
     * @throws Exception String
     */

    public static String getSign(String jsTicket, String nonceStr, Long timeStamp, String url) throws Exception {
        String plainTex = "jsapi_ticket=" + jsTicket + "&noncestr=" + nonceStr + "&timestamp=" + timeStamp + "&url=" + url;
        System.out.println(plainTex);
        try {
            MessageDigest crypt = MessageDigest.getInstance("SHA-1");
            crypt.reset();
            crypt.update(plainTex.getBytes("UTF-8"));
            return byteToHex(crypt.digest());
        } catch (NoSuchAlgorithmException e) {
            throw new Exception(e.getMessage());
        } catch (UnsupportedEncodingException e) {
            throw new Exception(e.getMessage());
        }
    }  

    //将bytes类型的数据转化为16进制类型
    private static String byteToHex(byte[] hash) {
        Formatter formatter = new Formatter();
        for (byte b : hash) {
            formatter.format("%02x", new Object[] { Byte.valueOf(b) });
        }
        String result = formatter.toString();
        formatter.close();
        return result;
    }

3.签名校验的流程

3.1 后端准备好前端校验参数

后台方法:getConfig(HttpServletRequest)

    public static String getConfig(HttpServletRequest request){  

        //1.准备好参与签名的字段
        /*
         *以http://localhost/test.do?a=b&c=d为例
         *request.getRequestURL的结果是http://localhost/test.do
         *request.getQueryString的返回值是a=b&c=d
         */
        String urlString = request.getRequestURL().toString();
        String queryString = request.getQueryString();

        String queryStringEncode = null;
        String url;
        if (queryString != null) {
            queryStringEncode = URLDecoder.decode(queryString);
            url = urlString + "?" + queryStringEncode;
        } else {
            url = urlString;
        }

        String nonceStr=UUID.randomUUID().toString();      //随机数
        long timeStamp = System.currentTimeMillis() / 1000;     //时间戳参数  

        String signedUrl = url;
        String accessToken = null;
        String ticket = null;
        String signature = null;       //签名

        //2.进行签名,获取signature
        try {
            accessToken=AuthHelper.getAccessToken(Env.CORP_ID, Env.CORP_SECRET);  

            ticket=AuthHelper.getJsapiTicket(accessToken);
            signature=getSign(ticket,nonceStr,timeStamp,signedUrl);  

        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }  

        System.out.println("accessToken:"+accessToken);
        System.out.println("ticket:"+ticket);
        System.out.println("nonceStr:"+nonceStr);
        System.out.println("timeStamp:"+timeStamp);
        System.out.println("signedUrl:"+signedUrl);
        System.out.println("signature:"+signature);
        System.out.println("agentId:"+Env.AGENTID);
        System.out.println("corpId:"+Env.CORP_ID);

           String configValue = "{jsticket:‘" + ticket + "‘,signature:‘" + signature + "‘,nonceStr:‘" + nonceStr + "‘,timeStamp:‘"
                    + timeStamp + "‘,corpId:‘" + Env.CORP_ID + "‘,agentId:‘" + Env.AGENTID + "‘}";
            System.out.println(configValue);

        return configValue;
    }  

3.2 前端接收后台参数

在前端调用后端方法,获取dd.config所需的校验参数:‘url’,‘nonceStr’,‘agentId’,‘timeStamp’,‘corpId’,‘signature’。

<script type="text/javascript">
    var _config =<%=com.ray.dingtalk.auth.AuthHelper.getConfig(request)%>;
</script>

3.3 执行前端 dd.config ,进行签名校验

dd.config 用接收到的 nonceStr、agentId、timeStamp、corpId这四个参数去钉钉官方后端计算出一个签名(signature ), 并将这个签名与我们后端所计算的signature来进行比对,若一致,则校验通过。若不一致,则是我们后端计算签名的时候出错了。此时可根据错误消息提示去进行调试。

dd.config({
    agentId : _config.agentId,
    corpId : _config.corpId,
    timeStamp : _config.timeStamp,
    nonceStr : _config.nonceStr,
    signature : _config.signature,
    jsApiList : [                           //需要调用的借口列表
        ‘runtime.info‘,
        ‘biz.contact.choose‘,            //选择用户接口
        ‘device.notification.confirm‘,
        ‘device.notification.alert‘,   //confirm,alert,prompt都是弹出小窗口的接口
        ‘device.notification.prompt‘,
        ‘biz.ding.post‘,
        ‘biz.util.openLink‘ ]
});

3.4 异常:js加载顺序有误所引起的 前端什么信息都不提示

出现这个原因,可能是自己js出错了。我的原因是js加载顺序有误。

请注意这几个js的加载顺序: _config,jquery-3.2.1.min.js 必须在auth.js之前加载

<script src="js/jquery-3.2.1.min.js"></script>
<script type="text/javascript" src="http://g.alicdn.com/dingding/open-develop/1.6.9/dingtalk.js"></script>

<script type="text/javascript">
    var _config =<%=com.ray.dingtalk.auth.AuthHelper.getConfig(request)%>;
</script>
<script type="text/javascript" src="js/auth.js"></script>

4. 将code送往后端:ajax

签名校验成功之后,即dd.config校验成功之后,会执行dd.ready函数,这时我们就可以使用钉钉的jsapi了。

签名校验成功后,我们就可以调用获取免登授权码(CODE)的jsapi,来获取code,然后通过ajax方式将这个code传到后台userInfoServlet

 /**获取免登授权码 CODE
     *
     */
    dd.runtime.permission.requestAuthCode({
        corpId : _config.corpId,
        onSuccess : function(info) {                                                   //成功获得code值,code值在info中
            alert(‘authcode: ‘ + info.code);
            /*
             *$.ajax的是用来使得当前js页面和后台服务器交互的方法
             *参数url:是需要交互的后台服务器处理代码,userInfoServlet
             *参数type:指定和后台交互的方法,因为后台servlet代码中处理Get和post的doGet和doPost
             *data:负责传递请求参数
             *其中success方法和error方法是回调函数,分别表示成功交互后和交互失败情况下处理的方法
             */
            $.ajax({
                type : "POST",
                url : "http://p65s3p.natappfree.cc/DingTalk_Demo/userInfoServlet",
                data : {
                    code : info.code
                },
                success : function(data, status, xhr) {
                    alert(data);
                    var userInfo = JSON.parse(data);

                    document.getElementById("userName").innerHTML = userInfo.name;
                    document.getElementById("userId").innerHTML = userInfo.userid;

                    // 图片
                    if(info.avatar.length != 0){
                        var img = document.getElementById("userImg");
                        img.src = info.avatar;
                        img.height = ‘200‘;
                        img.width = ‘200‘;
                    }

                },
                error : function(xhr, errorType, error) {
                    logger.e("yinyien:" + _config.corpId);
                    alert(errorType + ‘, ‘ + error);
                }
            });  

        },
        onFail : function(err) {                                                       //获得code值失败
            alert(‘fail: ‘ + JSON.stringify(err));
        }
    });  

5.根据code获取userid

    private static final String GET_USERINFO_BYCODE_URL="https://oapi.dingtalk.com/user/getuserinfo?access_token=ACCESSTOKEN&code=CODE";

/** 5.根据免登授权码Code查询免登用户userId
     * @desc :钉钉服务器返回的用户信息为:
     * userid    员工在企业内的UserID
     * deviceId    手机设备号,由钉钉在安装时随机产生
     * is_sys    是否是管理员
     * sys_level    级别,0:非管理员 1:超级管理员(主管理员) 2:普通管理员(子管理员) 100:老板
     *
     * @param accessToken
     * @param code
     * @throws Exception void
     */
    public JSONObject getUserInfo(String accessToken,String code) throws Exception {

        //1.获取请求url
        String url=GET_USERINFO_BYCODE_URL.replace("ACCESSTOKEN", accessToken).replace("CODE", code);

        //2.发起GET请求,获取返回结果
        JSONObject jsonObject=HttpHelper.httpGet(url);
        System.out.println("jsonObject:"+jsonObject.toString());

        //3.解析结果,获取User
        if (null != jsonObject) {
            //4.请求成功,则返回jsonObject
            if (0==jsonObject.getInteger("errcode")) {
                return jsonObject;
            }
            //5.错误消息处理
            if (0 != jsonObject.getInteger("errcode")) {
                int errCode = jsonObject.getInteger("errcode");
                String errMsg = jsonObject.getString("errmsg");
                throw new Exception("error code:"+errCode+", error message:"+errMsg);
            }
        }   

        return null;
    }

6.根据userid获取用户信息

    private static final String GET_USER_URL="https://oapi.dingtalk.com/user/get?access_token=ACCESSTOKEN&userid=USERID";

    /** 2.根据userid获取成员详情
     * @desc :获取成员详情
     *   参考文档: https://open-doc.dingtalk.com/docs/doc.htm?spm=0.0.0.0.jjSfQQ&treeId=371&articleId=106816&docType=1#s0
     * @param accessToken
     * @param userId void
     * @throws Exception
     */
    public JSONObject getUser(String accessToken, String userId) throws Exception {

        //1.获取请求url
        String url=GET_USER_URL.replace("ACCESSTOKEN", accessToken).replace("USERID", userId);

        //2.发起GET请求,获取返回结果
        JSONObject jsonObject=HttpHelper.httpGet(url);
        System.out.println("jsonObject:"+jsonObject.toString());
        //3.解析结果,获取User
        if (null != jsonObject) {
            //4.请求成功,则返回jsonObject
            if (0==jsonObject.getInteger("errcode")) {
                return jsonObject;
            }
            //5.错误消息处理
            if (0 != jsonObject.getInteger("errcode")) {
                int errCode = jsonObject.getInteger("errcode");
                String errMsg = jsonObject.getString("errmsg");
                throw new Exception("error code:"+errCode+", error message:"+errMsg);
            }
        }   

        return null;
    }

7.将用户信息传到前端

注意:传输格式为json

//3.通过userid换取用户信息
            JSONObject jsonObject=us.getUser(accessToken, userId);
            result=JSON.toJSON(jsonObject);

PrintWriter out = response.getWriter();
        out.print(result);
        out.close();
        out = null;  

8.前端接收用户信息后做相应处理

jsp中代码:

<div align="center">
        <img id="userImg" alt="头像" src="">
    </div>

    <div align="center">
        <span>UserName:</span>
        <div id="userName" style="display: inline-block"></div>
    </div>

    <div align="center">
        <span>UserId:</span>
        <div id="userId" style="display: inline-block"></div>
    </div>

js中代码:发送code的ajax调用成功后

success : function(data, status, xhr) {
                    alert(data);
                    //接收后端发送过来的用户信息
                    var userInfo = JSON.parse(data);

                    //收到用户信息后所做的处理
                    document.getElementById("userName").innerHTML = userInfo.name;
                    document.getElementById("userId").innerHTML = userInfo.userid;

                    // 图片
                    if(info.avatar.length != 0){
                        var img = document.getElementById("userImg");
                        img.src = info.avatar;
                        img.height = ‘200‘;
                        img.width = ‘200‘;
                    }

                },

二、代码实现

1.钉钉参数配置——Env.java

将Env.java中的配置修改成你自己的

package com.ray.dingtalk.config;

/**@desc  : 企业应用接入时的常量定义
 *
 * @author: shirayner
 * @date  : 2017年9月27日 下午4:57:36
 */

public class Env {

    /**
     * 企业应用接入秘钥相关
     */
    public static final String CORP_ID = "ding6d4828968696691535c2f4657eb6378f";
    public static final String CORP_SECRET = "ZigmkCY4VcsGUhLIzmfxOmP0ElJbGI5uBhn-2mPelovnjPcA6e4LrjpYXQQw89Q4";
    public static final String SSO_Secret = "YgIGtCHmcwAmOuKsAo_lgqJJiOwyez2G6vBvhCf1zwR6kZ5DGMJsxOcUgK5p1C";
    public static final String AGENTID = "128838526";

    /**
     * DING API地址
     */
    public static final String OAPI_HOST = "https://oapi.dingtalk.com";
    /**
     * 企业应用后台地址,用户管理后台免登使用
     */
    public static final String OA_BACKGROUND_URL = "";

    /**
     * 企业通讯回调加密Token,注册事件回调接口时需要传递给钉钉服务器
     */
    public static final String TOKEN = "";
    public static final String ENCODING_AES_KEY = "";

}

2.Http请求工具类——HttpHelper.java

主要包括发送GET请求和POST请求

package com.ray.dingtalk.util;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Formatter;

import javax.servlet.http.HttpServletRequest;

import org.apache.http.HttpEntity;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.util.EntityUtils;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.ray.dingtalk.auth.AuthHelper;
import com.ray.dingtalk.config.Env;

/**
 * HTTP请求封装,建议直接使用sdk的API
 */
public class HttpHelper {

    /**
     * @desc :1.发起GET请求
     *
     * @param url
     * @return JSONObject
     * @throws Exception
     */
    public static JSONObject httpGet(String url) throws Exception {
        //1.创建httpClient
        CloseableHttpClient httpClient = HttpClients.createDefault();
        //2.生成一个请求
        HttpGet httpGet = new HttpGet(url);
        //3.配置请求的属性
        RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(2000).setConnectTimeout(2000).build();
        httpGet.setConfig(requestConfig);

        //4.发起请求,获取响应信息
        CloseableHttpResponse response = null;
        try {
            response = httpClient.execute(httpGet, new BasicHttpContext());

            //如果返回结果的code不等于200,说明出错了
            if (response.getStatusLine().getStatusCode() != 200) {

                System.out.println("request url failed, http code=" + response.getStatusLine().getStatusCode()
                        + ", url=" + url);
                return null;
            }
            //5.解析请求结果
            HttpEntity entity = response.getEntity();      //reponse返回的数据在entity中
            if (entity != null) {
                String resultStr = EntityUtils.toString(entity, "utf-8");  //将数据转化为string格式  

                JSONObject result = JSON.parseObject(resultStr);    //将String转换为 JSONObject
                if (result.getInteger("errcode") == 0) {
                    return result;
                } else {
                    System.out.println("request url=" + url + ",return value=");
                    System.out.println(resultStr);
                    int errCode = result.getInteger("errcode");
                    String errMsg = result.getString("errmsg");
                    throw new Exception("error code:"+errCode+", error message:"+errMsg);
                }
            }
        } catch (IOException e) {
            System.out.println("request url=" + url + ", exception, msg=" + e.getMessage());
            e.printStackTrace();
        } finally {
            if (response != null) try {
                response.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        return null;
    }

    /** 2.发起POST请求
     * @desc :
     *
     * @param url
     * @param data
     * @return
     * @throws Exception JSONObject
     */
    public static JSONObject httpPost(String url, Object data) throws Exception {
        HttpPost httpPost = new HttpPost(url);
        CloseableHttpResponse response = null;
        CloseableHttpClient httpClient = HttpClients.createDefault();
        RequestConfig requestConfig = RequestConfig.custom().
                setSocketTimeout(2000).setConnectTimeout(2000).build();
        httpPost.setConfig(requestConfig);
        httpPost.addHeader("Content-Type", "application/json");

        try {
            StringEntity requestEntity = new StringEntity(JSON.toJSONString(data), "utf-8");
            httpPost.setEntity(requestEntity);

            response = httpClient.execute(httpPost, new BasicHttpContext());

            if (response.getStatusLine().getStatusCode() != 200) {

                System.out.println("request url failed, http code=" + response.getStatusLine().getStatusCode()
                        + ", url=" + url);
                return null;
            }
            HttpEntity entity = response.getEntity();
            if (entity != null) {
                String resultStr = EntityUtils.toString(entity, "utf-8");

                JSONObject result = JSON.parseObject(resultStr);
                if (result.getInteger("errcode") == 0) {
                    result.remove("errcode");
                    result.remove("errmsg");
                    return result;
                } else {
                    System.out.println("request url=" + url + ",return value=");
                    System.out.println(resultStr);
                    int errCode = result.getInteger("errcode");
                    String errMsg = result.getString("errmsg");
                    throw new Exception("error code:"+errCode+", error message:"+errMsg);
                }
            }
        } catch (IOException e) {
            System.out.println("request url=" + url + ", exception, msg=" + e.getMessage());
            e.printStackTrace();
        } finally {
            if (response != null) try {
                response.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        return null;
    }

}

3.钉钉相关接口权限的获取工具类——AuthHelper.java

主要包括:AccessToken、JsapiTicket、以及签名校验的工具类

package com.ray.dingtalk.auth;

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Formatter;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

import javax.servlet.http.HttpServletRequest;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.alibaba.fastjson.JSONObject;
import com.ray.dingtalk.config.Env;
import com.ray.dingtalk.util.HttpHelper;

/**
 * 钉钉相关配置参数的获取工具类
 * @desc  : AccessToken和jsticket的获取封装
 *
 * @author: shirayner
 * @date  : 2017年9月27日 下午5:00:25
 */
public class AuthHelper {
    //private static Logger log = LoggerFactory.getLogger(AuthHelper.class);
    //获取access_token的接口地址,有效期为7200秒
    private static final String GET_ACCESSTOKEN_URL="https://oapi.dingtalk.com/gettoken?corpid=CORPID&corpsecret=CORPSECRET"; 

    //获取getJsapiTicket的接口地址,有效期为7200秒
    private static final String GET_JSAPITICKET_URL="https://oapi.dingtalk.com/get_jsapi_ticket?access_token=ACCESSTOKE"; 

    /** 1.获取access_token
     * @desc :
     *
     * @param corpId
     * @param corpSecret
     * @return
     * @throws Exception String
     */
    public static String getAccessToken(String corpId,String corpSecret) throws Exception {
        //1.获取请求url
        String url=GET_ACCESSTOKEN_URL.replace("CORPID", corpId).replace("CORPSECRET", corpSecret);

        //2.发起GET请求,获取返回结果
        JSONObject jsonObject=HttpHelper.httpGet(url);

        //3.解析结果,获取accessToken
        String accessToken="";
        if (null != jsonObject) {
            accessToken=jsonObject.getString("access_token");

            //4.错误消息处理
            if (0 != jsonObject.getInteger("errcode")) {
                int errCode = jsonObject.getInteger("errcode");
                String errMsg = jsonObject.getString("errmsg");
                throw new Exception("error code:"+errCode+", error message:"+errMsg);
            }
        }  

        return accessToken;
    }

    /**
     * 2、获取JSTicket, 用于js的签名计算
     * 正常的情况下,jsapi_ticket的有效期为7200秒,所以开发者需要在某个地方设计一个定时器,定期去更新jsapi_ticket
     * @throws Exception
     */
    public static String getJsapiTicket(String accessToken) throws Exception  {
        //1.获取请求url
        String url=GET_JSAPITICKET_URL.replace("ACCESSTOKE", accessToken);

        //2.发起GET请求,获取返回结果
        JSONObject jsonObject=HttpHelper.httpGet(url);

        //3.解析结果,获取ticket
        String ticket="";
        if (null != jsonObject) {
            ticket=jsonObject.getString("ticket");

            //4.错误消息处理
            if (0 != jsonObject.getInteger("errcode")) {
                int errCode = jsonObject.getInteger("errcode");
                String errMsg = jsonObject.getString("errmsg");
                throw new Exception("error code:"+errCode+", error message:"+errMsg);
            }
        }  

        return ticket;
    }

    /**
     * @desc : 3.生成签名的函数
     *
     * @param ticket jsticket
     * @param nonceStr 随机串,自己定义
     * @param timeStamp 生成签名用的时间戳
     * @param url 需要进行免登鉴权的页面地址,也就是执行dd.config的页面地址
     * @return
     * @throws Exception String
     */

    public static String getSign(String jsTicket, String nonceStr, Long timeStamp, String url) throws Exception {
        String plainTex = "jsapi_ticket=" + jsTicket + "&noncestr=" + nonceStr + "&timestamp=" + timeStamp + "&url=" + url;
        System.out.println(plainTex);
        try {
            MessageDigest crypt = MessageDigest.getInstance("SHA-1");
            crypt.reset();
            crypt.update(plainTex.getBytes("UTF-8"));
            return byteToHex(crypt.digest());
        } catch (NoSuchAlgorithmException e) {
            throw new Exception(e.getMessage());
        } catch (UnsupportedEncodingException e) {
            throw new Exception(e.getMessage());
        }
    }  

    //将bytes类型的数据转化为16进制类型
    private static String byteToHex(byte[] hash) {
        Formatter formatter = new Formatter();
        for (byte b : hash) {
            formatter.format("%02x", new Object[] { Byte.valueOf(b) });
        }
        String result = formatter.toString();
        formatter.close();
        return result;
    }

    /**
     * @desc :获取前端jsapi需要的配置参数(已弃用,请用getConfig(HttpServletRequest))
     *
     * @param request      request:在钉钉中点击微应用图标跳转的url地址
     * @return Map<String,Object>  将需要的参数存入map,并返回
     */
    public static Map<String, Object> getDDConfig(HttpServletRequest request){  

        Map<String, Object> configMap = new HashMap<String, Object>();

        //1.准备好参与签名的字段
        /*
         *以http://localhost/test.do?a=b&c=d为例
         *request.getRequestURL的结果是http://localhost/test.do
         *request.getQueryString的返回值是a=b&c=d
         */
        String urlString = request.getRequestURL().toString();
        String queryString = request.getQueryString();

        String queryStringEncode = null;
        String url;
        if (queryString != null) {
            queryStringEncode = URLDecoder.decode(queryString);
            url = urlString + "?" + queryStringEncode;
        } else {
            url = urlString;
        }

        String nonceStr=UUID.randomUUID().toString();      //随机数
        long timeStamp = System.currentTimeMillis() / 1000;     //时间戳参数  

        String signedUrl = url;
        String accessToken = null;
        String ticket = null;
        String signature = null;       //签名

        //2.进行签名,获取signature
        try {
            accessToken=AuthHelper.getAccessToken(Env.CORP_ID, Env.CORP_SECRET);  

            ticket=AuthHelper.getJsapiTicket(accessToken);
            signature=getSign(ticket,nonceStr,timeStamp,signedUrl);  

        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }  

        System.out.println("accessToken:"+accessToken);
        System.out.println("ticket:"+ticket);
        System.out.println("nonceStr:"+nonceStr);
        System.out.println("timeStamp:"+timeStamp);
        System.out.println("signedUrl:"+signedUrl);
        System.out.println("signature:"+signature);
        System.out.println("agentId:"+Env.AGENTID);
        System.out.println("corpId:"+Env.CORP_ID);

        //3.将配置参数存入Map
        configMap.put("agentId", Env.AGENTID);
        configMap.put("corpId", Env.CORP_ID);
        configMap.put("timeStamp", timeStamp);
        configMap.put("nonceStr", nonceStr);
        configMap.put("signature", signature);

        return configMap;
    }  

    public static String getConfig(HttpServletRequest request){  

        //1.准备好参与签名的字段
        /*
         *以http://localhost/test.do?a=b&c=d为例
         *request.getRequestURL的结果是http://localhost/test.do
         *request.getQueryString的返回值是a=b&c=d
         */
        String urlString = request.getRequestURL().toString();
        String queryString = request.getQueryString();

        String queryStringEncode = null;
        String url;
        if (queryString != null) {
            queryStringEncode = URLDecoder.decode(queryString);
            url = urlString + "?" + queryStringEncode;
        } else {
            url = urlString;
        }

        String nonceStr=UUID.randomUUID().toString();      //随机数
        long timeStamp = System.currentTimeMillis() / 1000;     //时间戳参数  

        String signedUrl = url;
        String accessToken = null;
        String ticket = null;
        String signature = null;       //签名

        //2.进行签名,获取signature
        try {
            accessToken=AuthHelper.getAccessToken(Env.CORP_ID, Env.CORP_SECRET);  

            ticket=AuthHelper.getJsapiTicket(accessToken);
            signature=getSign(ticket,nonceStr,timeStamp,signedUrl);  

        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }  

        System.out.println("accessToken:"+accessToken);
        System.out.println("ticket:"+ticket);
        System.out.println("nonceStr:"+nonceStr);
        System.out.println("timeStamp:"+timeStamp);
        System.out.println("signedUrl:"+signedUrl);
        System.out.println("signature:"+signature);
        System.out.println("agentId:"+Env.AGENTID);
        System.out.println("corpId:"+Env.CORP_ID);

           String configValue = "{jsticket:‘" + ticket + "‘,signature:‘" + signature + "‘,nonceStr:‘" + nonceStr + "‘,timeStamp:‘"
                    + timeStamp + "‘,corpId:‘" + Env.CORP_ID + "‘,agentId:‘" + Env.AGENTID + "‘}";
            System.out.println(configValue);

        return configValue;
    }  

}

4.用户业务类——UserService.java

package com.ray.dingtalk.service.contact;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.ray.dingtalk.model.contact.User;
import com.ray.dingtalk.util.HttpHelper;

/**@desc  :
 *
 * @author: shirayner
 * @date  : 2017年9月28日 上午9:53:51
 */
public class UserService {

    private static final String CREATE_USER_URL="https://oapi.dingtalk.com/user/create?access_token=ACCESSTOKEN";
    private static final String GET_USER_URL="https://oapi.dingtalk.com/user/get?access_token=ACCESSTOKEN&userid=USERID";
    private static final String GET_DEPARTMENTUSER_URL="https://oapi.dingtalk.com/user/simplelist?access_token=ACCESSTOKEN&department_id=DEPARTMENTID";
    private static final String GET_DEPARTMENTUSERDETAIL_URL="https://oapi.dingtalk.com/user/list?access_token=ACCESSTOKEN&department_id=DEPARTMENTID";
    private static final String GET_USERINFO_BYCODE_URL="https://oapi.dingtalk.com/user/getuserinfo?access_token=ACCESSTOKEN&code=CODE";

    /**1.创建用户
     * @desc :
     *
     * @param accessToken
     * @param user
     * @return
     * @throws Exception String
     */
    public String createUser(String accessToken,User user) throws Exception {
        //1.准备POST请求参数
        Object data=JSON.toJSON(user);
        System.out.println(data);

        //2.获取请求url
        String url=CREATE_USER_URL.replace("ACCESSTOKEN", accessToken);

        //3.发起POST请求,获取返回结果
        JSONObject jsonObject=HttpHelper.httpPost(url, data);
        System.out.println("jsonObject:"+jsonObject.toString());

        //4.解析结果,获取UserId
        String userId="";
        if (null != jsonObject) {
            userId=jsonObject.getString("userid");
            //5.错误消息处理
            if (0 != jsonObject.getInteger("errcode")) {
                int errCode = jsonObject.getInteger("errcode");
                String errMsg = jsonObject.getString("errmsg");
                throw new Exception("error code:"+errCode+", error message:"+errMsg);
            }
        }   

        return userId;
    }

    /** 2.根据userid获取成员详情
     * @desc :获取成员详情
     *   参考文档: https://open-doc.dingtalk.com/docs/doc.htm?spm=0.0.0.0.jjSfQQ&treeId=371&articleId=106816&docType=1#s0
     * @param accessToken
     * @param userId void
     * @throws Exception
     */
    public JSONObject getUser(String accessToken, String userId) throws Exception {

        //1.获取请求url
        String url=GET_USER_URL.replace("ACCESSTOKEN", accessToken).replace("USERID", userId);

        //2.发起GET请求,获取返回结果
        JSONObject jsonObject=HttpHelper.httpGet(url);
        System.out.println("jsonObject:"+jsonObject.toString());
        //3.解析结果,获取User
        if (null != jsonObject) {
            //4.请求成功,则返回jsonObject
            if (0==jsonObject.getInteger("errcode")) {
                return jsonObject;
            }
            //5.错误消息处理
            if (0 != jsonObject.getInteger("errcode")) {
                int errCode = jsonObject.getInteger("errcode");
                String errMsg = jsonObject.getString("errmsg");
                throw new Exception("error code:"+errCode+", error message:"+errMsg);
            }
        }   

        return null;
    }

    /** 3.获取部门成员
     * @desc :
     *
     * @param accessToken
     * @param departmentId
     * @throws Exception void
     */
    public void getDepartmentUser(String accessToken, String departmentId) throws Exception {

        //1.获取请求url
        String url=GET_DEPARTMENTUSER_URL.replace("ACCESSTOKEN", accessToken).replace("DEPARTMENTID", departmentId);

        //2.发起GET请求,获取返回结果
        JSONObject jsonObject=HttpHelper.httpGet(url);
        System.out.println("jsonObject:"+jsonObject.toString());

        //3.解析结果,获取User
        if (null != jsonObject) {  

            //4.错误消息处理
            if (0 != jsonObject.getInteger("errcode")) {
                int errCode = jsonObject.getInteger("errcode");
                String errMsg = jsonObject.getString("errmsg");
                throw new Exception("error code:"+errCode+", error message:"+errMsg);
            }
        }
    }

    /** 4.获取部门成员(详情)
     * @desc :
     *
     * @param accessToken
     * @param departmentId
     * @throws Exception void
     */
    public void getDepartmentUserDetail(String accessToken, String departmentId) throws Exception {

        //1.获取请求url
        String url=GET_DEPARTMENTUSERDETAIL_URL.replace("ACCESSTOKEN", accessToken).replace("DEPARTMENTID", departmentId);

        //2.发起GET请求,获取返回结果
        JSONObject jsonObject=HttpHelper.httpGet(url);
        System.out.println("jsonObject:"+jsonObject.toString());

        //3.解析结果,获取User
        if (null != jsonObject) {  

            //4.错误消息处理
            if (0 != jsonObject.getInteger("errcode")) {
                int errCode = jsonObject.getInteger("errcode");
                String errMsg = jsonObject.getString("errmsg");
                throw new Exception("error code:"+errCode+", error message:"+errMsg);
            }
        }
    }

    /** 5.根据免登授权码Code查询免登用户userId
     * @desc :钉钉服务器返回的用户信息为:
     * userid    员工在企业内的UserID
     * deviceId    手机设备号,由钉钉在安装时随机产生
     * is_sys    是否是管理员
     * sys_level    级别,0:非管理员 1:超级管理员(主管理员) 2:普通管理员(子管理员) 100:老板
     *
     * @param accessToken
     * @param code
     * @throws Exception void
     */
    public JSONObject getUserInfo(String accessToken,String code) throws Exception {

        //1.获取请求url
        String url=GET_USERINFO_BYCODE_URL.replace("ACCESSTOKEN", accessToken).replace("CODE", code);

        //2.发起GET请求,获取返回结果
        JSONObject jsonObject=HttpHelper.httpGet(url);
        System.out.println("jsonObject:"+jsonObject.toString());

        //3.解析结果,获取User
        if (null != jsonObject) {
            //4.请求成功,则返回jsonObject
            if (0==jsonObject.getInteger("errcode")) {
                return jsonObject;
            }
            //5.错误消息处理
            if (0 != jsonObject.getInteger("errcode")) {
                int errCode = jsonObject.getInteger("errcode");
                String errMsg = jsonObject.getString("errmsg");
                throw new Exception("error code:"+errCode+", error message:"+errMsg);
            }
        }   

        return null;
    }

}

5.Servlet——UserInfoServlet

(1)UserInfoServlet.java

package com.ray.dingtalk.servlet;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.ray.dingtalk.auth.AuthHelper;
import com.ray.dingtalk.config.Env;
import com.ray.dingtalk.service.contact.UserService;

/**身份认证Servlet:免登
 *
 *
 * Servlet implementation class AuthServlet
 */
@WebServlet("/UserInfoServlet")
public class UserInfoServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;

    public UserInfoServlet() {
        super();
        // TODO Auto-generated constructor stub
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // TODO Auto-generated method stub
        response.getWriter().append("Served at: ").append(request.getContextPath());
    }

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //1.将请求、响应的编码均设置为UTF-8(防止中文乱码)
        request.setCharacterEncoding("UTF-8");
        response.setCharacterEncoding("UTF-8"); 

        //1.获取code
        String code = request.getParameter("code");
        System.out.println("code:"+code);

        Object result=null;
        try {
            //2.通过CODE换取身份userid
            String accessToken = AuthHelper.getAccessToken(Env.CORP_ID, Env.CORP_SECRET);
            UserService us = new UserService();
            String userId=us.getUserInfo(accessToken, code).getString("userid");

            //3.通过userid换取用户信息
            JSONObject jsonObject=us.getUser(accessToken, userId);
            result=JSON.toJSON(jsonObject);

        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        PrintWriter out = response.getWriter();
        out.print(result);
        out.close();
        out = null;
    }

}

(2)web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:web="http://java.sun.com/xml/ns/javaee" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5">
  <welcome-file-list>
    <welcome-file>index.jsp</welcome-file>
  </welcome-file-list>

  <servlet>
    <servlet-name>userInfoServlet</servlet-name>
    <servlet-class>
            com.ray.dingtalk.servlet.UserInfoServlet
    </servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>userInfoServlet</servlet-name>
    <url-pattern>/userInfoServlet</url-pattern>
  </servlet-mapping>

</web-app>

6.前端代码

(1)IDAuthentication.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>身份认证</title>
<script src="js/jquery-3.2.1.min.js"></script>
<script type="text/javascript" src="http://g.alicdn.com/dingding/open-develop/1.6.9/dingtalk.js"></script>

<script type="text/javascript">
    var _config =<%=com.ray.dingtalk.auth.AuthHelper.getConfig(request)%>;
</script>
<script type="text/javascript" src="js/auth.js"></script>

</head>
<body>

    <div align="center">
        <img id="userImg" alt="头像" src="">
    </div>

    <div align="center">
        <span>UserName:</span>
        <div id="userName" style="display: inline-block"></div>
    </div>

    <div align="center">
        <span>UserId:</span>
        <div id="userId" style="display: inline-block"></div>
    </div>

    <div align="center">
        <span class="desc">是否验证成功</span>
        <button class="btn btn_primary" id="yanzheng">ceshi</button>
    </div>
    <div align="center">
        <span class="desc">测试按钮</span>
        <button class="btn btn_primary" id="ceshi">ceshi</button>
    </div>

</body>
</html>

(2)auth.js

dd.config({
    agentId : _config.agentId,
    corpId : _config.corpId,
    timeStamp : _config.timeStamp,
    nonceStr : _config.nonceStr,
    signature : _config.signature,
    jsApiList : [                           //需要调用的借口列表
        ‘runtime.info‘,
        ‘biz.contact.choose‘,            //选择用户接口
        ‘device.notification.confirm‘,
        ‘device.notification.alert‘,   //confirm,alert,prompt都是弹出小窗口的接口
        ‘device.notification.prompt‘,
        ‘biz.ding.post‘,
        ‘biz.util.openLink‘ ]
});

dd.ready(function() {  

    document.getElementById("yanzheng").innerHTML = "验证成功";  

    document.querySelector(‘#ceshi‘).onclick = function () {
        alert("ceshiaaa");
    };

    /* 1.获取容器信息
     *获取容器信息,返回值为ability:版本号,也就是返回容器版本
     *用来表示这个版本的jsapi的能力,来决定是否使用jsapi
     */
    dd.runtime.info({
        onSuccess : function(info) {
            logger.e(‘runtime info: ‘ + JSON.stringify(info));
        },
        onFail : function(err) {
            logger.e(‘fail: ‘ + JSON.stringify(err));
        }
    });      

    /**获取免登授权码 CODE
     *
     */
    dd.runtime.permission.requestAuthCode({
        corpId : _config.corpId,
        onSuccess : function(info) {                                                   //成功获得code值,code值在info中
            alert(‘authcode: ‘ + info.code);
            /*
             *$.ajax的是用来使得当前js页面和后台服务器交互的方法
             *参数url:是需要交互的后台服务器处理代码,userInfoServlet
             *参数type:指定和后台交互的方法,因为后台servlet代码中处理Get和post的doGet和doPost
             *data:负责传递请求参数
             *其中success方法和error方法是回调函数,分别表示成功交互后和交互失败情况下处理的方法
             */
            $.ajax({
                type : "POST",
                url : "http://p65s3p.natappfree.cc/DingTalk_Demo/userInfoServlet",
                data : {
                    code : info.code
                },
                success : function(data, status, xhr) {
                    alert(data);
                    //接收后端发送过来的用户信息
                    var userInfo = JSON.parse(data);

                    //收到用户信息后所做的处理
                    document.getElementById("userName").innerHTML = userInfo.name;
                    document.getElementById("userId").innerHTML = userInfo.userid;

                    // 图片
                    if(info.avatar.length != 0){
                        var img = document.getElementById("userImg");
                        img.src = info.avatar;
                        img.height = ‘200‘;
                        img.width = ‘200‘;
                    }

                },
                error : function(xhr, errorType, error) {
                    logger.e("yinyien:" + _config.corpId);
                    alert(errorType + ‘, ‘ + error);
                }
            });  

        },
        onFail : function(err) {                                                       //获得code值失败
            alert(‘fail: ‘ + JSON.stringify(err));
        }
    });  

});  

//在dd.config函数验证失败时执行 dd.error
dd.error(function(err) {                                             //验证失败
    alert("进入到error中");
    document.getElementById("userName").innerHTML = "验证出错";
    alert(‘dd error: ‘ + JSON.stringify(err));
});  

时间: 2024-10-03 09:50:10

Java钉钉开发_02_免登授权(身份验证)(附源码)的相关文章

C#/ASP.NET MVC微信公众号接口开发之从零开发(三)回复消息 (附源码)

C#/ASP.NET MVC微信接口开发文章目录: 1.C#/ASP.NET MVC微信公众号接口开发之从零开发(一) 接入微信公众平台 2.C#/ASP.NET MVC微信公众号接口开发之从零开发(二) 接收微信消息并且解析XML(附源码) 一.拼凑回复的XML字符串 微信被动回复的形式有一下六种: 1 回复文本消息 2 回复图片消息 3 回复语音消息 4 回复视频消息 5 回复音乐消息 6 回复图文消息 分别对应不同的XML形式,这里以文本消息和图文为例,读者举一反三其他的类似,不再赘述:

JAVA代码重用机制复用类之继承语法(附源码)

前言 继承是所有OOP语言和Java语言不可缺少的组成部分.当创建一个类时,总是在继承,因此,除非已明确指出要从其他类中继承,否则就是在隐式地从Java的标准根类Object进行继承. 组合的语法比较平实,但是继承使用的是一种特殊语法.在继承过程中,需要先声明"新类与旧类相似".这种声明是通过在类主体的左边花括号之前,书写后面紧随基类名称的关键字extends而实现的.当这么做时,会自动得到基类中所有的域和方法.例如: 示例源码 基类 package com.mufeng.thesev

java中break和continue的区别详解(附源码)

序言 在自己学习java语言的过程中,很容易把break和continue的用法混淆.为了便于以后快速查阅及温习,在此特留学习笔记一份. 简述 在任何迭代语句的主体部分,都可以用break和continue控制循环的流程.其中,break用于强行退出循环,不执行循环中剩余的语句.而continue则停止执行当前迭代,然后退回循环起始处,开始下一次迭代. 源码 下面这个程序向大家展示了break和continue在for和while循环中的例子: package com.mufeng.thefou

零基础学Java应知道的学习步骤规划与市场行情「附源码和视频」

无论是在校的学生也好,还是转行的也好,如今学JAVA开发的人越来越多,造成了如今新手越来越多,有人就说JAVA饱和了,JAVA才刚开始以一种好的势头发展就饱和了.我也是无语,一般说饱和的人,基本是学的不咋地,找不到工作的,怨天尤人说饱和了,类似于吃不到葡萄说葡萄酸. 纵观中国目前整体行业来说,互联网IT行业 成为了拔尖的行业,机械行业有点夕阳西下的意思,电子行业被国企所垄断,没有关系很难混起来.如果说没有背景,单凭自己能力的话,在如今这个需要钱的社会,IT互联网程序开发成了靠自己能力可以多挣一点

asp.net开发中常见公共捕获异常方式总结(附源码)

本文实例总结了asp.net开发中常见公共捕获异常方式.分享给大家供大家参考,具体如下: 前言:在实际开发过程中,对于一个应用系统来说,应该有自己的一套成熟的异常处理框架,这样当异常发生时,也能得到统一的处理风格,将异常信息优雅 地反馈给开发人员和用户.我们都知道,.net的异常处理是按照“异常链”的方式从底层向高层逐层抛出,如果不能尽可能地早判断异常发生的边界并捕获异 常,CLR会自动帮我们处理,但是这样系统的开销是非常大的,所以异常处理的一个重要原则是“早发现早抛出早处理”.但是本文总结的服

Java之内部类可以被覆盖吗详解(附源码)

前言 如果创建了一个内部类,然后继承其外围类并重新定义此内部类时,会发生什么呢?也就是说,内部类可以被覆盖吗?这看起来似乎是个很有用的思想,但是"覆盖"内部类就好像它是外围类的一个方法,其实并不起什么作用: 示例源码1 package com.mufeng.thetenthchapter; class Egg { private Yolk y; public Egg() { // TODO Auto-generated constructor stub System.out.print

用java实现“钉钉微应用,免登进入某H5系统首页“功能”

一.前言 哈哈,这是我的第一篇博客. 先说一下这个小功能的具体场景: 用户登录钉钉app,点击微应用,获取当前用户的信息,与H5系统的数据库的用户信息对比,如果存在该用户,则点击后直接进入H5系统的首页,否则显示“您无权限”. 补充:又加了一个小需求,就是免登成功,会给该用户发条消息 我是参考钉钉开发文档实现的这个小功能,文档地址:https://ding-doc.dingtalk.com/doc#/serverapi2/clotub 二.准备工作 需要创建一个微应用:https://open-

企业微信开发免登授权时提示scope不能为空,错误代码1001

企业免登授权提示scope不能为空1001 原因是我们是单页面应用url自带#/在微信里面认为#号后面的参数不被识别 后端开发人员把参数放到跳转?URL地址前面,正确形式是 https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx354283df6c21d57f926382d1c&response_type=code&scope=snsapi_privateinfo&agentid=90&redirect_ur

Java企业微信开发_09_身份验证之移动端网页授权(有完整项目源码)

注: 源码已上传github: https://github.com/shirayner/WeiXin_QiYe_Demo 一.本节要点 1.1 授权回调域(可信域名) 在开始使用网页授权之前,需要先设置一下授权回调域.这里瞬间想到之前做JSSDK的时候,也设置过一个域名.二者本质上都是设置可信域名. 当用户授权完毕之后,请求将重定向到此域名(或者子域名)下的执行者(jsp页面或者servlet等).如何设置授权回调域,请见第二节. 1.2 获取Code https://open.weixin.