From ed635e0127edd10b245e8b7ce054634100d99f38 Mon Sep 17 00:00:00 2001 From: ozline Date: Tue, 31 Dec 2024 13:24:35 +0800 Subject: [PATCH] feat: support jwch user login --- app/(tabs)/user.tsx | 187 ++++++++++++++---- modules/native-request/index.ts | 8 +- .../ios/NativeRequestModule.swift | 22 ++- 3 files changed, 175 insertions(+), 42 deletions(-) diff --git a/app/(tabs)/user.tsx b/app/(tabs)/user.tsx index 607ae76a..2c45c063 100644 --- a/app/(tabs)/user.tsx +++ b/app/(tabs)/user.tsx @@ -1,29 +1,62 @@ import { Buffer } from 'buffer'; -import React, { useState } from 'react'; -import { Alert, Button, Image, Text, View } from 'react-native'; +import React, { useRef, useState } from 'react'; +import { Alert, Button, Image, Text, TextInput, View } from 'react-native'; import { ThemedView } from '@/components/ThemedView'; import { get, post } from '@/modules/native-request'; +const URLS = { + LOGIN_CHECK: 'https://jwcjwxt1.fzu.edu.cn/logincheck.asp', + VERIFY_CODE: 'https://jwcjwxt1.fzu.edu.cn/plus/verifycode.asp', + SSO_LOGIN: 'https://jwcjwxt2.fzu.edu.cn/Sfrz/SSOLogin', + LOGIN_CHECK_XS: 'https://jwcjwxt2.fzu.edu.cn:81/loginchk_xs.aspx', + GET: 'https://www.baidu.com', +}; + +const HEADERS = { + REFERER: 'https://jwch.fzu.edu.cn', + ORIGIN: 'https://jwch.fzu.edu.cn', +}; + +const STUDENT = { + ID: 'student-id', + PASSWORD: 'student-password-md5-16', +}; + +interface Cookie { + [key: string]: string; +} + +let cookies: Cookie = {}; + export default function HomePage() { const [dictionary, setDictionary] = useState<{ [key: string]: string }>({}); const [imageUrl, setImageUrl] = useState(null); // 用于显示验证码图片 + const [captcha, setCaptcha] = useState(''); // 用于输入验证码 + + const updateCookies = (newCookies: string) => { + const cookieArray = newCookies.split(',').map(cookie => cookie.trim()); + const updatedCookies: { [key: string]: string } = { ...cookies }; - const urlLoginCheck = 'https://jwcjwxt1.fzu.edu.cn/logincheck.asp'; - const urlVerifyCode = 'https://jwcjwxt1.fzu.edu.cn/plus/verifycode.asp'; - const urlGet = 'https://www.baidu.com'; - const headers = { - Referer: 'https://jwch.fzu.edu.cn', - Origin: 'https://jwch.fzu.edu.cn', - Cookie: 'ASPSESSIONIDAGRSTDCC=JADPAJABIJOFHMALKENMNHCP', + cookieArray.forEach(cookie => { + const [key, value] = cookie.split(';')[0].split('='); + if (updatedCookies[key]) { + updatedCookies[key] = value; // 更新现有的 cookie + } else { + updatedCookies[key] = value; // 添加新的 cookie + } + }); + cookies = updatedCookies; + console.log('Updated cookies:', updatedCookies); + // 处理后的 cookies可能长这样 + // {"ASPSESSIONIDCGTRTCDD": "PDHEKIDAJILFAFPPHEDEPDKP", "Learun_ADMS_V7_Mark": "eac94f18-be04-4e74-81cd-96fa6b16251d", "Learun_ADMS_V7_Token": "a25f83b3-6147-4e4e-9d77-e718e0aa83c2"} }; - const formData = { - Verifycode: '111', - muser: 'student-id', - passwd: 'student-password', + + const handleError = (error: any) => { + Alert.alert('错误', String(error)); }; - const handlePress = async ( + const requestPOST = async ( url: string, headers: Record, formData: Record, @@ -35,18 +68,16 @@ export default function HomePage() { headers: respHeaders, } = await post(url, headers, formData); setDictionary(respHeaders); - Alert.alert( - '结果', - respStatus + - '\n' + - JSON.stringify(Buffer.from(respData).toString('utf-8')), // 这里默认了 PSOT 返回的是 JSON 数据 - ); + if (respHeaders['Set-Cookie']) { + updateCookies(respHeaders['Set-Cookie']); + } + return respData; } catch (error) { - Alert.alert('错误', String(error)); + handleError(error); } }; - const handlePressGet = async ( + const requestGET = async ( url: string, headers: Record, isBinary = false, // 是否为二进制数据,如果是的话转为 base64输出(只是测试,我们认为二进制数据就是图片) @@ -58,19 +89,87 @@ export default function HomePage() { headers: respHeaders, } = await get(url, headers); setDictionary(respHeaders); - // 根据 Content-Type 处理响应数据(可能需要内置一个映射表?) + if (respHeaders['Set-Cookie']) { + updateCookies(respHeaders['Set-Cookie']); + } if (isBinary) { - // 图片 - const base64Data = btoa(String.fromCharCode(...respData)); - const imageUrl = `data:image/png;base64,${base64Data}`; - setImageUrl(imageUrl); // 保存图片 URL 到状态 - } else { - // 其他(默认为文本) - const responseData = Buffer.from(respData).toString('utf-8'); // 使用 Buffer 解码 - Alert.alert('结果', respStatus + '\n' + responseData); + setImageUrl( + `data:image/png;base64,${btoa(String.fromCharCode(...respData))}`, + ); } + return respData; } catch (error) { - Alert.alert('错误', String(error)); + handleError(error); + } + }; + + const handleSubmitCaptcha = async () => { + if (!captcha) { + Alert.alert('错误', '请输入验证码'); + return; + } + // Login Check + const respdata = await requestPOST( + URLS.LOGIN_CHECK, + { + ...HEADERS, + Cookie: Object.entries(cookies) + .map(([k, v]) => `${k}=${v}`) + .join('; '), + }, + { + muser: STUDENT.ID, + passwd: STUDENT.PASSWORD, + Verifycode: captcha, + }, + ); + // 我们禁用了 302 重定向,因此这个提取是从 URL 中提取的(但注意到教务处的 HTTP 报文 Body 也提供了 URL,我们从 Body 中入手,不走 Header 的 Location 字段) + var dataStr = Buffer.from(respdata).toString('utf-8').replace(/\s+/g, ''); + const tokenMatch = /token=(.*?)&/.exec(dataStr); + const idMatch = /id=(.*?)&/.exec(dataStr); + const numMatch = /num=(.*?)&/.exec(dataStr); + if (!tokenMatch) { + Alert.alert('错误', '缺失 Token'); + return; + } + const token = tokenMatch[1]; + const id = idMatch ? idMatch[1] : ''; + const num = numMatch ? numMatch[1] : ''; + console.log('Token:', token, 'ID:', id, 'Num:', num); + + // SSOLogin + const respSSOData = await requestPOST( + URLS.SSO_LOGIN, + { + 'X-Requested-With': 'XMLHttpRequest', + }, + { + token: token, + }, + ); + // 正常响应应当为:{"code":200,"info":"登录成功","data":{}} + dataStr = Buffer.from(respSSOData).toString('utf-8').replace(/\s+/g, ''); + + // account conflict 是 400,正常则是 200 + if (JSON.parse(dataStr).code == 200) { + const updatedCookies = Object.entries(cookies) + .map(([k, v]) => `${k}=${v}`) + .join('; '); + // LoginCheckXS + const respCookiesData = await requestGET( + URLS.LOGIN_CHECK_XS + + `?id=${id}&num=${num}&ssourl=https://jwcjwxt2.fzu.edu.cn&hosturl=https://jwcjwxt2.fzu.edu.cn:81&ssologin=`, + { + ...HEADERS, + Cookie: updatedCookies, + }, + ); + // 后续会在 Response Header 中获取 Cookie,这个是我们所需的 + const dataStr = Buffer.from(respCookiesData) + .toString('binary') + .replace(/\s+/g, ''); + const idMatch = /id=(.*?)&/.exec(dataStr); + console.log('ID:', idMatch ? idMatch[1] : ''); } }; @@ -80,15 +179,33 @@ export default function HomePage() { User