企业微信工作台集成CAS实现单点登录
需求描述
最近客户有一个需求,希望在企业微信的工作台上放一些业务系统的链接。这些业务系统本身已经完成了和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所对应的地址,并携带两个参数:code
和 state
。这两个参数,code是我们所需要的,我们需要用这个 code
去调用企业微信另一个接口,用于获取用户的id。而 state
则是可以用来携带一些信息,会在重定向时原样带回。在这里我们不需要使用,只需要 code
即可。
那么我们的步骤就很明确了:
- 在企业微信工作台上,将对应应用的链接配置成上面那样,
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
中。 - 在企业微信后台管理中,将应用的可信域名配置成单点服务器的域名(需要包括端口号)。否则在后面验证code的时候,会重定向域名不是可信域名。
- 用户点击应用,企业微信跳转到单点登录页,并携带code。这个时候我们需要让CAS自动识别这个code,并提交到后端进入认证流程。这里的详细操作会放在下面展开来介绍。
- CAS后端接收到企业微信提供的code,调用企业微信的
https://open.weixin.qq.com/cgi-bin/user/getuserinfo?access_token=ACCESS_TOKEN&code=CODE
接口,将code放入接口中,接口会返回用户对应的id。 - 如果企业微信的用户id就是CAS使用的用户名,那可以直接使用用户id创建票据。如果不是,则需要通过企业微信的用户id匹配到对应的用户名,然后再创建票据。如果没有建立企业微信用户id与用户名之间的映射关系,我们也可以通过调用企业微信提供的通讯录接口,通过用户id获取用户详细信息如手机号或者邮箱等,再通过这些信息进行匹配。
- 创建完票据,就可以走正常的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传到后端。这里我提供两种思路:
- 通过表单进行传递,将code作为账号,密码采用一个特殊值,后端识别到这个特殊值,就代表是企业微信传输过来的(不推荐,因为不能保证没有人会用这个特殊值作为密码,即使概率很小)
- 放入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,并传给后端。但是登录界面会一闪而过,比较影响体验。那么我们为了让用户无感,要想办法让用户看不到这个登录界面。
这里我采用的方法是:
- 先让整个登录界面都默认不显示,即display:none,然后在整个界面上增加一个纯白的遮罩,之后再让登录界面显示。
- 在判断不需要走企业微信登录逻辑,即走正常登录逻辑时,删除遮罩
这样做的好处是,纯白的遮罩看起来像是网页加载过程中正常的空白,所以对用户来说是相对无感的。
下面给出部分代码:
.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实现单点登录相关推荐
- 多服务集成CAS实现单点登录
简要概述实现步骤(思路): 1.创建web应用 2.集成CAS(使用CAS服务端内配置账户密码进行登录验证) 2.1导入jar包 2.2配置web.xml文件(拦截跳转验证以及验证结束返回) 3.配置 ...
- 微服务集成cas_Spring Cloud Security集成CAS (单点登录)对微服务认证
一.前言 由于leader要求在搭好的spring cloud 框架中加入对微服务的认证包括单点登录认证,来确保系统的安全,所以研究了Spring Cloud Security这个组件.在前面搭好的d ...
- 微服务集成cas_Spring Cloud(四) Spring Cloud Security集成CAS (单点登录)对微服务认证...
一.前言 由于leader要求在搭好的spring cloud 框架中加入对微服务的认证包括单点登录认证,来确保系统的安全,所以研究了Spring Cloud Security这个组件.在前面搭好的d ...
- cas jwt 单点登录
单点登录是我比较喜欢的一个技术解决方案,一方面他能够提高产品使用的便利性,另一方面他分离了各个应用都需要的登录服务,对性能以及工作量都有好处.自从上次研究过JWT如何应用于会话管理,加之以前的项目中也 ...
- CAS SSO 单点登录 【完整版】
什么是单点登录?什么是SSO? SSO就是单点登录!!! SSO即Single Sign On. 可是为什么我们要单点登录呢?为什么不能把所有的系统做成一个war包里呢? 道理很简单啊,如果这个银行这 ...
- Java架构-CAS SSO单点登录框架介绍
1.了解单点登录 SSO 主要特点是: SSO 应用之间使用 Web 协议(如 HTTPS) ,并且只有一个登录入口. SSO 的体系中有下面三种角色: User(多个) Web 应用(多个) SSO ...
- CAS5.3服务器搭建及SpringBoot整合CAS实现单点登录
CAS5.3服务器搭建及SpringBoot整合CAS实现单点登录 1.1 什么是单点登录 1.2 什么是CAS 1.3 CAS服务端部署 1.template下载 1.4 客户端搭建 1.1 什么是 ...
- odoo与企业微信深度集成
odoo与企业微信深度集成 基础数据:部门.员工 考勤:考勤管理.排版管理.出勤管理 休假:休假额度.休假申请 审批:审批模板.审批引擎 * 微信扫码登录 1.基础数据:部门.员工 部门 员工 2.考 ...
- [精华][推荐]CAS SSO单点登录服务端客户端学习
1.了解单点登录 SSO 主要特点是: SSO 应用之间使用 Web 协议(如 HTTPS) ,并且只有一个登录入口. SSO 的体系中有下面三种角色: 1) User(多个) 2) Web 应用(多 ...
最新文章
- 编写程序,计算分段函数的值
- 干货 | 基于 BDD 理念的 UI 自动化测试在携程度假的应用
- 秀操作 | 函数宏的三种封装方式
- 华为P50系列再曝“坏消息”:疑似再度延期至7月份
- 如何打印网页版的发票_纸质发票将消失,电子发票如何报销、打印、收集?这一篇就够了...
- electron 改变窗体 大小_「Science子刊」约翰·霍普金斯大学创造灰尘大小设备,可协助药物在胃肠道停留24小时之久...
- 【单目标优化求解】基于matlab改进的遗传算法求解单目标优化问题【含Matlab源码 1834期】
- Qt制作贪吃蛇小游戏
- 【C】VC6调试器的使用
- 【iqiqiya专版】超级网易云音乐V1.0----网易云解析下载工具
- 非常不错的Solaris文章,适合入门
- Android APP OpenGL ES应用(01)GLSurfaceView 2D/3D绘图基础
- Apache Pulsar 生态项目 AoP 新增两位中国移动 Maintainer!
- 游戏挂机时计算机设置在哪里,电脑挂机锁如何设置 电脑挂机锁设置方法【图文】...
- 少儿编程微课程7:星际飞行单机版
- 『NLP经典项目集』10:使用预训练模型优化快递单信息抽取
- 卷帘曝光和全局曝光的差别
- 上海高校计算机二级考纲,上海市普通高校计算机等级考试考纲
- MFC 报错Buffer too small
- 计算机毕业设计ssm基于ssm的酒店管理系统设计与实现
热门文章
- 【苹果家庭推送】iMessage Number是一种及时静态(Differential Privacy)
- 【exp】virtualbox 安装增强功能失败问题解决(vbox虚拟机, Ubuntu)
- ADSP21489 Target halted due to software breakpoint but no breakpoint found at address: 0x208c0b6 ()
- 居民身份证阅读器产品开发学习心得(再谈标准-软件-协议)
- Android EditText限制输入表情和特殊符号的处理
- ruoyi框架文件上传之后端代码测试及打印日志
- 【Python黑科技】几行代码绘制gif动图(保姆级图文+实现代码)
- 中国虚拟招聘工具行业深度调研与市场规模份额预测报告2022年
- 《Test-Driven Development for Embedded C》读书笔记(三)
- 2019年AI领域回顾:稳定发展还是幻想破灭?