zoukankan      html  css  js  c++  java
  • 今日校园自动登录教程

    我之前写的脚本都是保持会话的那种,登录一次永久有效。这不是放假了吗,就重新完成了自动登录版。

    此处是废话,可以直接大纲目录跳过去。

    现在是2021年1月11日的凌晨2点,连着加班了4天,从无到有的写出来了一个自动登录脚本,还是蛮开心的。

    网上很早就有大佬写的自动登录,但是他们的学校是以NOCLOUD的方式加入今日校园的,像比较牛逼的合肥工业大学,这种的都是将这些功能对接到自己学校官网了。

    而像长春工大这种的,我看是CLOUD方式加入的,所以他们的那些NOCLOUD自动登录就不好使。

    但是写完了,就感觉接下来的生活没啥动力了,我可能需要给自己定个新的目标了。

    言归正传!

    源码,放张运行截图。

    一、接口

    以下接口更新于1月11,后续像接口啥的不好使了,那就是今日校园升级了。

    查询加入今日校园的所有学校

    https://static.campushoy.com/apicache/tenantListSort
    

    查询学校的详细信息

    https://mobile.campushoy.com/v6/config/guest/tenant/info?ids=参数
    

    参数是在上面的链接中搜索你的学校,获取的ID值。以长春工业大学为例,ccut即我们所需的参数

    获取到了参数,我们就可以查询学校的详细信息。通过下图可知,长春工业大学的加入方式是CLOUD,登录地址idsUrl后面的那串地址。

    xxx学校的云端登录地址在这里像xxx.campusphere.net我就用host来代替了,下面同理

    https://host/iap
    

    二、分析

    今日校园是CAS单点登录系统,说白了,就是多个系统中,用户登录一次各个系统即可感知用户已登录。所以呢,云端跟手机app之前是共享某些关键数据的,比如cookie。因此,我们可以通过获取网页端的cookie,来实现手机app的提交。

    我们提交问卷表时,需要携带正确的MOD_AUTH_CAS,而这个cookie是登录之后获取的。所以自动登录的最终目标就是获取MOD_AUTH_CAS。

    手动登录一次,然后分析抓包的数据。

    登录的接口

    https://host/iap/doLogin
    

    登录的请求体是

    username=学号&password=密码&mobile=&dllt=&captcha=验证码&rememberMe=false&lt=lt值
    

    可知,我们登录所需的是学号、密码、验证码和lt。

    lt在请求过程中,匹配的前提是,你携带conversation请求。再通过抓包分析,我们需要访问下面的这个地址,来获取lt和conversation

    https://host/iap/login?service=https://host/portal/login 
    

    返回结果如图所示

    如此,我们就获取到了Conversation和lt。

    接下来,就需要考虑验证码,需要携带lt和conversation来获取的,否则是不匹配的。

    https://host/iap/generateCaptcha?ltId=lt
    

    验证码是在错误三次的时候,才会异步请求验证码,界面弹出验证码选项。

    我一开始的做法是不携带验证码登录,错误之后,再携带验证码,就跟常规登录流程一样。后来发现大可不必这么麻烦,我们第一次就主动请求验证码,然后携带登录,这样就方便多了。

    学号、密码、验证码和lt以及Cookie中的Conversation准备就绪之后,我们就可以构造请求体,向登录接口发送请求了。

    成功登录之后,会返回一个跳转链接。

    访问这个链接,我们就可以获取到MOD_AUTH_CAS,目标达成!

    总结步骤啦

    1. 获取lt与Conversation
    2. 识别captcha
    3. 构造body
    4. 获取MOD_AUTH_CAS

    三、重点

    通过上面分析来看,其实不难,难得是识别验证码。

    这验证码的识别,原来门道这么多,比方说一个简单的数字验证码,就要经过将图片预处理(类似于人在调节亮度、对比度之类的这种操作)、然后将图片中数字分割、训练、最后再进行识别。识别还要进行一个像素一个像素的比较,取相同点最多的。

    我简直头大了,这要是我自己写的话,不得搞一年??

    后来就试了一下百度的AI识别验证码,不得不说,真牛逼。但是呢,还要注册绑定个人信息,才能给用,算了,太麻烦了。

    就在网上看了看,发现了Java一个比较牛逼的库,tess4j,使用他的前提是,你还得下载他的识别训练库

    那就用他了,我一开始是想让java直接识别网页的验证码,但是格式不支持。没想到好的办法。最后的实现思路是

    1. 下载验证码
    2. 识别
    3. 矫正格式

    附上识别验证码的工具类CaptchaDecoding.java

    import java.io.File;
    import java.io.FileOutputStream;
    import java.io.InputStream;
    import java.net.HttpURLConnection;
    import java.net.URL;
    import java.util.Map;
    import java.util.Set;
    import java.util.Map.Entry;
    import net.sourceforge.tess4j.Tesseract;
    import net.sourceforge.tess4j.TesseractException;
    
    /**
     * 
     * CaptchaDecoding 用来识别验证码
     *
     * @author kit chen
     * @github https://github.com/meethigher
     * @blog https://meethigher.top
     * @time 2021年1月10日
     */
    public class CaptchaDecoding {
    	/**
    	 * 云端下载验证码
    	 * 
    	 * @param url
    	 * @param headers
    	 * @return
    	 */
    	public static File downloadCaptcha(String url, Map<String, String> headers) {
    		InputStream is = null;
    		FileOutputStream fos = null;
    		try {
    			URL realUrl = new URL(url);
    			HttpURLConnection conn = (HttpURLConnection) realUrl.openConnection();
    			// 必须设置false,否则会自动重定向到目标地址
    			conn.setInstanceFollowRedirects(false);
    			if (headers != null) {
    				Set<Entry<String, String>> set = headers.entrySet();
    				for (Entry<String, String> header : set) {
    					conn.setRequestProperty(header.getKey(), header.getValue());
    				}
    			}
    			conn.connect();
    			is = conn.getInputStream();
    			fos = new FileOutputStream("captcha.jpg");
    			byte[] buffer = new byte[1024];
    			int length;
    			while ((length = is.read(buffer)) > 0) {
    				fos.write(buffer, 0, length);
    			}
    		} catch (Exception e) {
    			System.out.println("读取验证码出错!");
    			e.printStackTrace();
    		} finally {
    			try {
    				if (is != null)
    					is.close();
    				if (fos != null)
    					fos.close();
    			} catch (Exception e2) {
    
    			}
    		}
    		return new File("captcha.jpg");
    	}
    
    	/**
    	 * 识别验证码
    	 * 
    	 * @param file
    	 * @return
    	 */
    	public static String parseCaptcha(File file) {
    		Tesseract tess = new Tesseract();
    		//开发环境运行时设置
    		tess.setDatapath(ClassLoader.getSystemResource("tessdata").getPath().substring(1));
    		//jar包运行时设置
    //		String tesspath =  System.getProperty("user.dir");
    //		tess.setDatapath(tesspath+"/tessdata");
    		tess.setLanguage("eng");
    		try {
    			return tess.doOCR(file).replace(" ", "");
    		} catch (TesseractException e) {
    			System.out.println("解析验证码出错!");
    			e.printStackTrace();
    			return null;
    		}
    	}
    }
    

    好像没啥特别难的了。

    最后附上登录的工具类Login.java吧。难倒是不难,主要是分析以及试错耗费了不少时间。

    import java.net.HttpURLConnection;
    import java.util.LinkedHashMap;
    import java.util.Map;
    import java.util.regex.Matcher;
    import java.util.regex.Pattern;
    
    import net.sf.json.JSONObject;
    
    /**
     * 
     * Login 用来登录获取cookie的工具类
     *
     * @author kit chen
     * @github https://github.com/meethigher
     * @blog https://meethigher.top
     * @time 2021年1月7日-2021年1月10日
     */
    public class Login {
    	private static String host = Data.host;
    	private static String id = Data.id;
    	private static String pw = Data.pw;
    	// 最大试错次数
    	private static int maxError = 10;
    	// 这个值用来登录时携带,服务端有验证
    	private static String lt;
    	// 用来存放cookie
    	private static String cookie;
    	// 用来获取MOD_CAS_AUTH,返回值中ticket后面的值就是
    	public static String doLogin = host + "/iap/doLogin";
    	// 用来登录
    	public static String login = host + "/portal/login";
    	// 用来获取lt
    	public static String getLt = host + "/iap/login?service=" + host + "/portal/login";
    	// 用来验证lt
    	public static String checkLt = host + "/iap/security/lt";
    	// 用来获取验证码
    	public static String getCaptcha = host + "/iap/generateCaptcha?ltId=";
    	// 用来存放MOD_AUTH_CAS
    	public static String MOD_AUTH_CAS = null;
    	// 用于验证登录状态
    	public static String task = host + "/portal/task/queryTodoTask";
    
    	/**
    	 * 通过正则截取字符串
    	 * 
    	 * @param s
    	 * @param regex
    	 * @return
    	 */
    	public static String getSub(String s, String regex) {
    		// "(?<==)\S+$",正则用来提取=号之后的东西
    		Matcher matcher = Pattern.compile(regex).matcher(s);
    		while (matcher.find()) {
    			return matcher.group(0);
    		}
    		return null;
    	}
    
    	/**
    	 * 请求头
    	 * 
    	 * @param cookie
    	 * @return
    	 */
    	public static Map<String, String> getHeaders(String cookie) {
    		Map<String, String> map = new LinkedHashMap<String, String>();
    		map.put("User-Agent",
    				"Mozilla/5.0 (Linux; Android 11; MI 11 Build/QKQ1.190825.002; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/33.0.0.0 Mobile Safari/537.36 okhttp/3.8.1");
    		map.put("Content-Type", "application/x-www-form-urlencoded");
    		map.put("Host", host);
    		map.put("Connection", "Keep-Alive");
    		map.put("Accept-Encoding", "gzip");
    		// 这个必须带着,不然登录时,要多一步获取cookie的步骤
    		map.put("X-Requested-With", "XMLHttpRequest");
    		map.put("Cookie", cookie);
    		return map;
    	}
    
    	/**
    	 * 获取验证码
    	 * 
    	 * @param url
    	 * @return
    	 */
    	public static String getCaptcha(String url) {
    		String s = CaptchaDecoding.parseCaptcha(CaptchaDecoding.downloadCaptcha(url, null));
    		return s.substring(0, 5);
    	}
    
    	/**
    	 * 获取LT
    	 * 
    	 * @param conn
    	 * @return
    	 */
    	public static String getLt(HttpURLConnection conn) {
    		return getSub(conn.getHeaderField("Location"), "(?<==)\S+$");
    	}
    
    	/**
    	 * 获取响应头中的cookie
    	 * 
    	 * @param conn
    	 * @return
    	 */
    	public static String getCookie(HttpURLConnection conn) {
    		return conn.getHeaderField("Set-Cookie").split(";")[0];
    	}
    
    	/**
    	 * 生成登录请求体
    	 * 
    	 * @param captcha
    	 * @return
    	 */
    	public static String getLoginBody(String captcha) {
    		if (captcha == null)
    			captcha = "";
    		return "username=" + id + "&password=" + pw + "&mobile=&dllt=&captcha=" + captcha + "&rememberMe=false&" + "lt="
    				+ lt;
    	}
    
    	/**
    	 * 进行登录
    	 * 
    	 * @param param
    	 * @return
    	 */
    	public static String login(String param) {
    		JSONObject object = JSONObject.fromObject(HttpUtil.sendPost(doLogin, param, getHeaders(cookie)));
    		// 下面这串代码是开发时为了验证异步请求。结果证明需要。使用时直接注释,不用管
    //		HttpURLConnection postConn = HttpUtil.postConn(doLogin,param,getHeaders(cookie));
    //		System.out.println("输出:"+postConn.getHeaderField("Location").replace(host+"/portal/login?", ""));
    
    		String string = null;
    		if ("REDIRECT".equals(object.get("resultCode"))) {
    			string = "success";
    			HttpUtil.sendGet(object.getString("url"), getHeaders(""));
    			MOD_AUTH_CAS = getSub(object.getString("url"), "(?<==)\S+$");
    		} else if ("CAPTCHA_NOTMATCH".equals(object.get("resultCode"))) {
    			string = "captchaError";
    		} else if ("LT_NOTMATCH".equals(object.get("resultCode"))) {
    			string = "ltError";
    		} else if ("FAIL_UPNOTMATCH".equals(object.get("resultCode"))) {
    			string = "upError";
    		} else {
    			string = "error";
    		}
    		return string;
    	}
    
    	/**
    	 * 获取成功登录状态的cookie
    	 * 
    	 * @return
    	 */
    	public static String getAccess() {
    		String captcha, body;
    		System.out.println("获取登录数据...");
    		HttpURLConnection conn = HttpUtil.getConn(getLt, null);
    		lt = getLt(conn);
    		System.out.println("获取lt:" + lt);
    		cookie = getCookie(conn);
    		System.out.println("获取cookie:" + cookie);
    		int i = 1;
    		String loginResult = null;
    		while (i <= maxError) {
    			captcha = getCaptcha(getCaptcha + lt);
    			System.out.println("识别captcha:" + captcha);
    			body = getLoginBody(captcha);
    			System.out.println("生成body..." );
    			System.out.print("正在尝试第" + i + "次登录:");
    			loginResult = login(body);
    			if ("success".equals(loginResult)) {
    				break;
    			} else if ("captchaError".equals(loginResult)) {
    				System.out.println("captcha识别不正确!");
    			} else if ("ltError".equals(loginResult)) {
    				System.out.println("lt不匹配!");
    			} else if ("upError".equals(loginResult)) {
    				System.out.println("账户密码不匹配!");
    			} else {
    				System.out.println("检查账户是否冻结、今日校园官方系统是否异常、lt或账号密码是否为空,或者直接联系开发者meethigher@qq.com!");
    			}
    			i++;
    		}
    		if ("success".equals(loginResult)) {
    			System.out.println("登录成功!");
    			return MOD_AUTH_CAS;
    		} else {
    			System.out.println("登录失败!");
    		}
    		return null;
    	}
    
    	/**
    	 * 验证是否已经失效
    	 * 
    	 * @return
    	 */
    	public static boolean isOff() {
    		String result = HttpUtil.sendPost(task, "", getHeaders("MOD_AUTH_CAS=" + MOD_AUTH_CAS));
    		if (result.indexOf("WEC-REDIRECTURL") > 0) {
    			return true;
    		} else {
    			return false;
    		}
    	}
    }
    

    四、傻瓜版使用教程

    本来我想做个网页端的,用于接收账户密码等个人信息,服务器自行运行,这样算是全透明的,但是考虑到工程量较大,意义也不大,就放弃了。

    傻瓜版的话,下载我的源码中的easy版,这个适用于不会编程的小伙伴。

    里面有三个文件,分别是cpdaily.jar包、tessdata语言识别包、collection.properties配置文件。

    将他们随便放到一个文件夹中(如果运行有误,那就更换为路径没有中文的文件夹)

    配置文件中,输入你的账号密码、学校的host、发件邮箱账号密码、收件邮箱、签到地址、提交的关键字(我们学校是单选,所以就关键字了)

    切记配置文件中的中文用Unicode编码,不要用中文。

    打开cmd,运行下面的命令即可(如果电脑没有java环境,自己百度即可,java8或java1.8或者更高即可)

    java -jar cpdaily.jar
    

    五、致谢

    1. Java识别验证码
    2. captcha-ock
    3. tess4j
    4. tessdata
    5. 使用Tesseract OCR来实现图片文字识别
    6. 利用Tess4J进行验证码识别
    7. java将网页转图片

    写在最后,我写这篇教程的意义,不是为了让你照抄代码,说实话,我的码品也不太行,抄代码没意思。我分享的是思路。如果思路搞明白了,那么问卷、查寝、签到、请假的登录不就都可以实现了吗?哈哈。

    这叫做授人以鱼不如授人以渔

  • 相关阅读:
    树上点对统计poj1741(树的点分治)
    hdu5115(区间dp)
    bestcoder Round#52 1001(最短路+状压dp)
    lightoj1038(期望dp)
    模线性方程组
    hdu2089 数位dp
    poj2955括号匹配 区间DP
    poj1417 带权并查集 + 背包 + 记录路径
    poj1984 带权并查集(向量处理)
    zoj3261 并查集离线处理
  • 原文地址:https://www.cnblogs.com/meethigher/p/14260477.html
Copyright © 2011-2022 走看看