需求描述

最近客户有一个需求,希望在企业微信的工作台上放一些业务系统的链接。这些业务系统本身已经完成了和CAS单点的对接,但是放在企业微信工作台上就会出现问题。点击系统链接的时候,会先被CAS拦截下来,跳转到单点登录页。用户在输入完账号密码后,才能进入对应系统。这样就很不方便,希望可以实现点击对应系统的链接之后,可以自动实现认证过程,直接进入系统。

方案说明

我们可以利用企业微信本身提供的Oauth2认证来实现这个需求。通过企业微信的Oauth2认证,我们可以安全获取当前微信的登录用户。我们再根据这个用户去CAS创建票据,即可成功实现企业微信和CAS之间的组合认证。

企业微信提供了一个链接:

https://open.weixin.qq.com/connect/oauth2/authorize?appid=CORPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&agentid=AGENTID&state=STATE#wechat_redirect

这个特殊的链接,只能在企业微信客户端打开(这里提供的是公共版企业微信的地址,如果企业微信是本地化部署的,则对应的域名要换成企业微信本地服务器的地址),在企业微信打开后,它会自动跳转到redirect_uri所对应的地址,并携带两个参数:codestate。这两个参数,code是我们所需要的,我们需要用这个 code去调用企业微信另一个接口,用于获取用户的id。而 state 则是可以用来携带一些信息,会在重定向时原样带回。在这里我们不需要使用,只需要 code即可。

那么我们的步骤就很明确了:

  1. 在企业微信工作台上,将对应应用的链接配置成上面那样,redirect_uri配置成CAS的地址,并且携带对应的service参数。假设CAS服务器的登录地址是 https://test.cas.com/cas/login ,对应的业务系统地址是 https://www.baidu.com 。那么对于正常的CAS登录,登录页的url应该是 https://test.cas.com/cas/login?service=https://www.baidu.com。而这里,我们就要将这个url配置成企业微信认证的 redirect_uri。但这里需要注意一点,企业微信要求这里的redirect_uri必须是经过urlEncode过的,所以我们要先对这个登录页url进行一次Encode。但这里还有一些特殊,因为我们的url中包含客户端业务系统的url,所以我们需要进行二次Encode,即先对业务系统的url进行一次Encode,变成下面这样:https://test.cas.com/cas/login?service=https%3A%2F%2Fwww.baidu.com ,然后再对整个url进行一次Encode,变成:https%3A%2F%2Ftest.cas.com%2Fcas%2Flogin%3Fservice%3Dhttps%253A%252F%252Fwww.baidu.com。这样才能放入企业微信认证链接的 redirect_uri中。
  2. 在企业微信后台管理中,将应用的可信域名配置成单点服务器的域名(需要包括端口号)。否则在后面验证code的时候,会重定向域名不是可信域名。
  3. 用户点击应用,企业微信跳转到单点登录页,并携带code。这个时候我们需要让CAS自动识别这个code,并提交到后端进入认证流程。这里的详细操作会放在下面展开来介绍。
  4. CAS后端接收到企业微信提供的code,调用企业微信的 https://open.weixin.qq.com/cgi-bin/user/getuserinfo?access_token=ACCESS_TOKEN&code=CODE接口,将code放入接口中,接口会返回用户对应的id。
  5. 如果企业微信的用户id就是CAS使用的用户名,那可以直接使用用户id创建票据。如果不是,则需要通过企业微信的用户id匹配到对应的用户名,然后再创建票据。如果没有建立企业微信用户id与用户名之间的映射关系,我们也可以通过调用企业微信提供的通讯录接口,通过用户id获取用户详细信息如手机号或者邮箱等,再通过这些信息进行匹配。
  6. 创建完票据,就可以走正常的CAS登录流程了,结束。

可以看到,整个方案最关键的点,就是让CAS拿到企业微信提供的code,并通过这个code来获取当前登录人。

CAS的改造

在上面的第3点,我们提到要让CAS自动识别到url中的code,然后提交到后端进入认证流程。这一步要如何实现呢?我这里提供一种思路,就是通过js来判断url中是否含有code参数,如果有,则代表这个请求是通过企业微信重定向过来的,那我们就需要特殊处理。

首先我们需要一个工具方法,用来获取url中的参数

/*** 从location中得到参数* @param location* @param name* @returns {null}*/var getQueryString = function (location, name) {var reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)');var r = location.search.substr(1).match(reg);if (r !== null) return unescape(r[2]);return null};let code = getQueryString(window.location, 'code');

这样,我们就可以从url中拿到code的值了。接下来我们需要考虑如何将这个code传到后端。这里我提供两种思路:

  1. 通过表单进行传递,将code作为账号,密码采用一个特殊值,后端识别到这个特殊值,就代表是企业微信传输过来的(不推荐,因为不能保证没有人会用这个特殊值作为密码,即使概率很小)
  2. 放入cookie中,后端判断cookie中有code,则走企业微信认证逻辑,否则走正常逻辑。

这里提供一下放入cookie的代码:

var setEnterpriseWechatCodeCookie = function (code) {document.cookie = "authCode=" + code;
};setEnterpriseWechatCodeCookie(code);

下面,怎么将这个code提交到后端呢?这里用比较取巧的方法,走正常的表单提交,只不过账号密码里面填上无意义的值。

document.getElementById("username").setAttribute("value", "_");
document.getElementById("password").setAttribute("value", "_");
document.getElementById("login-btn").click();

可以看到,这里模拟了登录按钮的点击操作,这样就可以将code提交到后端了。

但仅仅是这样,还是有一些瑕疵。在实际的效果中,这样的确可以实现自动识别code,并传给后端。但是登录界面会一闪而过,比较影响体验。那么我们为了让用户无感,要想办法让用户看不到这个登录界面。

这里我采用的方法是:

  1. 先让整个登录界面都默认不显示,即display:none,然后在整个界面上增加一个纯白的遮罩,之后再让登录界面显示。
  2. 在判断不需要走企业微信登录逻辑,即走正常登录逻辑时,删除遮罩

这样做的好处是,纯白的遮罩看起来像是网页加载过程中正常的空白,所以对用户来说是相对无感的。

下面给出部分代码:

.blank-mask {left: 0;top: 0;bottom:0;right:0;position: fixed;z-index: 99999;background: rgb(255,255,255);}.html-blank-mask{height: 100%;width: 100%;overflow: hidden;}var createMask = function () {if( document.getElementById("blank-mask")){return true;}let mask = document.createElement("div");mask.id = "blank-mask";mask.className = "blank-mask";// 把 mask 添加到body 里。document.body.appendChild(mask);document.documentElement.classList.add("html-blank-mask");
}var deleteMask = function () {let mask = document.getElementById("blank-mask");if(mask){mask.parentNode.removeChild(mask);document.documentElement.classList.remove("html-blank-mask");}}$(function () {createMask();$("#all").show();let code = getQueryString(window.location, 'code');if (code) {setEnterpriseWechatCodeCookie(code);document.getElementById("username").setAttribute("value", "_");document.getElementById("password").setAttribute("value", "_");document.getElementById("login-btn").click();} else {deleteMask();}
});

到这里为止,前端代码算是结束了,我们可以实现当企业微信重定向到CAS服务端的时候,可以自动将code传到后端了。那么后端如何使用这个code,下面进行一些简单的介绍。

在表单提交后,会进入到正常的表单登录认证的AuthenticationHandler中,我们需要在执行正常的认证逻辑之前,插入我们的企业微信认证逻辑。由于代码逻辑不算复杂,这里就不多做介绍,只贴上一份代码作为参考。

/*** 获取企业微信认证code的工具类* @return*/private String getAuthCode() {HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();Cookie[] cookies = request.getCookies();if (null != cookies) {for (Cookie c : cookies) {if ("authCode".equalsIgnoreCase(c.getName())) {return c.getValue();}}}return null;}/*** 获取企业微信认证token的工具类,token缓存多次使用* @param isRefresh 是否刷新* @return*/private String getAccessToken(boolean isRefresh) {Long newTime = System.currentTimeMillis();if (expiresIn > newTime && !isRefresh) {return accessToken;} else {String tokenUrl = PropertyUtil.getProperty("wx.tokenUrl",MyAuthenticationHandler.class);JSONObject tokenJson = new JSONObject(restTemplate.getForObject(tokenUrl, String.class,PropertyUtil.getProperty("wx.corpId",MyAuthenticationHandler.class),PropertyUtil.getProperty("wx.corpSecret",MyAuthenticationHandler.class)));if (0 == tokenJson.getInt("errcode")) {accessToken = tokenJson.getString("access_token");expiresIn = newTime + tokenJson.getLong("expires_in") * 1000;return accessToken;} else {return getAccessToken(true);}}}/*** 通过code调用企业微信接口获取userId* @param code* @param accessToken* @return*/private String getWechatUserId(String code, String accessToken) {String infoUrl = PropertyUtil.getProperty("wx.userInfoUrl",MyAuthenticationHandler.class);JSONObject infoJson = new JSONObject(restTemplate.getForObject(infoUrl, String.class, accessToken, code));if (0 == infoJson.getInt("errcode")) {return infoJson.getString("UserId");} else if (40014 == infoJson.getInt("errcode")) {//token过期,重新获取token再次调用接口return getWechatUserId(code, getAccessToken(true));}return null;}//企业微信认证String code = getAuthCode();if (StringUtils.isNotBlank(code)) {//获取tokenString token = getAccessToken(false);//获取userIdString userId = getWechatUserId(code, token);//创建票据,通过认证if (StringUtils.isNotBlank(userId)) {credential.setUsername(userId);return createHandlerResult(credential,this.principalFactory.createPrincipal(credential.getUsername()), null);}}

企业微信工作台集成CAS实现单点登录相关推荐

  1. 多服务集成CAS实现单点登录

    简要概述实现步骤(思路): 1.创建web应用 2.集成CAS(使用CAS服务端内配置账户密码进行登录验证) 2.1导入jar包 2.2配置web.xml文件(拦截跳转验证以及验证结束返回) 3.配置 ...

  2. 微服务集成cas_Spring Cloud Security集成CAS (单点登录)对微服务认证

    一.前言 由于leader要求在搭好的spring cloud 框架中加入对微服务的认证包括单点登录认证,来确保系统的安全,所以研究了Spring Cloud Security这个组件.在前面搭好的d ...

  3. 微服务集成cas_Spring Cloud(四) Spring Cloud Security集成CAS (单点登录)对微服务认证...

    一.前言 由于leader要求在搭好的spring cloud 框架中加入对微服务的认证包括单点登录认证,来确保系统的安全,所以研究了Spring Cloud Security这个组件.在前面搭好的d ...

  4. cas jwt 单点登录

    单点登录是我比较喜欢的一个技术解决方案,一方面他能够提高产品使用的便利性,另一方面他分离了各个应用都需要的登录服务,对性能以及工作量都有好处.自从上次研究过JWT如何应用于会话管理,加之以前的项目中也 ...

  5. CAS SSO 单点登录 【完整版】

    什么是单点登录?什么是SSO? SSO就是单点登录!!! SSO即Single Sign On. 可是为什么我们要单点登录呢?为什么不能把所有的系统做成一个war包里呢? 道理很简单啊,如果这个银行这 ...

  6. Java架构-CAS SSO单点登录框架介绍

    1.了解单点登录 SSO 主要特点是: SSO 应用之间使用 Web 协议(如 HTTPS) ,并且只有一个登录入口. SSO 的体系中有下面三种角色: User(多个) Web 应用(多个) SSO ...

  7. CAS5.3服务器搭建及SpringBoot整合CAS实现单点登录

    CAS5.3服务器搭建及SpringBoot整合CAS实现单点登录 1.1 什么是单点登录 1.2 什么是CAS 1.3 CAS服务端部署 1.template下载 1.4 客户端搭建 1.1 什么是 ...

  8. odoo与企业微信深度集成

    odoo与企业微信深度集成 基础数据:部门.员工 考勤:考勤管理.排版管理.出勤管理 休假:休假额度.休假申请 审批:审批模板.审批引擎 * 微信扫码登录 1.基础数据:部门.员工 部门 员工 2.考 ...

  9. [精华][推荐]CAS SSO单点登录服务端客户端学习

    1.了解单点登录 SSO 主要特点是: SSO 应用之间使用 Web 协议(如 HTTPS) ,并且只有一个登录入口. SSO 的体系中有下面三种角色: 1) User(多个) 2) Web 应用(多 ...

最新文章

  1. 编写程序,计算分段函数的值
  2. 干货 | 基于 BDD 理念的 UI 自动化测试在携程度假的应用
  3. 秀操作 | 函数宏的三种封装方式
  4. 华为P50系列再曝“坏消息”:疑似再度延期至7月份
  5. 如何打印网页版的发票_纸质发票将消失,电子发票如何报销、打印、收集?这一篇就够了...
  6. electron 改变窗体 大小_「Science子刊」约翰·霍普金斯大学创造灰尘大小设备,可协助药物在胃肠道停留24小时之久...
  7. 【单目标优化求解】基于matlab改进的遗传算法求解单目标优化问题【含Matlab源码 1834期】
  8. Qt制作贪吃蛇小游戏
  9. 【C】VC6调试器的使用
  10. 【iqiqiya专版】超级网易云音乐V1.0----网易云解析下载工具
  11. 非常不错的Solaris文章,适合入门
  12. Android APP OpenGL ES应用(01)GLSurfaceView 2D/3D绘图基础
  13. Apache Pulsar 生态项目 AoP 新增两位中国移动 Maintainer!
  14. 游戏挂机时计算机设置在哪里,电脑挂机锁如何设置 电脑挂机锁设置方法【图文】...
  15. 少儿编程微课程7:星际飞行单机版
  16. 『NLP经典项目集』10:使用预训练模型优化快递单信息抽取
  17. 卷帘曝光和全局曝光的差别
  18. 上海高校计算机二级考纲,上海市普通高校计算机等级考试考纲
  19. MFC 报错Buffer too small
  20. 计算机毕业设计ssm基于ssm的酒店管理系统设计与实现

热门文章

  1. 【苹果家庭推送】iMessage Number是一种及时静态(Differential Privacy)
  2. 【exp】virtualbox 安装增强功能失败问题解决(vbox虚拟机, Ubuntu)
  3. ADSP21489 Target halted due to software breakpoint but no breakpoint found at address: 0x208c0b6 ()
  4. 居民身份证阅读器产品开发学习心得(再谈标准-软件-协议)
  5. Android EditText限制输入表情和特殊符号的处理
  6. ruoyi框架文件上传之后端代码测试及打印日志
  7. 【Python黑科技】几行代码绘制gif动图(保姆级图文+实现代码)
  8. 中国虚拟招聘工具行业深度调研与市场规模份额预测报告2022年
  9. 《Test-Driven Development for Embedded C》读书笔记(三)
  10. 2019年AI领域回顾:稳定发展还是幻想破灭?