diff --git a/mywust-core/pom.xml b/mywust-core/pom.xml
index 80d0fda..d554722 100644
--- a/mywust-core/pom.xml
+++ b/mywust-core/pom.xml
@@ -17,6 +17,17 @@
0.0.1-dev
compile
+
+ cn.linghang
+ mywust-util
+ 0.0.1-dev
+
+
+
+ net.sourceforge.jregex
+ jregex
+ 1.2_01
+
@@ -24,5 +35,4 @@
11
UTF-8
-
\ No newline at end of file
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
new file mode 100644
index 0000000..68b25e9
--- /dev/null
+++ b/mywust-core/src/main/java/cn/linghang/mywust/core/api/JwcApiUrl.java
@@ -0,0 +1,15 @@
+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/exception/BasicException.java b/mywust-core/src/main/java/cn/linghang/mywust/core/exception/BasicException.java
new file mode 100644
index 0000000..c894996
--- /dev/null
+++ b/mywust-core/src/main/java/cn/linghang/mywust/core/exception/BasicException.java
@@ -0,0 +1,4 @@
+package cn.linghang.mywust.core.exception;
+
+public class BasicException extends Exception {
+}
diff --git a/mywust-core/src/main/java/cn/linghang/mywust/core/exception/PasswordWornException.java b/mywust-core/src/main/java/cn/linghang/mywust/core/exception/PasswordWornException.java
new file mode 100644
index 0000000..09b358e
--- /dev/null
+++ b/mywust-core/src/main/java/cn/linghang/mywust/core/exception/PasswordWornException.java
@@ -0,0 +1,4 @@
+package cn.linghang.mywust.core.exception;
+
+public class PasswordWornException extends BasicException {
+}
diff --git a/mywust-core/src/main/java/cn/linghang/mywust/core/service/undergraduate/AuthRequestGenerator.java b/mywust-core/src/main/java/cn/linghang/mywust/core/service/undergraduate/AuthRequestGenerator.java
new file mode 100644
index 0000000..eeb2dc9
--- /dev/null
+++ b/mywust-core/src/main/java/cn/linghang/mywust/core/service/undergraduate/AuthRequestGenerator.java
@@ -0,0 +1,41 @@
+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 AuthRequestGenerator {
+ public static HttpRequest unionLoginTGTRequest(String username, String password, String service) {
+ Map requestForm = new HashMap<>();
+ requestForm.put("username", username);
+ requestForm.put("password", password);
+ requestForm.put("service", service);
+ requestForm.put("loginType", "");
+
+ String queryString = StringUtil.generateQueryString(requestForm);
+
+ HttpRequest request = new HttpRequest();
+ request.setData(queryString.getBytes(StandardCharsets.UTF_8));
+
+ return request;
+ }
+
+ public static HttpRequest loginTicketRequest(String service) {
+ Map requestForm = new HashMap<>();
+ requestForm.put("service", service);
+
+ String queryString = StringUtil.generateQueryString(requestForm);
+
+ HttpRequest request = new HttpRequest();
+ request.setData(queryString.getBytes(StandardCharsets.UTF_8));
+
+ return request;
+ }
+
+ public static HttpRequest sessionCookieRequest() {
+ return new HttpRequest();
+ }
+}
diff --git a/mywust-core/src/main/java/cn/linghang/mywust/core/service/undergraduate/JwcLogin.java b/mywust-core/src/main/java/cn/linghang/mywust/core/service/undergraduate/JwcLogin.java
index 07db51f..34e9f20 100644
--- a/mywust-core/src/main/java/cn/linghang/mywust/core/service/undergraduate/JwcLogin.java
+++ b/mywust-core/src/main/java/cn/linghang/mywust/core/service/undergraduate/JwcLogin.java
@@ -1,7 +1,65 @@
package cn.linghang.mywust.core.service.undergraduate;
+import cn.linghang.mywust.core.api.JwcApiUrl;
+import cn.linghang.mywust.core.exception.BasicException;
+import cn.linghang.mywust.core.exception.PasswordWornException;
+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 java.io.IOException;
+import java.net.URL;
+
public class JwcLogin {
- public String getLoginCookie(String user, String password) {
- return "";
+ // 默认5秒超时
+ private static final int DEFAULT_TIMEOUT = 5;
+
+ private final Requester requester;
+
+ public JwcLogin(Requester requester) {
+ this.requester = requester;
+ }
+
+ public String getLoginCookie(String username, String password, RequestClientOption requestOption) throws IOException, BasicException {
+ // 获取ticket granting ticket(TGT)
+ HttpRequest TGTRequest = AuthRequestGenerator.unionLoginTGTRequest(username, password, JwcApiUrl.JWC_SSO_SERVICE);
+
+ HttpResponse unionAuthResponse = requester.post(new URL(JwcApiUrl.UNION_AUTH_API), TGTRequest, requestOption);
+
+ String redirectAddress = unionAuthResponse.getHeaders().get("Location");
+ if (redirectAddress == null) {
+ throw new PasswordWornException();
+ }
+
+ // 获取服务ticket(service ticket,st)
+ HttpRequest serviceTicketRequest = AuthRequestGenerator.loginTicketRequest(JwcApiUrl.JWC_SSO_SERVICE);
+
+ HttpResponse serviceTicketResponse =
+ requester.post(new URL(redirectAddress.replace("http:", "https:")), serviceTicketRequest, requestOption);
+
+ byte[] serviceTicketResponseData = serviceTicketResponse.getBody();
+ if (serviceTicketResponseData == null) {
+ System.err.println("获取服务st出错,serviceTicketResponseData == null");
+ throw new BasicException();
+ }
+ String serviceTicket = new String(serviceTicketResponseData);
+
+ // 获取登录cookie(session)
+ HttpRequest sessionRequest = AuthRequestGenerator.sessionCookieRequest();
+ HttpResponse sessionResponse = requester.get(new URL(String.format(JwcApiUrl.JWC_TICKET_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);
+ throw new BasicException();
+ }
+
+ return cookies;
+ }
+
+ private void getLoginGTG(String username, String password) {
+
}
}
diff --git a/mywust-network-okhttp/pom.xml b/mywust-network-okhttp/pom.xml
index 685aeb6..0791490 100644
--- a/mywust-network-okhttp/pom.xml
+++ b/mywust-network-okhttp/pom.xml
@@ -48,6 +48,12 @@
mywust-network
0.0.1-dev
+
+ cn.linghang
+ mywust-util
+ 0.0.1-dev
+ compile
+
\ No newline at end of file
diff --git a/mywust-network-okhttp/src/main/java/cn/linghang/mywust/network/okhttp/SimpleOkhttpRequester.java b/mywust-network-okhttp/src/main/java/cn/linghang/mywust/network/okhttp/SimpleOkhttpRequester.java
index 940a70e..cc52cae 100644
--- a/mywust-network-okhttp/src/main/java/cn/linghang/mywust/network/okhttp/SimpleOkhttpRequester.java
+++ b/mywust-network-okhttp/src/main/java/cn/linghang/mywust/network/okhttp/SimpleOkhttpRequester.java
@@ -4,6 +4,7 @@ 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.StringUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import okhttp3.*;
@@ -15,6 +16,7 @@ import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.TreeMap;
import java.util.concurrent.TimeUnit;
/**
@@ -105,7 +107,9 @@ public class SimpleOkhttpRequester implements Requester {
private OkHttpClient buildClient(OkHttpClient.Builder builder, RequestClientOption requestClientOption) {
builder.callTimeout(requestClientOption.getTimeout(), TimeUnit.SECONDS)
.readTimeout(requestClientOption.getTimeout(), TimeUnit.SECONDS)
- .connectTimeout(requestClientOption.getTimeout(), TimeUnit.SECONDS);
+ .connectTimeout(requestClientOption.getTimeout(), TimeUnit.SECONDS)
+ .sslSocketFactory(TrustAllCert.getSSLSocketFactory(), TrustAllCert.getX509TrustManager())
+ .hostnameVerifier(TrustAllCert.getHostnameVerifier());
// 设置代理
RequestClientOption.Proxy proxyOption = requestClientOption.getProxy();
@@ -159,8 +163,12 @@ public class SimpleOkhttpRequester implements Requester {
*/
private MediaType getMediaType(HttpRequest httpRequest) {
// 按照指定的Content-Type设置MediaType,不指定的话按form处理
- String contentType = httpRequest.getHeaders().get("Content-Type");
- MediaType mediaType;
+ Map headers = httpRequest.getHeaders();
+ if (headers == null) {
+ return MediaType.get(DEFAULT_CONTENT_TYPE);
+ }
+
+ String contentType = headers.get("Content-Type");
if (!"".equals(contentType)) {
return MediaType.get(contentType);
} else {
@@ -180,9 +188,14 @@ public class SimpleOkhttpRequester implements Requester {
Request.Builder requestBuilder = new Request.Builder().url(url);
Map requestHeaders = httpRequest.getHeaders();
- requestHeaders.forEach(requestBuilder::header);
+ if (requestHeaders != null) {
+ requestHeaders.forEach(requestBuilder::header);
+ }
- requestBuilder.header("Cookie", httpRequest.getCookies());
+ String requestCookie = httpRequest.getCookies();
+ if (requestCookie != null) {
+ requestBuilder.header("Cookie", requestCookie);
+ }
if (requestMethod == RequestMethod.GET) {
requestBuilder.get();
@@ -218,7 +231,7 @@ public class SimpleOkhttpRequester implements Requester {
// 取所有的响应头,如果同一个响应头字段有多个值只拿第一个
Map> responseHeaders = response.headers().toMultimap();
- Map headerMap = new HashMap<>();
+ Map headerMap = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
responseHeaders.forEach((k, v) -> headerMap.put(k, v.get(0)));
httpResponse.setHeaders(headerMap);
@@ -229,7 +242,9 @@ public class SimpleOkhttpRequester implements Requester {
httpResponse.setBody(body.bytes());
}
- httpResponse.setCookies(response.header("Set-Cookie"));
+ // 提取Cookie
+ List cookieHeaders = response.headers("Set-Cookie");
+ httpResponse.setCookies(StringUtil.parseCookie(cookieHeaders));
return httpResponse;
}
diff --git a/mywust-network-okhttp/src/main/java/cn/linghang/mywust/network/okhttp/TrustAllCert.java b/mywust-network-okhttp/src/main/java/cn/linghang/mywust/network/okhttp/TrustAllCert.java
new file mode 100644
index 0000000..3e14a13
--- /dev/null
+++ b/mywust-network-okhttp/src/main/java/cn/linghang/mywust/network/okhttp/TrustAllCert.java
@@ -0,0 +1,59 @@
+package cn.linghang.mywust.network.okhttp;
+
+import javax.net.ssl.*;
+import java.security.KeyStore;
+import java.security.SecureRandom;
+import java.security.cert.X509Certificate;
+import java.util.Arrays;
+
+public class TrustAllCert {
+ public static SSLSocketFactory getSSLSocketFactory() {
+ try {
+ SSLContext sslContext = SSLContext.getInstance("SSL");
+ sslContext.init(null, getTrustManager(), new SecureRandom());
+ return sslContext.getSocketFactory();
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static TrustManager[] getTrustManager() {
+ return new TrustManager[]{
+ new X509TrustManager() {
+ @Override
+ public void checkClientTrusted(X509Certificate[] chain, String authType) {
+ }
+
+ @Override
+ public void checkServerTrusted(X509Certificate[] chain, String authType) {
+ }
+
+ @Override
+ public X509Certificate[] getAcceptedIssuers() {
+ return new X509Certificate[]{};
+ }
+ }
+ };
+ }
+
+ public static HostnameVerifier getHostnameVerifier() {
+ return (s, sslSession) -> true;
+ }
+
+ public static X509TrustManager getX509TrustManager() {
+ X509TrustManager trustManager = null;
+ try {
+ TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
+ trustManagerFactory.init((KeyStore) null);
+ TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
+ if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
+ throw new IllegalStateException("Unexpected default trust managers:" + Arrays.toString(trustManagers));
+ }
+ trustManager = (X509TrustManager) trustManagers[0];
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ return trustManager;
+ }
+}
\ No newline at end of file
diff --git a/mywust-network/src/main/java/cn/linghang/mywust/network/RequestClientOption.java b/mywust-network/src/main/java/cn/linghang/mywust/network/RequestClientOption.java
index 28fe880..07454b3 100644
--- a/mywust-network/src/main/java/cn/linghang/mywust/network/RequestClientOption.java
+++ b/mywust-network/src/main/java/cn/linghang/mywust/network/RequestClientOption.java
@@ -6,6 +6,7 @@ import lombok.Data;
public class RequestClientOption {
private Proxy proxy;
private long timeout;
+ private boolean fallowUrlRedirect = false;
@Data
public static class Proxy {
diff --git a/mywust-test/pom.xml b/mywust-test/pom.xml
new file mode 100644
index 0000000..166ac2e
--- /dev/null
+++ b/mywust-test/pom.xml
@@ -0,0 +1,31 @@
+
+
+ 4.0.0
+
+ cn.linghang
+ mywust-test
+ 1.0-SNAPSHOT
+
+
+ cn.linghang
+ mywust-core
+ 0.0.1-dev
+ test
+
+
+ cn.linghang
+ mywust-network-okhttp
+ 1.0-SNAPSHOT
+ test
+
+
+
+
+ 11
+ 11
+ UTF-8
+
+
+
\ No newline at end of file
diff --git a/mywust-test/src/test/java/JwcLoginTest.java b/mywust-test/src/test/java/JwcLoginTest.java
new file mode 100644
index 0000000..8d42323
--- /dev/null
+++ b/mywust-test/src/test/java/JwcLoginTest.java
@@ -0,0 +1,39 @@
+import cn.linghang.mywust.core.exception.BasicException;
+import cn.linghang.mywust.core.service.undergraduate.JwcLogin;
+import cn.linghang.mywust.network.RequestClientOption;
+import cn.linghang.mywust.network.okhttp.SimpleOkhttpRequester;
+import cn.linghang.mywust.util.PasswordEncoder;
+
+import java.io.IOException;
+import java.util.Scanner;
+
+public class JwcLoginTest {
+ public static void main(String[] args) throws BasicException, IOException {
+ new JwcLoginTest().run();
+ }
+
+ private void run() throws BasicException, IOException {
+ System.out.println("输入账号(学号)和密码,用“ ”(空格)分割");
+
+ Scanner scanner = new Scanner(System.in);
+
+ String input = scanner.nextLine();
+
+ String username = input.split(" ")[0];
+ String password = input.split(" ")[1];
+
+ System.out.println("账号:" + username);
+ System.out.println("密码:" + password);
+
+ JwcLogin jwcLogin = new JwcLogin(new SimpleOkhttpRequester());
+
+ RequestClientOption option = new RequestClientOption();
+ option.setTimeout(5);
+ option.setProxy(null);
+ option.setFallowUrlRedirect(false);
+
+ String cookies = jwcLogin.getLoginCookie(username, PasswordEncoder.encodePassword(password), option);
+
+ System.out.println(cookies);
+ }
+}
diff --git a/mywust-util/pom.xml b/mywust-util/pom.xml
index aed53ac..43cee42 100644
--- a/mywust-util/pom.xml
+++ b/mywust-util/pom.xml
@@ -23,5 +23,12 @@
commons-codec
1.15
+
+
+
+ com.google.guava
+ guava
+ 31.1-jre
+
\ No newline at end of file
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
new file mode 100644
index 0000000..63af593
--- /dev/null
+++ b/mywust-util/src/main/java/cn/linghang/mywust/util/StringUtil.java
@@ -0,0 +1,37 @@
+package cn.linghang.mywust.util;
+
+import com.google.common.base.Joiner;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+public class StringUtil {
+ /**
+ * 将map转换成url请求表单格式的请求字符串(类似于 user=admin&passwd=123456&time=11111 这种)
+ *
+ * @param queryParams 请求参数
+ * @return 生成的表单请求字符串
+ */
+ public static String generateQueryString(Map queryParams) {
+ return Joiner.on('&')
+ .useForNull("")
+ .withKeyValueSeparator('=')
+ .join(queryParams);
+ }
+
+ /**
+ * 解析服务器相应的Set-Cookie的header值,并拼成能够直接用于Cookie的header值
+ *
+ * @param cookieHeaders 服务器响应的Set-Cookie
+ * @return 解析后可用的Cookie
+ */
+ public static String parseCookie(List cookieHeaders) {
+ List allCookies = new ArrayList<>(cookieHeaders.size());
+ cookieHeaders.forEach((cookieHeader) -> allCookies.add(cookieHeader.split(";")[0]));
+
+ return Joiner.on(';')
+ .useForNull("")
+ .join(allCookies);
+ }
+}