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

作者:郑德鼎 约 10 分钟阅读 更新日期:2025-03-02 1 年前更新 标签:ABAP, Data Engineering, SD, 开发, 数据工程, 销售分销
企业微信工作台集成 CAS 实现单点登录 - 封面图
企业微信工作台集成 CAS 实现单点登录 - 封面图
企业微信工作台集成 CAS 实现单点登录 - 封面图
企业微信工作台集成 CAS 实现单点登录 - 封面图
企业微信工作台集成 CAS 实现单点登录 - 封面图
企业微信工作台集成 CAS 实现单点登录 - 封面图
企业微信工作台集成 CAS 实现单点登录 - 封面图
企业微信工作台集成 CAS 实现单点登录 - 封面图
企业微信工作台集成 CAS 实现单点登录 - 封面图
企业微信工作台集成 CAS 实现单点登录 - 封面图

** 企业微信工作台集成CAS实现单点登录 **

企业微信工作台集成 CAS 实现单点登录 - 封面图
企业微信工作台集成 CAS 实现单点登录 - 封面图

**需求描述**

企业微信工作台集成 CAS 实现单点登录 - 封面图
企业微信工作台集成 CAS 实现单点登录 - 封面图

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

企业微信工作台集成 CAS 实现单点登录 - 封面图
企业微信工作台集成 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即可。

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

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 实现单点登录 - 封面图
企业微信工作台集成 CAS 实现单点登录 - 封面图
企业微信工作台集成 CAS 实现单点登录 - 封面图
企业微信工作台集成 CAS 实现单点登录 - 封面图
企业微信工作台集成 CAS 实现单点登录 - 封面图
企业微信工作台集成 CAS 实现单点登录 - 封面图

**CAS的改造**

企业微信工作台集成 CAS 实现单点登录 - 封面图
企业微信工作台集成 CAS 实现单点登录 - 封面图

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

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

企业微信工作台集成 CAS 实现单点登录 - 封面图
企业微信工作台集成 CAS 实现单点登录 - 封面图

/**

• 从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');

企业微信工作台集成 CAS 实现单点登录 - 封面图
企业微信工作台集成 CAS 实现单点登录 - 封面图

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

1. 通过表单进行传递,将code作为账号,密码采用一个特殊值,后端识别到这个特殊值,就代表是企业微信传输过来的(不推荐,因为不能保证没有人会用这个特殊值作为密码,即使概率很小)

2. 放入cookie中,后端判断cookie中有code,则走企业微信认证逻辑,否则走正常逻辑

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

企业微信工作台集成 CAS 实现单点登录 - 封面图
企业微信工作台集成 CAS 实现单点登录 - 封面图

var setEnterpriseWechatCodeCookie = function (code) {

document.cookie = "authCode=" + code;

};

setEnterpriseWechatCodeCookie(code);

企业微信工作台集成 CAS 实现单点登录 - 封面图
企业微信工作台集成 CAS 实现单点登录 - 封面图

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

企业微信工作台集成 CAS 实现单点登录 - 封面图
企业微信工作台集成 CAS 实现单点登录 - 封面图

document.getElementById("username").setAttribute("value", "_");

document.getElementById("password").setAttribute("value", "_");

document.getElementById("login-btn").click();

企业微信工作台集成 CAS 实现单点登录 - 封面图
企业微信工作台集成 CAS 实现单点登录 - 封面图

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

但仅仅是这样,还是有一些瑕疵。在实际的效果中,这样的确可以实现自动识别code,并传给后端。 **但是登录界面会一闪而过,比较影响体验。**

那么我们为了让用户无感,要想办法让用户看不到这个登录界面。

这里我采用的方法是:

1. 先让整个登录界面都默认不显示,即display:none,然后在整个界面上增加一个纯白的遮罩,之后再让登录界面显示。

2. 在判断不需要走企业微信登录逻辑,即走正常登录逻辑时,删除遮罩

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

企业微信工作台集成 CAS 实现单点登录 - 封面图
企业微信工作台集成 CAS 实现单点登录 - 封面图

.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 实现单点登录 - 封面图
企业微信工作台集成 CAS 实现单点登录 - 封面图

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

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

企业微信工作台集成 CAS 实现单点登录 - 封面图
企业微信工作台集成 CAS 实现单点登录 - 封面图

/**

• 获取企业微信认证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, St

...

郑德鼎

关于作者:郑德鼎

企业信息化与 SAP 技术顾问,长期专注 SAP ABAP、FI/CO、MM、SD 等模块的技术分享与实战经验总结。查看更多介绍

来源说明:本文内容由「企业微信工作台集成CAS实现单点登录.md」整理生成,仅用于内部技术分享与学习交流。