From 76ee2f5bb63575aefd8242efb6842138a5185a84 Mon Sep 17 00:00:00 2001 From: bbaa Date: Sun, 19 Feb 2023 18:09:39 +0800 Subject: [PATCH 1/2] chore:Add AESUtil --- .../top/yvyan/guettable/util/AESUtil.java | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 app/src/main/java/top/yvyan/guettable/util/AESUtil.java diff --git a/app/src/main/java/top/yvyan/guettable/util/AESUtil.java b/app/src/main/java/top/yvyan/guettable/util/AESUtil.java new file mode 100644 index 0000000..b8788b0 --- /dev/null +++ b/app/src/main/java/top/yvyan/guettable/util/AESUtil.java @@ -0,0 +1,43 @@ +package top.yvyan.guettable.util; + +import java.util.Random; + +import javax.crypto.Cipher; +import javax.crypto.spec.SecretKeySpec; +import javax.crypto.spec.IvParameterSpec; + +import android.util.Base64; + +public class AESUtil { + /** + * CAS登录AES - CBC加密 + * + * @param text 未加密字符串 + * @param skey AES CBC Key + * @return AES加密后的字符串(base64 字符串) + */ + public static String CASEncryption(String text,String skey) { + try { + byte[] bkey = skey.getBytes("UTF-8"); + SecretKeySpec secretKey = new SecretKeySpec(bkey, "AES"); + IvParameterSpec Iv=new IvParameterSpec(AESUtil.getRandomString(16).getBytes("UTF-8")); + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING"); + cipher.init(Cipher.ENCRYPT_MODE, secretKey,Iv); + String paddingText = AESUtil.getRandomString(64)+text; + return Base64.encode(cipher.doFinal(paddingText.getBytes("UTF-8")),Base64.DEFAULT).toString(); + } catch (Exception ignored) { + + } + return null; + } + public static String getRandomString(int length) { + String RNDCHARS = "ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678"; + StringBuilder rndString = new StringBuilder(); + Random rnd = new Random(); + for (int i=0;i Date: Sun, 19 Feb 2023 22:42:47 +0800 Subject: [PATCH 2/2] =?UTF-8?q?feat:=E6=96=B0CAS=E7=99=BB=E5=BD=95?= =?UTF-8?q?=E7=B3=BB=E7=BB=9F=E5=85=BC=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/gradle.xml | 4 +- .../guettable/activity/LoginActivity.java | 10 +- .../top/yvyan/guettable/data/TokenData.java | 30 ++++-- .../yvyan/guettable/service/fetch/Net.java | 97 +++++++++++++++++++ .../service/fetch/StaticService.java | 27 +++--- .../top/yvyan/guettable/util/AESUtil.java | 10 +- app/src/main/res/values/strings.xml | 2 + 7 files changed, 145 insertions(+), 35 deletions(-) diff --git a/.idea/gradle.xml b/.idea/gradle.xml index 3eb4685..6cbecd3 100644 --- a/.idea/gradle.xml +++ b/.idea/gradle.xml @@ -4,7 +4,7 @@ diff --git a/app/src/main/java/top/yvyan/guettable/activity/LoginActivity.java b/app/src/main/java/top/yvyan/guettable/activity/LoginActivity.java index a30f143..7199fb4 100644 --- a/app/src/main/java/top/yvyan/guettable/activity/LoginActivity.java +++ b/app/src/main/java/top/yvyan/guettable/activity/LoginActivity.java @@ -347,17 +347,17 @@ public class LoginActivity extends Activity implements View.OnClickListener { private void testCAS(String account, String password, String VPNToken) { new Thread(() -> { runOnUiThread(() -> button.setText("正在认证")); - String TGTTokenStr = StaticService.SSOLogin(this, account, password, VPNToken); - if (TGTTokenStr.contains("TGT-")) { + String CasCookie = StaticService.SSOLogin(this, account, password, VPNToken); + if (CasCookie.contains("TGT-")) { TokenData tokenData = TokenData.newInstance(this); - tokenData.setTGTToken(TGTTokenStr); + tokenData.setCASCookie(CasCookie); tokenData.setLoginType(0); accountData.setUser(account, null, password, cbRememberPwd.isChecked()); getInfo(); } else { - if (TGTTokenStr.equals("ERROR1")) { + if (CasCookie.equals("ERROR1")) { showErrorToast(-4); - } else if (TGTTokenStr.equals("ERROR2")) { + } else if (CasCookie.equals("ERROR2")) { showErrorToast(-2); } else { showErrorToast(-8); diff --git a/app/src/main/java/top/yvyan/guettable/data/TokenData.java b/app/src/main/java/top/yvyan/guettable/data/TokenData.java index 2f1794d..26008a3 100644 --- a/app/src/main/java/top/yvyan/guettable/data/TokenData.java +++ b/app/src/main/java/top/yvyan/guettable/data/TokenData.java @@ -17,6 +17,8 @@ public class TokenData { private static final String SHP_NAME = "tokenData"; private static final String LOGIN_TYPE = "loginType"; private static final String TGT_TOKEN = "TGTToken"; + + private static final String CAS_Cookie = "CASCookie"; private static final String VPN_TOKEN = "VPNToken"; private static final String BKJW_COOKIE = "bkjwCookie"; private static final String IS_DEVELOP = "isDevelop"; @@ -31,6 +33,7 @@ public class TokenData { //强制获取vpn private boolean forceVPN = false; + private String CASCookie; // 新版CAS认证Cookie; CASTGT/JSESSION private String TGTToken; //统一登录TGT令牌 private String VPNToken; //VPN认证Token private String bkjwCookie; //教务系统认证Cookie @@ -71,6 +74,7 @@ public class TokenData { VPNToken = sharedPreferences.getString(VPN_TOKEN, null); bkjwCookie = sharedPreferences.getString(BKJW_COOKIE, null); isDevelop = sharedPreferences.getBoolean(IS_DEVELOP, false); + CASCookie = sharedPreferences.getString(CAS_Cookie, ""); } public static TokenData newInstance(Context context) { @@ -164,13 +168,13 @@ public class TokenData { int n = loginVpnByCAS(VPNTokenStr); //登录教务 if (n == 0) { - String ST_BKJW = StaticService.SSOGetST(context, TGTToken, context.getResources().getString(R.string.service_bkjw), VPNTokenStr); + String ST_BKJW = StaticService.SSOGetST(context, CASCookie, context.getResources().getString(R.string.service_bkjw), VPNTokenStr); if (ST_BKJW.equals("ERROR0")) { return -2; } else if (ST_BKJW.equals("ERROR1")) { //TGT失效 n = refreshTGT(VPNTokenStr); if (n == 0) { - ST_BKJW = StaticService.SSOGetST(context, TGTToken, context.getResources().getString(R.string.service_bkjw), VPNTokenStr); + ST_BKJW = StaticService.SSOGetST(context, CASCookie, context.getResources().getString(R.string.service_bkjw), VPNTokenStr); if (!ST_BKJW.contains("ST-")) { return -2; } @@ -186,13 +190,13 @@ public class TokenData { return n; } else { // 内网 StringBuilder cookie_builder = new StringBuilder(); - String ST_BKJW = StaticService.SSOGetST(context, TGTToken, context.getResources().getString(R.string.service_bkjw), null); + String ST_BKJW = StaticService.SSOGetST(context, CASCookie, context.getResources().getString(R.string.service_bkjw), null); if (!ST_BKJW.contains("ST-")) { // TGT失效 int n = refreshTGT(null); if (n != 0) { return n; } - ST_BKJW = StaticService.SSOGetST(context, TGTToken, context.getResources().getString(R.string.service_bkjw), null); + ST_BKJW = StaticService.SSOGetST(context, CASCookie, context.getResources().getString(R.string.service_bkjw), null); if (!ST_BKJW.contains("ST-")) { // 网络错误,切换为外网模式 return -2; } @@ -215,14 +219,14 @@ public class TokenData { */ private int loginVpnByCAS(String VPNTokenStr) { int n; - String ST_VPN = StaticService.SSOGetST(context, TGTToken, context.getResources().getString(R.string.service_vpn), VPNTokenStr); + String ST_VPN = StaticService.SSOGetST(context, CASCookie, context.getResources().getString(R.string.service_vpn), VPNTokenStr); if (ST_VPN.equals("ERROR0")) { return -2; } else if (ST_VPN.equals("ERROR1")) { //TGT失效 n = refreshTGT(VPNTokenStr); //刷新TGT if (n == 0) { //重新获取登录vpn的st令牌 - ST_VPN = StaticService.SSOGetST(context, TGTToken, context.getResources().getString(R.string.service_vpn), VPNTokenStr); + ST_VPN = StaticService.SSOGetST(context, CASCookie, context.getResources().getString(R.string.service_vpn), VPNTokenStr); if (!ST_VPN.contains("ST-")) { return -2; } @@ -248,18 +252,24 @@ public class TokenData { * @return 操作结果 */ public int refreshTGT(String VPNToken) { - String TGTTokenStr = StaticService.SSOLogin(context, accountData.getUsername(), accountData.getVPNPwd(), VPNToken); - if (TGTTokenStr.equals("ERROR2") || TGTTokenStr.equals("ERROR0")) { + String CASCookieStr = StaticService.SSOLogin(context, accountData.getUsername(), accountData.getVPNPwd(), VPNToken); + if (CASCookieStr.equals("ERROR2") || CASCookieStr.equals("ERROR0")) { return -2; } - if (TGTTokenStr.contains("TGT-")) { - setTGTToken(TGTTokenStr); + if (CASCookieStr.contains("TGT-")) { + setCASCookie(CASCookieStr); return 0; } else { return -1; } } + public void setCASCookie(String CASCookie) { + this.CASCookie = CASCookie; + editor.putString(CAS_Cookie, CASCookie); + editor.apply(); + } + public int getLoginType() { return loginType; } diff --git a/app/src/main/java/top/yvyan/guettable/service/fetch/Net.java b/app/src/main/java/top/yvyan/guettable/service/fetch/Net.java index 75721e5..cf1b394 100644 --- a/app/src/main/java/top/yvyan/guettable/service/fetch/Net.java +++ b/app/src/main/java/top/yvyan/guettable/service/fetch/Net.java @@ -6,7 +6,10 @@ import android.content.res.Resources; import com.google.gson.Gson; import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; import java.util.concurrent.TimeUnit; +import java.net.URLEncoder; import okhttp3.Call; import okhttp3.OkHttpClient; @@ -19,7 +22,9 @@ import top.yvyan.guettable.Http.HttpConnectionAndCode; import top.yvyan.guettable.Http.Post; import top.yvyan.guettable.R; import top.yvyan.guettable.data.GeneralData; +import top.yvyan.guettable.util.AESUtil; import top.yvyan.guettable.util.RSAUtil; +import top.yvyan.guettable.util.RegularUtil; import top.yvyan.guettable.util.UrlReplaceUtil; public class Net { @@ -75,6 +80,69 @@ public class Net { return get_res.cookie; } + /** + * 获取CAS 登录令牌 + * + * @param context context + * @param account 学号 + * @param password 密码 + * @param VPNToken VPNToken + * @return CAS服务Cookie *请求 + */ + public static HttpConnectionAndCode getCASToken(Context context, String account, String password, String VPNToken) { + StringBuilder cookie_builder=new StringBuilder(); + try { + if(VPNToken!=null) cookie_builder.append(VPNToken); + Resources resources = context.getResources(); + HttpConnectionAndCode loginParams = Get.get( + VPNToken != null ? resources.getString(R.string.url_get_TGT_vpn) : resources.getString(R.string.url_Authserver), + null, + resources.getString(R.string.user_agent), + resources.getString(R.string.SSO_referer), + cookie_builder.toString(), + null, + resources.getString(R.string.cookie_delimiter), + null, + null, + null, + null, + 10000, + null); + if (loginParams.code != 0) { + return new HttpConnectionAndCode(-5); + } + if(cookie_builder.length() != 0){ + cookie_builder.append(resources.getString(R.string.cookie_delimiter)); + } + cookie_builder.append(loginParams.cookie); + ArrayList listExp = RegularUtil.getAllSatisfyStr(loginParams.comment, "(?<=id=\"pwdEncryptSalt\" value=\")(\\w+)(?=\")"); + String AESKey = listExp.get(0); + listExp = RegularUtil.getAllSatisfyStr(loginParams.comment, "(?<=name=\"execution\" value=\")(.*?)(?=\")"); + String execution = listExp.get(0); + String body = "username=" + account + "&password=" + URLEncoder.encode(AESUtil.CASEncryption(password, AESKey), "UTF-8")+"&captcha=&_eventId=submit&cllt=userNameLogin&dllt=generalLogin<=&execution="+URLEncoder.encode(execution,"UTF-8"); + HttpConnectionAndCode LoginRequest = Post.post( + VPNToken != null ? resources.getString(R.string.url_get_TGT_vpn) : resources.getString(R.string.url_Authserver), + null, + resources.getString(R.string.user_agent), + resources.getString(R.string.SSO_referer), + body, + cookie_builder.toString(), + "}", + resources.getString(R.string.cookie_delimiter), + null, + null, + null, + resources.getString(R.string.SSO_context_type)); + if(LoginRequest.code==0) { + LoginRequest.cookie = cookie_builder.append(LoginRequest.cookie).toString(); + } + return LoginRequest; + } catch (Exception igonred) { + + } + return new HttpConnectionAndCode(-5); + } + /** * 获取SSO TGT令牌 * @@ -105,6 +173,35 @@ public class Net { ); } + /** + * 获取SSO ST令牌 新版CAS + * + * @param context context + * @param CASCookie + * @param service ST令牌的服务端 + * @param VPNToken VPNToken #仅用于兼容性使用,此处会包含在CASCookie内 + * @return ST令牌 + */ + public static HttpConnectionAndCode getSTbyCas(Context context, String CASCookie, String service, String VPNToken) { + Resources resources = context.getResources(); + HttpConnectionAndCode probeST = Get.get( + (VPNToken != null ? resources.getString(R.string.url_get_TGT_vpn) : resources.getString(R.string.url_Authserver))+"?"+service, + null, + resources.getString(R.string.user_agent), + resources.getString(R.string.SSO_referer), + CASCookie, + null, + resources.getString(R.string.cookie_delimiter), + null, + null, + false, + null, + 10000, + null); + return probeST; + //return new HttpConnectionAndCode(-5); + } + /** * 获取SSO ST令牌 * diff --git a/app/src/main/java/top/yvyan/guettable/service/fetch/StaticService.java b/app/src/main/java/top/yvyan/guettable/service/fetch/StaticService.java index 79ca0fc..370c5d0 100644 --- a/app/src/main/java/top/yvyan/guettable/service/fetch/StaticService.java +++ b/app/src/main/java/top/yvyan/guettable/service/fetch/StaticService.java @@ -46,29 +46,28 @@ import top.yvyan.guettable.util.RegularUtil; public class StaticService { /** - * 获取SSO登录TGT令牌 + * 获取SSO登录CasCookie * * @param context context * @param account 学号 * @param password 密码 * @param VPNToken VPNToken - * @return TGT令牌 + * @return CAS Cookie * ERROR0 : 网络错误 * ERROR1 : 密码错误 * ERROR2 : 需要使用外网网址进行访问 */ public static String SSOLogin(Context context, String account, String password, String VPNToken) { - HttpConnectionAndCode response = Net.getTGT(context, account, password, VPNToken); + HttpConnectionAndCode response = Net.getCASToken(context, account, password, VPNToken); if (response.code != 0) { if (response.code == -5) { return "ERROR2"; } return "ERROR0"; } else { - String html = response.comment; - if (html.contains("TGT-")) { - ArrayList listExp = RegularUtil.getAllSatisfyStr(html, "TGT-(.*?)\""); - return listExp.get(0).substring(0, listExp.get(0).length() - 1); + String Cookie = response.cookie; + if (Cookie.contains("TGT-")) { + return Cookie; } else { return "ERROR1"; } @@ -79,7 +78,7 @@ public class StaticService { * 获取SSO ST令牌 * * @param context context - * @param TGT TGT令牌 + * @param CASCookie * @param service ST令牌的服务端 * @param VPNToken VPNToken * @return ST令牌 @@ -88,9 +87,9 @@ public class StaticService { * ERROR2 : 需要使用外网网址进行访问 或 TGT失效(上层调用时,若内网返回此错误, * 则先尝试外网,若是TGT失效,则重新获取;若正常获取,则需要将全局网络设置为外网) */ - public static String SSOGetST(Context context, String TGT, String service, String VPNToken) { - HttpConnectionAndCode response = Net.getST(context, TGT, service, VPNToken); - if (response.code != 0) { + public static String SSOGetST(Context context, String CASCookie, String service, String VPNToken) { + HttpConnectionAndCode response = Net.getSTbyCas(context, CASCookie, service, VPNToken); + if (response.code != -7) { if (response.code == -5) { if (VPNToken != null) { return "ERROR1"; @@ -99,9 +98,9 @@ public class StaticService { } return "ERROR0"; } else { - String html = response.comment; - if (html.contains("ST-")) { - return html; + String Location = response.c.getHeaderField("location"); + if (Location.contains("ST-")) { + return Location.substring(Location.indexOf("?ticket=ST-")+8); } return "ERROR1"; } diff --git a/app/src/main/java/top/yvyan/guettable/util/AESUtil.java b/app/src/main/java/top/yvyan/guettable/util/AESUtil.java index b8788b0..6b3c60f 100644 --- a/app/src/main/java/top/yvyan/guettable/util/AESUtil.java +++ b/app/src/main/java/top/yvyan/guettable/util/AESUtil.java @@ -7,6 +7,7 @@ import javax.crypto.spec.SecretKeySpec; import javax.crypto.spec.IvParameterSpec; import android.util.Base64; +import android.util.Log; public class AESUtil { /** @@ -20,11 +21,14 @@ public class AESUtil { try { byte[] bkey = skey.getBytes("UTF-8"); SecretKeySpec secretKey = new SecretKeySpec(bkey, "AES"); - IvParameterSpec Iv=new IvParameterSpec(AESUtil.getRandomString(16).getBytes("UTF-8")); + IvParameterSpec Iv=new IvParameterSpec(getRandomString(16).getBytes("UTF-8")); Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING"); cipher.init(Cipher.ENCRYPT_MODE, secretKey,Iv); - String paddingText = AESUtil.getRandomString(64)+text; - return Base64.encode(cipher.doFinal(paddingText.getBytes("UTF-8")),Base64.DEFAULT).toString(); + String paddingText = getRandomString(64)+text; + byte[] Encrypted=cipher.doFinal(paddingText.getBytes("UTF-8")); + String Base64Result = Base64.encodeToString(Encrypted,Base64.DEFAULT); + // Log.d("AES",Encrypted); + return Base64Result; } catch (Exception ignored) { } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b038389..b4c4432 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -5,6 +5,8 @@ https://v.guet.edu.cn/login https://v.guet.edu.cn + https://cas.guet.edu.cn/authserver/login + https://cas.guet.edu.cn/cas/v1/tickets https://v.guet.edu.cn/https/77726476706e69737468656265737421f3f652d220256d44300d8db9d6562d/cas/v1/tickets https://cas.guet.edu.cn/cas/v1/tickets/ -- Gitee