diff --git a/.idea/gradle.xml b/.idea/gradle.xml index 3eb46851bee7dde9e5be859209a0923deac9a942..6cbecd347658b1de463fb2c26d026e632a9fa49c 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 a30f14307d57a79bd2ee999deb554ad9e52fd5c0..7199fb4a63d0b7d389d7b7cd73c5a9ad6afa93cf 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 2f1794d7882d017c0116dcfcc987588329152a34..26008a32f99bd11a9c7054f01d7376923160e9d6 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 75721e55ea43769499bfa36668adba795d1a8643..cf1b39429b712eb4bdc5f339027faa8f6bdf1beb 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 79ca0fc1009392bbf2a50143aecf1ac2fdc806b3..370c5d008c1de2a1fe2d0b06da31394a4f114331 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 new file mode 100644 index 0000000000000000000000000000000000000000..6b3c60f2166399267f9fc6b4a5f31fcd9c849b00 --- /dev/null +++ b/app/src/main/java/top/yvyan/guettable/util/AESUtil.java @@ -0,0 +1,47 @@ +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; +import android.util.Log; + +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(getRandomString(16).getBytes("UTF-8")); + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING"); + cipher.init(Cipher.ENCRYPT_MODE, secretKey,Iv); + 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) { + + } + return null; + } + public static String getRandomString(int length) { + String RNDCHARS = "ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678"; + StringBuilder rndString = new StringBuilder(); + Random rnd = new Random(); + for (int i=0;ihttps://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/