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); + } +}