(一)基本概述:

钉钉中实现免登陆的核心思想就是通过corpId和corpSecret这两个参数来获得免登陆码Code,继而通过Code来获取用户信息,并在后台数据库中比对该用户信息是否存在,如果比对成功就免登陆成功。具体实现的流程图如下:

(二)过程详解:

1.注册企业用户和创建微应用:

这个过程比较简单,略过。

2.获取corpId,corpSecret,agentId:

可以登录钉钉企业用户账号直接获得,可以存储在本地文件中,便于后面存取,本人存储在本地的properties文件中。略过。

3.获取access_token:

在钉钉官方文档中有获取access_token的方法介绍,通过get方式向https://oapi.dingtalk.com/gettokencorpid=id&corpsecret=secrect请求access_token数据,其中需要两个参数,分别是corpId和corpSecret,网页响应值就是access_token,具体java实现如下:

定义一个get请求的方法:
public class HttpHelper {
/*
* params:
*       url:需要Get请求的网址
*
* return:
*       返回请求时网页相应的数据,用json存储
*/
public static JSONObject httpGet(String url){//创建httpClientCloseableHttpClient httpClient=HttpClients.createDefault();HttpGet httpGet=new HttpGet(url);                             //生成一个请求RequestConfig requestConfig = RequestConfig.custom().         //配置请求的一些属性setSocketTimeout(2000).setConnectTimeout(2000).build();httpGet.setConfig(requestConfig);                             //为请求设置属性CloseableHttpResponse response=null;try {response=httpClient.execute(httpGet);//如果返回结果的code不等于200,说明出错了if (response.getStatusLine().getStatusCode() != 200) {System.out.println("request url failed, http code=" + response.getStatusLine().getStatusCode()+ ", url=" + url);return null;}HttpEntity entity = response.getEntity();                 //reponse返回的数据在entity中if(entity!=null){String resultStr=EntityUtils.toString(entity,"utf-8");//将数据转化为string格式JSONObject result=JSONObject.parseObject(resultStr);  //将结果转化为json格式if(result.getInteger("errcode")==0){                  //如果返回值得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("ErrorCode:"+errCode+"ErrorMsg"+errMsg); }}} catch (ClientProtocolException e) {// TODO Auto-generated catch blockSystem.out.println("request url=" + url + ", exception, msg=" + e.getMessage());e.printStackTrace();} catch (Exception e) {// TODO Auto-generated catch blockSystem.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;
}

然后是调用httpGet方法获得access_token的代码实现:
public static String getAccess_Token(String corpid,String corpsecret){  String url="https://oapi.dingtalk.com/gettoken?"+"corpid="+corpid+"&corpsecret="+corpsecret;JSONObject res=HttpHelper.httpGet(url);                      //将httpGet方法封装在HttpHelper类中String access_token="";if(res!=null){access_token=res.getString("access_token");}else{new Exception("Cannot resolve field access_token from oapi resonpse");}return access_token;
}

4.获取ticket:

钉钉官方文档有关于ticket获取的介绍,通过get方式向https://oapi.dingtalk.com/get_jsapi_ticket?access_token=ACCESS_TOKE
请求ticket数据,请求时需要携带一个参数access_token,也就是在步骤3中获得的access。具体java代码实现如下:

/*
* 向网页请求ticket值,用Get方式请求网页
* param:
*   access_token:上面得到的access_token值
*
* return:
*   返回值是ticket
*/
public static String getTicket(String access_token){String url="https://oapi.dingtalk.com/get_jsapi_ticket?"+"access_token="+access_token;JSONObject res=HttpHelper.httpGet(url);                                //步骤3中有httpGet的定义,只是封装在HttpHelper类中String ticket="";if(res!=null){ticket=res.getString("ticket");}else{new Exception("Cannot resolve field ticket from oapi resonpse");}return ticket;
}

5.获取签名signatrue:

钉钉官方文档中有关于获取签名的介绍,并且给出了使用的算法,参数说明。所以我们只需要调用它使用的算法,并且做一些格式 的 调整即可。具体java代码实现如下:

/*
* 生成签名的函数
* params:
*   ticket:签名数据
*   nonceStr:签名用的随机字符串,从properties文件中读取
*   timeStamp:生成签名用的时间戳
*   url:当前请求的URL地址
*/
public static String getSign(String ticket, String nonceStr, long timeStamp, String url) throws Exception {String plain = "jsapi_ticket=" + ticket + "&noncestr=" + nonceStr + "×tamp=" + String.valueOf(timeStamp)+ "&url=" + url;try {MessageDigest sha1 = MessageDigest.getInstance("SHA-1");    //安全hash算法sha1.reset();sha1.update(plain.getBytes("UTF-8"));                       //根据参数产生hash值return bytesToHex(sha1.digest());} catch (NoSuchAlgorithmException e) {throw new Exception(e.getMessage());} catch (UnsupportedEncodingException e) {throw new Exception(e.getMessage());}
}//将bytes类型的数据转化为16进制类型
private static String bytesToHex(byte[] hash) {                    //将字符串转化为16进制的数据Formatter formatter = new Formatter();for (byte b : hash) {formatter.format("%02x", b);}String result = formatter.toString();formatter.close();return result;
}

6.封装好所有需要的参数,并且传递到企业应用网址的前端H5中。

需要的参数有corpId,agentId,ticket,signature,nonceStr,timeStamp,url。其中nonceStr,timeStamp,url用来在服务器后台生成signatrue签名,然后将ticket,nonceStr,timeStamp和signatrue传送到前台,前台网页就会调用jsapi的dd.config函数重新生成signatrue,和传进的signatrue进行比较,来实现验证过程。java实现如下:

6.封装好所有需要的参数,并且传递到企业应用网址的前端H5中。需要的参数有corpId,agentId,ticket,signature,nonceStr,timeStamp,url.
其中nonceStr,timeStamp,url用来在服务器后台生成signatrue签名,然后将ticket,nonceStr,timeStamp和signatrue传送到前台,前台网页就会
调用jsapi的dd.config函数重新生成signatrue,和传进的signatrue进行比较,来实现验证过程。
/*
* 将所有需要传送到前端的参数进行打包,在前端会调用jsapi提供的dd.config接口进行签名的验证
*params:
*   request:在钉钉中点击微应用图标跳转的url地址
*return:
*   将需要的参数打包好,按json格式打包
*/
public static String getConfig(HttpServletRequest request){/**以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 url=null;if(queryString!=null){url=urlString+queryString;}else{url=urlString;}String corpId=PropertiesHelp.getValue("corpid");        //一些比较重要的不变得参数本人存储在properties文件中String corpSecret=PropertiesHelp.getValue("corpsecret");String nonceStr=PropertiesHelp.getValue("noncestr");String agentId =PropertiesHelp.getValue("agentid");     //agentid参数long timeStamp = System.currentTimeMillis() / 1000;     //时间戳参数String signedUrl = url;                                 //请求链接的参数,这个链接主要用来生成signatrue,并不需要传到前端String accessToken = null;                              //token参数String ticket = null;                                   //ticket参数String signature = null;                                //签名参数try {accessToken=getAccess_Token(corpId,corpSecret);ticket=getTicket(accessToken);signature=getSign(ticket,nonceStr,timeStamp,signedUrl);} catch (Exception e) {// TODO Auto-generated catch blocke.printStackTrace();}return "{jsticket:'" + ticket + "',signature:'" + signature + "',nonceStr:'" + nonceStr + "',timeStamp:'"+ timeStamp + "',corpId:'" + corpId + "',agentId:'" + agentId+ "'}";
}

7.前台对传进来的参数进行验证,并且生成code值,并且将code值传送给后台服务器程序。

验证过程需要调用jsapi的一些借口,所以我们要在前台网页中引入相应的js文件,引入的方法就是直接在前台网页中包含jsapi的js文件,引入代码如下:

<script type="text/javascript" src="http://g.alicdn.com/ilw/ding/0.7.3/scripts/dingtalk.js">
</script>

引入了jsapi之后,我们就需要自己编写js文件对它的相应的接口进行调用,从而获得code,如下的代码是本人根据实际应用编写的js 代码,具有详细的注解:

/***************************开始****************************/
/*** _config 这个参数是在前台的H5文件中我定义的,它的值是通过调用步骤6中封装好的参数来获得的*//*我们需要明白的一点是,所有的这些文件都是放在企业应用的服务器后台,和钉钉网站没有半毛钱的关系并且钉钉的jsapi中唯一的作用就是提供了对config的验证和获得code值对于其他值得获取,如access_token,ticket,sign,username,userid都是自己在后台写java代码通过get或者post方式向钉钉开发平台请求得来的,并不是从jsapi中的接口得来的*/
dd.config({                                                //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',     //confirm,alert,prompt都是弹出小窗口的接口   'device.notification.alert','device.notification.prompt','biz.util.openLink' ]});/*
*在dd.config()验证通过的情况下,就会执行ready()函数,
*dd.ready参数为回调函数,在环境准备就绪时触发,jsapi的调用需要保证在
*该回调函数触发后调用,否则无效,所以你会发现所有对jsapi接口的调用都会在
*ready的回调函数里面
*/
dd.ready(function() {/**获取容器信息,返回值为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));}});/**获得免登授权码,需要的参数为corpid,也就是企业的ID*成功调用时返回onSuccess,返回值在function的参数info中,具体操作可以在function中实现*返回失败时调用onFail*/dd.runtime.permission.requestAuthCode({corpId : _config.corpId,onSuccess : function(info) {                                                   //成功获得code值,code值在info中
//          alert('authcode: ' + info.code);/**$.ajax的是用来使得当前js页面和后台服务器交互的方法*参数url:是需要交互的后台服务器处理代码,这里的userinfo对应WEB-INF -> classes文件中的UserInfoServlet处理程序*参数type:指定和后台交互的方法,因为后台servlet代码中处理Get和post的doGet和doPost*原本需要传输的参数可以用data来存储的,格式为data:{"code":info.code,"corpid":_config.corpid}*其中success方法和error方法是回调函数,分别表示成功交互后和交互失败情况下处理的方法*/$.ajax({url : 'userinfo?code=' + info.code + '&corpid='                //userinfo为本企业应用服务器后台处理程序+ _config.corpId,type : 'GET',/**ajax中的success为请求得到相应后的回调函数,function(response,status,xhr)*response为响应的数据,status为请求状态,xhr包含XMLHttpRequest对象*/success : function(data, status, xhr) {                                var info = JSON.parse(data);alert("用户"+info.name+"登录成功");},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(function(err) {alert('dd error: ' + JSON.stringify(err));
});/*
dd中借口的约定:
所有接口都为异步
接受一个object类型的参数,function在js中也是一个object
成功回调 onSuccess(某些异步接口的成功回调,将在事件触发时被调用,具体详情请查看相关onSuccess回调时机,未做描述的即为同步接口)
失败回调 onFail模板如下:
dd.命名空间.功能.方法({参数1: '',参数2: '',onSuccess: function(result) {//成功回调//{//所有返回信息都输出在这里//}},onFail: function(){//失败回调}
})
*//**************************************结束********************************/

我们编写的这个js文件也需要引入到企业前台的h5中,具体的引入方法和引入jsapi方式是一样的。

<script type="text/javascript" src="javascripts/opt.js">
</script>

只不过上面是从链接中引入,这里是从本地写好的资源中引入。

8.在后台编写userinfoServlet来获取前台传入的code值,并且通过code值获取用户信息,然后在后台数据库中比对用户信息实现登录。

通过code值获取用户信息的方法在钉钉官方文档中有详细的解答,通过get请求方式向
https://oapi.dingtalk.com/user/getuserinfo?access_token=ACCESS_TOKEN&code=CODE
发送请求,需要携带两个参数,access_token和code值,返回值就是用户的信息。

具体代码由于和后台交互不便透露,但原理已经很清晰,对于获取用户信息可以参考前面的获取access_token的方法,
对于后台用户数据的比对只需要一些简单的数据库知识。

9.至此所有的步骤都已完成,便可以实现免登陆。

(三)运作流程阐述:

首先,公司成员在钉钉客户端点击了公司创建的微应用,然后微应用会根据定向URL地址跳转到公司应用的网站首页,在网站首页的HTML源码(也可能还包含其他前端语言)中定义了_config变量,这个变量通过后台代码的getConfig(request)函数对值进行了初始化,网站首页的源码由上往下的执行,就会执行到我们自己写的那个免登的js代码,在这个代码中完成了和_config值的验证,然后就会从钉钉开放平台获取code值,获得code值后,便会向服务器后台的相应的servlet文件发送code,该servlet文件收到code后就会换取用户信息,并和后台数据库中的用户信息比对,如果存在,就向前台返回登录成功,并跳转到登录成功后的页面。

(四):结束

最好再次强调一下,这些操作都是在你的企业应用的前端和后台进行操作的,和钉钉没有半毛钱关系,我们只是调用了钉钉的一些接口,这些文件也都是放在企业应用对应的网站的源码中,并不是放到钉钉那里。

钉钉实现企业级微应用免登陆详解相关推荐

  1. 瑞芯微RK PX30中文详解(带开源资料)简介

    瑞芯微RK PX30中文详解(带开源资料)简介 非常感谢您选择九鼎创展PX30开发平台,本文档讲述PX30开发平台的硬件资源,电路原理以及支持的接口等. PX30开发板是基于瑞芯微PX30的一款高端开 ...

  2. Apollo微服务配置中心详解

    Apollo微服务配置中心详解 前言 一.Apollo架构 (一)简介 (二)角色介绍 (三)服务端实现 (四)客服端实现 二.Apollo部署 (一)准备数据库 (二)配置服务 1. 手动部署 (1 ...

  3. 黎明杀机手游注册登陆详解

    黎明杀机手游注册登陆详解 1.OurPlay2.酷酷跑3.Qoo APP 1.打开Qoo APP 下载黎明杀机 2.打开酷酷跑,申请账号 2.1先点击一件安装再进行点击申请 3.打开OurPlay 将 ...

  4. 10投屏后没有声音_钉钉怎么投屏,秒懂投屏详解

    钉钉投屏怎么投屏??? 知乎 @框框-秒懂投屏 给各位介绍. 传统办法,采用镜像同屏模式 苹果手机: 1·首先需要把电视打开,并且打开电视的投屏功能,如果没有投屏功能可以安装一个投屏软件. 2·打开苹 ...

  5. java实现的微服务架构_详解Java 微服务架构

    一.传统的整体式架构 传统的整体式架构都是模块化的设计逻辑,如展示(Views).应用程序逻辑(Controller).业务逻辑(Service)和数据访问对象(Dao),程序在编写完成后被打包部署为 ...

  6. 第三方登陆——QQ登陆详解

    申请地址 QQ互联:https://connect.qq.com/index.html 腾讯开放平台:https://open.tencent.com/ 注册账号 登陆 进入QQ互联,点击登陆 资料填 ...

  7. JAVA微信登陆详解

    步骤:  1. 使用第三方工具,生成内网映射  2. 微信公众平台使用测试账号  3. 测试账号中需要配置自己的域名,并关注该临时测试使用的公众号  4. 下载一个浏览器的二维码插件  4. 编码测试 ...

  8. 你从未见过的,最全微服务架构实战详解,干货满满

    前言 随着各行各业的快速发展,业务规模的不断扩大,不可避免地造成原有架构不能够适应快速的增长和变化.这时,微服务就进入大家的视野. 但微服务并不是一个全新的架构,更不是一个包治百病的架构.它同样也会给 ...

  9. 首席架构师推荐:史上最全微服务架构简史详解!

    本文将介绍微服务架构和相关的组件,介绍他们是什么以及为什么要使用微服务架构和这些组件.本文侧重于简明地表达微服务架构的全局图景,因此不会涉及具体如何使用组件等细节. 要理解微服务,首先要先理解不是微服 ...

最新文章

  1. BZOJ2831(小强的金字塔系列问题--区域整点数求法)
  2. python按概率输出分类结果_sklearn例程:多分类输出概率
  3. 手把手教你刷新BACKUP Bios
  4. java 图片压缩100k_如何将图片压缩到100K以内,教你几种免费方法
  5. python网页动图_python,tensorflow线性回归Django网页显示Gif动态图
  6. 【Java并发性和多线程】Java中的锁
  7. vhg电路是什么意思_电路板打样是什么意思?
  8. 《Servlet和JSP学习指南》一第1章 Servlet 1.1 Servlet API概述
  9. ttf文件 python 打开_[译]JS解析TTF字体文件
  10. 没有ajax以前的隐藏 iframe 技术
  11. cdecl.org 翻译C声明的网站
  12. 微计算机与单片机原理及应用答案,电子科技大学《单片机原理及应用》20春期末考试【标准答案】...
  13. android 铃音制作工具,手机铃声制作大师
  14. 【简答题】JavaWeb必问10道简答题
  15. ubuntu系统备份、系统镜像制作教程
  16. Ubuntu的ldconfig详解(解决*.so不是符号连接)
  17. 从“快稳省安全”看Chromium——Chromium学习系列
  18. 素民党的故事 (01) 什么是素民党
  19. UserWarning: Ignoring URL ‘xxx‘ since it exceeds Excel‘s limit of 65,530 URLS per worksheet.
  20. 成都振芯GM8913和GM8914:流媒体行车记录仪方案!~

热门文章

  1. 【万里征程——Windows App开发】应用栏
  2. .net微信公众号开发——基础接口
  3. JS事件循环机制:同步与异步任务 之 宏任务 微任务
  4. MySQL数据库同步神器 - Gravity - 比Datax好用
  5. Oracle死锁解决常用方法
  6. 《软件安装》VMware 安装 centos8
  7. mysql必知必会--用通配符进行过滤
  8. Java-环境搭建(Mac版)
  9. java 课后习题 冒泡排序的运用
  10. 三范式理解 数据库原理