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/