Bkjx(本科教学)系统对应的API常量列表(拼音咱就别吐槽了吧...)
+ *其实是本科生用的教务处系统
+ */ +@Getter +public class Bkjx { + public static final String BKJX_SSO_SERVICE = "http://bkjx.wust.edu.cn/jsxsd/sso.jsp"; + public static final String BKJX_SESSION_COOKIE_API = "http://bkjx.wust.edu.cn/jsxsd/sso.jsp?ticket=%s"; + + public static final String BKJX_TEST_API = "http://bkjx.wust.edu.cn/jsxsd/xxwcqk/xxwcqk_idxOnzh.do"; + + public static class Legacy { + public static final String BKJX_INDEX = "http://bkjx.wust.edu.cn"; + public static final String BKJX_DATA_STRING_API = "http://bkjx.wust.edu.cn/Logon.do?method=logon&flag=sess"; + public static final String BKJX_SESSION_COOKIE_API = "http://bkjx.wust.edu.cn/Logon.do?method=logon"; + } +} diff --git a/mywust-core/src/main/java/cn/linghang/mywust/core/api/JwcApiUrl.java b/mywust-core/src/main/java/cn/linghang/mywust/core/api/JwcApiUrl.java deleted file mode 100644 index 68b25e9..0000000 --- a/mywust-core/src/main/java/cn/linghang/mywust/core/api/JwcApiUrl.java +++ /dev/null @@ -1,15 +0,0 @@ -package cn.linghang.mywust.core.api; - -import lombok.Getter; - -@Getter -public class JwcApiUrl { - /** - * 统一认证登录验证的api地址,请求之后可进一步获取ticket - */ - public static final String UNION_AUTH_API = "https://auth.wust.edu.cn/lyuapServer/v1/tickets"; - - public static final String JWC_SSO_SERVICE = "http://bkjx.wust.edu.cn/jsxsd/sso.jsp"; - public static final String JWC_TICKET_API = "http://bkjx.wust.edu.cn/jsxsd/sso.jsp?ticket=%s"; - -} diff --git a/mywust-core/src/main/java/cn/linghang/mywust/core/api/UnionAuth.java b/mywust-core/src/main/java/cn/linghang/mywust/core/api/UnionAuth.java new file mode 100644 index 0000000..f6778dd --- /dev/null +++ b/mywust-core/src/main/java/cn/linghang/mywust/core/api/UnionAuth.java @@ -0,0 +1,8 @@ +package cn.linghang.mywust.core.api; + +public class UnionAuth { + /** + * 统一认证登录验证的api地址,请求之后可进一步获取service ticket来对具体的服务进行登录获取Cookies + */ + public static final String UNION_AUTH_API = "https://auth.wust.edu.cn/lyuapServer/v1/tickets"; +} diff --git a/mywust-core/src/main/java/cn/linghang/mywust/core/service/auth/UnionLogin.java b/mywust-core/src/main/java/cn/linghang/mywust/core/service/auth/UnionLogin.java new file mode 100644 index 0000000..2749917 --- /dev/null +++ b/mywust-core/src/main/java/cn/linghang/mywust/core/service/auth/UnionLogin.java @@ -0,0 +1,58 @@ +package cn.linghang.mywust.core.service.auth; + +import cn.linghang.mywust.core.api.UnionAuth; +import cn.linghang.mywust.core.exception.BasicException; +import cn.linghang.mywust.core.exception.PasswordWornException; +import cn.linghang.mywust.core.service.undergraduate.AuthRequestFactory; +import cn.linghang.mywust.network.HttpRequest; +import cn.linghang.mywust.network.HttpResponse; +import cn.linghang.mywust.network.RequestClientOption; +import cn.linghang.mywust.network.Requester; +import cn.linghang.mywust.util.PasswordEncoder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.net.URL; + +/** + * 统一认证登录 + */ +public class UnionLogin { + Logger log = LoggerFactory.getLogger(UnionLogin.class); + + private final Requester requester; + + public UnionLogin(Requester requester) { + this.requester = requester; + } + + public String getServiceTicket(String username, String password, String serviceUrl, RequestClientOption requestOption) throws IOException, BasicException { + String encodedPassword = PasswordEncoder.encodePassword(password); + + // 获取ticket granting ticket(TGT),以获取ServiceTicket + HttpRequest TGTRequest = AuthRequestFactory.unionLoginTGTRequest(username, encodedPassword, serviceUrl); + + HttpResponse unionAuthResponse = requester.post(new URL(UnionAuth.UNION_AUTH_API), TGTRequest, requestOption); + + String redirectAddress = unionAuthResponse.getHeaders().get("Location"); + if (redirectAddress == null) { + throw new PasswordWornException(); + } + + // 获取服务ticket(service ticket,ST) + HttpRequest serviceTicketRequest = AuthRequestFactory.loginTicketRequest(serviceUrl); + + HttpResponse serviceTicketResponse = + requester.post(new URL(redirectAddress.replace("http:", "https:")), serviceTicketRequest, requestOption); + + byte[] serviceTicketResponseData = serviceTicketResponse.getBody(); + if (serviceTicketResponseData == null) { + log.warn("获取服务st出错,serviceTicketResponseData == null"); + + throw new BasicException(); + } + + return new String(serviceTicketResponseData); + } +} diff --git a/mywust-core/src/main/java/cn/linghang/mywust/core/service/undergraduate/AuthRequestFactory.java b/mywust-core/src/main/java/cn/linghang/mywust/core/service/undergraduate/AuthRequestFactory.java new file mode 100644 index 0000000..f69d909 --- /dev/null +++ b/mywust-core/src/main/java/cn/linghang/mywust/core/service/undergraduate/AuthRequestFactory.java @@ -0,0 +1,68 @@ +package cn.linghang.mywust.core.service.undergraduate; + +import cn.linghang.mywust.network.HttpRequest; +import cn.linghang.mywust.util.StringUtil; + +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; + +public class AuthRequestFactory { + public static HttpRequest unionLoginTGTRequest(String username, String password, String service) { + Map旧版的登录方式,既相当于直接登录bkjx系统
+ *注意,这种登录方式的密码和新版可能是不一样的
+ *不过不论使用哪种登录方式获取到的cookie都是可用的
+ * + * @return 获取到的Cookies + */ + public String getLoginCookieLegacy(String username, String password, RequestClientOption requestOption) throws IOException, BasicException { + // 获取某段神秘的dataStr(反正官网代码是这么叫的) + HttpRequest dataStringRequest = AuthRequestFactory.Legacy.dataStringRequest(); + HttpResponse dataStringResponse = requester.post(new URL(Bkjx.Legacy.BKJX_DATA_STRING_API), dataStringRequest, requestOption); + if (dataStringResponse.getBody() == null) { + throw new BasicException(); + } - HttpResponse unionAuthResponse = requester.post(new URL(JwcApiUrl.UNION_AUTH_API), TGTRequest, requestOption); + String dataString = new String(dataStringResponse.getBody()); - String redirectAddress = unionAuthResponse.getHeaders().get("Location"); - if (redirectAddress == null) { - throw new PasswordWornException(); + // 获取登录ticket + String encoded = PasswordEncoder.legacyPassword(username, password, dataString); + HttpRequest ticketRequest = AuthRequestFactory.Legacy.ticketRedirectRequest(encoded); + ticketRequest.setCookies(dataStringResponse.getCookies()); + HttpResponse ticketResponse = requester.post(new URL(Bkjx.Legacy.BKJX_SESSION_COOKIE_API), ticketRequest, requestOption); + if (ticketResponse.getBody() == null) { + throw new BasicException(); } - // 获取服务ticket(service ticket,st) - HttpRequest serviceTicketRequest = AuthRequestGenerator.loginTicketRequest(JwcApiUrl.JWC_SSO_SERVICE); + // 使用跳转得到的链接获取cookies + String sessionRedirect = ticketResponse.getHeaders().get("Location"); + if (sessionRedirect == null) { + throw new PasswordWornException(); + } + HttpRequest sessionRequest = AuthRequestFactory.Legacy.dataStringRequest(); - HttpResponse serviceTicketResponse = - requester.post(new URL(redirectAddress.replace("http:", "https:")), serviceTicketRequest, requestOption); + HttpResponse sessionResponse = requester.get(new URL(sessionRedirect), sessionRequest, requestOption); - byte[] serviceTicketResponseData = serviceTicketResponse.getBody(); - if (serviceTicketResponseData == null) { - System.err.println("获取服务st出错,serviceTicketResponseData == null"); + String cookies = sessionResponse.getCookies(); + if (roughCheckCookie(cookies)) { throw new BasicException(); } - String serviceTicket = new String(serviceTicketResponseData); + + return cookies; + } + + public String getLoginCookie(String username, String password, RequestClientOption requestOption) throws IOException, BasicException { + String serviceTicket = unionLogin.getServiceTicket(username, password, Bkjx.BKJX_SSO_SERVICE, requestOption); // 获取登录cookie(session) - HttpRequest sessionRequest = AuthRequestGenerator.sessionCookieRequest(); - HttpResponse sessionResponse = requester.get(new URL(String.format(JwcApiUrl.JWC_TICKET_API, serviceTicket)), sessionRequest, requestOption); + HttpRequest sessionRequest = AuthRequestFactory.sessionCookieRequest(); + HttpResponse sessionResponse = requester.get(new URL(String.format(Bkjx.BKJX_SESSION_COOKIE_API, serviceTicket)), sessionRequest, requestOption); String cookies = sessionResponse.getCookies(); - if (cookies == null || !cookies.contains("JSESSIONID") || !cookies.contains("SERVERID")) { - System.err.println("获取服务session cookie出错,关键Cookie缺失"); - System.err.println("Cookies: " + cookies); + if (roughCheckCookie(cookies)) { throw new BasicException(); } return cookies; } - private void getLoginGTG(String username, String password) { + private boolean roughCheckCookie(String cookies) throws BasicException { + return cookies == null || !cookies.contains("JSESSIONID") || !cookies.contains("SERVERID"); + } + + private static final int COOKIES_ERROR_RESPONSE_LENGTH = + ("").length(); + + + public boolean checkCookies(String cookies) throws IOException { + RequestClientOption option = new RequestClientOption(); + option.setTimeout(5); + option.setProxy(null); + option.setFallowUrlRedirect(false); + HttpRequest testRequest = AuthRequestFactory.sessionCookieRequest(); + testRequest.setCookies(cookies); + HttpResponse testResponse = requester.get(new URL(Bkjx.BKJX_TEST_API), testRequest, option); + + // 判断响应长度是否为178个字,登录跳转响应长度 + // 这种办法虽然在极端情况下可能会出错(而且还挺蠢的),但是是最快的办法中比较准确的了 + return testResponse.getBody().length != COOKIES_ERROR_RESPONSE_LENGTH; } + } diff --git a/mywust-network-okhttp/pom.xml b/mywust-network-okhttp/pom.xml index 0791490..a7459bc 100644 --- a/mywust-network-okhttp/pom.xml +++ b/mywust-network-okhttp/pom.xml @@ -43,6 +43,12 @@- 将生成的字节数组转换成16进制字符串,得到加密后的数据
*希望不要再改登录方式了 [拜]
+ * * @author : lensfrex * @description : 使用统一登陆的加密方法对密码进行加密的工具类 * @create : 2022-09-18 14:42:06 @@ -69,7 +72,7 @@ public class PasswordEncoder { // 完成加密 return Hex.encodeHexString(cipher.doFinal(reversedTextData)); - } catch (IllegalBlockSizeException | BadPaddingException e) { + } catch (IllegalBlockSizeException | BadPaddingException e) { // 会报错的地方都是因为算法不正确,这里都是固定的值,出错了就是程序写错了,在测试的时候就应该发现的问题 // 对于调用者不必处理这种异常,但是开发测试的时候应该要打日志来发现问题 System.err.println("加密出错"); @@ -101,4 +104,34 @@ public class PasswordEncoder { return null; } + + /** + *使用旧版登录页的方法加密密码(bkjx系统直接登录)
+ *是直接照搬官网js代码的,只是经过了一点小修改,没有具体研究是什么加密方式(大概率可能是自创的),性能可能会有点差
+ * + * @param dataString 加密前置字符串 + * @param password 密码明文 + * @return 加密后的密文 + */ + public static String legacyPassword(String username, String password, String dataString) { + String[] parts = dataString.split("#"); + String scode = parts[0]; + String sxh = parts[1]; + + String code = username + "%%%" + password; + + StringBuilder encoded = new StringBuilder(); + for (int i = 0, codeLength = code.length(); i < codeLength; i++) { + if (i < 20) { + int endIndex = parseInt(sxh.substring(i, i + 1)); + encoded.append(code.charAt(i)).append(scode, 0, endIndex); + scode = scode.substring(endIndex); + } else { + encoded.append(code.substring(i)); + i = codeLength; + } + } + + return encoded.toString(); + } } diff --git a/mywust-util/src/main/java/cn/linghang/mywust/util/StringUtil.java b/mywust-util/src/main/java/cn/linghang/mywust/util/StringUtil.java index 63af593..e260a24 100644 --- a/mywust-util/src/main/java/cn/linghang/mywust/util/StringUtil.java +++ b/mywust-util/src/main/java/cn/linghang/mywust/util/StringUtil.java @@ -27,11 +27,15 @@ public class StringUtil { * @return 解析后可用的Cookie */ public static String parseCookie(List