模拟CAS单点登录(跨域提交、js正则匹配)

前言

前段时间,在项目上有个需求,用户希望保留原有应用的登录页面,在不动cas源码的情况下实现跨域单点登录系统,即不通过cas登录页面输入用户名和密码,通过之前系统登录页面认证然后达到单点登录效果。综合网上的帖子思路来看,要保留原有的登陆页面逻辑,实现的思路是模拟请求登陆页,先获取cas的lt及execution信息,再将这些信息放在原有登录页面的表单上,表单提交指向到cas登录验证,从而实现经过cas单点验证通过,然后成功重定向到要单点的系统。

如果cas和要单点的系统部署在一个tomcat的情况那么就不存在跨域的情况!但是cas和要单点的系统没有部署在一个tomcat的话那么就存在跨域了,存在跨域的话,可以用jsonp来请求实现!如果用户的需求是不改造cas的情况下实现上述的需求,那么可以通过第一次模拟请求cas,此时cas会返回一个html页面(cas自身的登录页面),通过js正则匹配此html页面中的需要的信息,也可以获取到lt及execution。

实现

下面是cas的登录页面的html,可以看出cas登录验证需要的3个关键参数lt、execution、_eventId,知道这个关键之后,那么可以通过Ajax+JSONP实现模拟登录。

<form id="login_form_id" action="/cas/login" method="post">
	<input type="hidden" name="lt" value="" />
	<input type="hidden" name="execution" value="bda3db89-046d-4dc0-bb24-a6f764850118_ZXlKaGJHY2lPaUpJVXpVeE1pSjkuZGxGd2VrUXZkbWR3T0U1T1NFc3dUbEpDVTI5WmRrWnJSVmMyT1hOeU5YYzNiVlpDTlRWMmNHTmlUSEExTVV0VVFqaG9WM1lyZFdkNmIyMWhVSEp3Y25ka01DdGhhV1pSYkdKNFNrdG1SREpLY3pKWE4yTnZhRU5SYVRsSFpVZDBSRkJMV21seU5uZG5VbXRDWmpneGJFWkpiVXBWV21aSVVVdFFhMFJuT1daME0xTkNiVGR0WTNwVVVFdFZjMGQyWVcxYVdpOWxaM3AwWm1oV1IzSlhWM3BuVTBwVlZGRjBjVWhJZUM4eWJtNVlkbWhsUVRKMWQxRm9OVmRWSzJkWlVVNTBWMkUwWWpCVlZERjBabWR6YzA5RU4zSlpaRVZoVFVFM1VHWldVbTlRZVRGWFdTOWFZbXh3VFdKWFZEWXZjVll2VWxFdmQwOXlkR1p2VFhOTk4wOWhkR052WXpSeFpIaFRaRUpHWTFSaWNEaExjQ3RhZUZWbGMwNTBUMnRtYjFCRFlUWXZaRFJFVkVKcE5rY3JhamhNVDFKcmRsQnRiVEpXVDBOUFRtRmFkR2RPVFhkcFJVdEphV3Q1YjNSWFFtRnhRemhYWmtaQ1pVcFVOWEp6WjFkNlVFVjBOblIyYlVReE1XRnhUa1ZaVERkSVZtSkVXR0ZpZVZVclFsaEZiSFp4UldacFNYZDZUMFJUTDJRMVJ6azVkSGxVTHpVNFNqWmpRWEo1WkdoVVQwSlBLekZrU0hnNVpHUldNRm96Um5WdVMzZE5lRTlFUlN0aFlqSktTV0ZJUkhBM1VsbGpjVkZzYm13eWVIcEZZbWhXVW5wRU5rdzNXa0ZLTDBSbWFXMDFaRXBDVEhSRmVVTXZiVEpaV1dGbE5UZ3dXblJzSzFCa1JqaHFVVWR0VjNGVlZIaEJhMWRZUkROdmNETlhiMkZVYUVSblJuQjRNV3RuV0hoRFFWVnFTMFJWYmtKV1oxbHZlVU16TjB0R2VHVllhblJGU1ROM2QyVnVZamRXV0VVd05HSXhiVXR5Um5VMlUxRjJWbmh5ZEd0WEswMWxkVm80VkdWWGVGbDNVR2R0UjJGeVZXRTJhVTA0WVZKa1RFd3hjbTVXYm05M1owdFVORkJ5VTIxMGRtMVhRbU5wYVhSWFRqWklTRkJOUldodWJYRnJaMlpzWkc5aWRXaFlTamRXU20xMmJIUXlORTh4VjBKMVdtdHdkMnhxVGpWdFVVcGxiR0V3U2pacVNtbHRhWFJvU0dKa1lYRmljUzkyV1VjMWJsVXplbVI0TkdKdGJIWnllbkZNWVhOR01FOUZMMlowVTBsWlozaHlaVEZxTVhoNmVVWk5UbXhGUTNkRlVrOW9NbFJxYTI5dlFtUmhWVTlqTVRsTE4wVTVWbmxoZW5SNlFWZHJUR1pMVjNCUU9HbHJaSFEyZFhOR1MwUnJkM2RMVm04elRHUkZaMkYxUTBWeUswVk9kbXhhU1ZkemVXZ3lTRkI2VUc1TlpEbHpZblZJVlRselFqVlBNekl2YXpCUWVXdG1iV2hDVFhsNVZVRkVhRWhMUm01amExSlJZa3BMYVZCNmREQkVTalZqUzNST01VMWxZMVJsYm5sV1NuQTJkV1ZPV210bGEwTnVMMkkzWVRWR1ZrRnBUMmd6TjA1Vk5YQkNTRE4xVjJkUE9YVlFUV1ZNTnpWR1NFZHAuRHk0NzN6RE1TMjdaaFFvNWVrRXExZ1dRc3ByNmN6djZGZF9fUWVIVVF2Zl9kbUZVLUo4VnFxOU5hd3lZc3cwdFplN3ZMdDhxMFZCRTF3YTd0NmRUZEE=" />
	<input type="hidden" name="_eventId" value="submit" />
	<!--.....cas登录页面的HTML代码逻辑......-->
</form>	

1.修改cas登录页(casLoginView.jsp)逻辑

在cas代码的casLoginView.jsp页面添加如下代码,用来获取lt、execution,因为系统中还用了一个随机数(encryptNonce)进行密码认证,且这个随机数是cas服务这边生成的,针对每一次登录验证对应的随机数,所以还需要获取encryptNonce;通过 if (action != null && action.equals(“getLt”)) 判断此次请求是否是上述定制的模拟请求,如果不是,那么走cas正常的逻辑,不影响cas对接的其他业务

<%
	// web页获取cas的encryptNonce、loginTicket、flowExecutionKey
	String action = request.getParameter("action");
	if (action != null && action.equals("getLt")) {
		String callbackName = request.getParameter("callback");
		String json = "{\"encryptNonce\":\"" + request.getAttribute("encryptNonce") + "\",\"lt\":\"" + request.getAttribute("loginTicket") + "\", \"execution\":\"" + request.getAttribute("flowExecutionKey") + "\"}";
		json = callbackName + "(" + json + ")";
		response.setContentType("application/javascript;charset=utf-8");
		response.getWriter().write(json);
	} else {
%>
<!DOCTYPE html>
<html lang="en">
<head>
	<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
	<%@ include file="common/common.jsp" %>
    <!--.....下面是原来页面的HTML代码逻辑......-->
</body>
</html>
<% } %>

2.修改原有客户端登录页面表单

在原有的客户端登录login.jsp页面的from表单中添加lt、execution及_eventId的字段对应的input标签,表单的action值${casUrl}即为cas的登录首页地址,例如:http://192.168.78.1/cas/login,使用jsonp第一次请求获取cas的lt及execution信息,然后赋值给在这个表单对应lt和execution的值,然后远程提交到cas那边进行验证,cas验证通过,那么就直接重定向到需要单点系统的首页

<form action="${casUrl}" method="post" id="login_form_id">
	<%--CAS单点登录--%>
	<input type="hidden" name="lt" id="lt" value="" />
	<input type="hidden" name="execution" id="execution" value="" />
	<input type="hidden" name="_eventId"  id="_eventId" value="submit" />
	<!--.....下面是原来页面的HTML代码逻辑......-->
	<input name="username" id="username"/>
    <input name="password" id="password"/>
    ...
</form>																								  

3.利用JSONP获取lt及execution参数(可跨域获取)
在上面的login.jsp页面的js逻辑中,在表单提交之前($.getJSON)先去获取cas登录页的lt、execution、encryptNonce参数的值,然后再给当前页面的表单对应的元素复制,最后提交至cas

	jQuery(function($) {
		// 用户名密码
		var username ='${username}';
		var pwd ='${pwd}';
		// cas登录URL
		var casLoginUrl ='${casLoginUrl}';
		var casCrossUrl = casLoginUrl + "&action=getLt&callback=?";
		$.getJSON(casCrossUrl,
				function (data) {
					var lt = data.lt;
					var execution = data.execution;
					$("#lt").val(data.lt);
					$("#execution").val(data.execution);
					var encryptNonce = data.encryptNonce;
					$('#username').val(username);
					$('#password').val(pwd);
					md5Encrypt($("#login_form_id"), encryptNonce);
					$("#login_form_id").append('+window.location.hostname+'"/>');
					$("#login_form_id").append('+window.location.host+'"/>');
					jQuery("#login_form_id").submit();
				});
	});

补充

可以不用改造cas的页面也可以实现上述的功能,即通过第一次请求到了cas的登录html页面(返回html页面是完整的cas登录页,上面是有lt、execution等参数值),然后使用js正则匹配获取参数,然后逻辑就和上面一致了。

jQuery(function($) {
		// 用户名密码
		var username ='${username}';
		var pwd ='${pwd}';
		// cas登录页面的url
		var casLoginUrl ='${casLoginUrl}';
		var lt = "";
		var execution = "";
		var encryptNonce = "";
		$.get(casLoginUrl, function(htmlData){
			var html = htmlData;
			var r1 = /]+>/g;
			var matchResult1 = html.match(r1);
			for(var i = 0; i < matchResult1.length; i++) {
				var tmp = matchResult1[i];
				if(tmp.indexOf("name=\"lt\"") >= 0) {
					tmp = tmp.replace(","");
					lt = tmp.replace("\" \/>","");
				}
				if(tmp.indexOf("execution") >= 0) {
					tmp = tmp.replace(","");
					execution = tmp.replace("\" \/>","");
				}
			}
			// var r2=/(?<=md5Encrypt\(\$\("#login_form_id"\), ").+(?="\))/;
			var r2 = /.*md5Encrypt\(\\$\("#login_form_id"\), (.*)\)/;
			var matchResult2 = html.match(r2);
			if(matchResult2.length > 0){
				encryptNonce = matchResult2[1];
				encryptNonce = encryptNonce.replace("\"","");
				encryptNonce = encryptNonce.replace("\"","");
			}
			$("#lt").val(lt);
			$("#execution").val(execution);
			$('#username').val(username);
			$('#password').val(pwd);
			md5Encrypt($("#login_form_id"), encryptNonce);
			$("#login_form_id").append('+window.location.hostname+'"/>');
			$("#login_form_id").append('+window.location.host+'"/>');
			jQuery("#login_form_id").submit();
		});
	});

这里主要说明一下js的正则匹配,使用 /]+>/g 获取html页面的所有的input标签,然后再用replace函数筛选符合值;使用 /.*md5Encrypt\(\\$\("#login_form_id"\), (.*)\)/ 来匹配html页面指定开头和结尾的数据,之前使用的一种匹配规则是/(?<=开头内容).+(=结束内容)/,可以在Chrome上正常使用,但是在IE上此正则存在兼容性问题,所以就使用了第一种。

参考文章:
CAS客户端使用Ajax登陆(即保留原有客户端登录页面):
https://blog.csdn.net/qq_21166213/article/details/78523597
cas ajax + jsonp实现跨域登录:
https://blog.csdn.net/just_lion/article/details/39316169
正则表达式转义特殊字符(匹配HTTP响应体中的字符串):
https://blog.csdn.net/cjx529377/article/details/78287552
正则表达式获取指定字符之后指定字符之前的字符串:
https://www.cnblogs.com/zlv2snote/p/10448529.html
Java爬虫系列三:使用Jsoup解析HTML:
https://www.cnblogs.com/sam-uncle/p/10922366.html
JS正则表达式完整版:
https://blog.csdn.net/h610443955/article/details/81079439

你可能感兴趣的:(学习笔记)