v1.2.50:辽宁工程技术大学(lntu)登录适配——强制走 CAS + 滑块验证码——① 根因:平台接口返回 joinType=CLOUD(IsIap=true),但网关 /iap/doLogin 不存在(HTTP 405),IAP 账密提交必然失败并返回 FAIL_UPNOTMATCH("用户名或密码不匹配",误导用户以为密码错误);真实链路为 fdm.lntu.edu.cn/iap 302 → 学校 CAS(authserver.lntu.edu.cn/authserver/login)→ 账密 + 验证码 → 回调 /iap/loginSuccess?sessionToken=...&ticket=... 建立网关会话(cookie 落在 fdm.lntu.edu.cn,与 wec-* 任务接口同域);② 修复:新增 login_lntu.go 的 lntuCasLogin(同 xcu 模式,用 /iap 网关入口覆盖 ampUrl 后走通用 CAS 流程),CpdailyLogin 中对 LntuSchoolId 特判强制走 CAS;③ 验证码类型:抓取登录页 JS 确认 captchaSwitch="2" 且 startLogin 走 createSliderCaptcha()(wisedu 定制滑块,非图形验证码),协议与华侨大学完全同源(openSliderCaptcha.htl 取图、sign=AES(JSON.stringify({canvasLength,moveLength,tracks}), 小图 base64 末16字节) 提交 verifySliderCaptcha.htl、errorCode==1 通过),故仅需将 lntu 加入 sliderIds 配置即可复用现有滑块实现(config.toml.example 同步更新);④ 端到端实测:真实待适配账号两次登录均一次通过(gapX=92/149 首候选 offset=0 直接命中 errorCode=1),CAS 302 → 6 cookies(CASTGC/JSESSIONID/MOD_AUTH_CAS/happyVoyage/route/platformMultilingual)→ FetchUserProfile 会话有效;⑤ 按用户要求回滚此前 LoginUrl(忘记密码地址)改动——原地址打不开是因为走了 IAP 入口,改走 CAS 后浏览器可直接打开,无需特殊处理;⑥ 新增 login_lntu_test.go(入口计算 7 用例 + ampUrl 覆盖 + 协议常量),go test ./...、go vet 全过
ci/woodpecker/tag/build Pipeline was successful
ci/woodpecker/tag/build Pipeline was successful
This commit is contained in:
@@ -31,7 +31,7 @@ length = 4
|
|||||||
appVersion = "9.8.8"
|
appVersion = "9.8.8"
|
||||||
systemVersion = "iOS 17"
|
systemVersion = "iOS 17"
|
||||||
calVersion = "first_v4"
|
calVersion = "first_v4"
|
||||||
sliderIds = ["1018764144934965", "sdhyxy", "hqdx"]
|
sliderIds = ["1018764144934965", "sdhyxy", "hqdx", "lntu"]
|
||||||
captchaBaseUrl = "http://127.0.0.1:8890"
|
captchaBaseUrl = "http://127.0.0.1:8890"
|
||||||
captchaApiKey = "your_captcha_api_key"
|
captchaApiKey = "your_captcha_api_key"
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package cpdaily
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ========== 辽宁工程技术大学(lntu)单独适配 ==========
|
||||||
|
|
||||||
|
// LntuSchoolId 辽宁工程技术大学 tenant id
|
||||||
|
const LntuSchoolId = "lntu"
|
||||||
|
|
||||||
|
// lntuCasLoginEntry 计算辽宁工程技术大学 CAS 登录入口:
|
||||||
|
// 网关域 fdm.lntu.edu.cn 的 /iap(school.IdsUrl)会 302 到学校 CAS
|
||||||
|
// (authserver.lntu.edu.cn/authserver/login),service 参数携带网关回调
|
||||||
|
// /iap/loginSuccess?sessionToken=...;走完 CAS 账密后回调该地址,
|
||||||
|
// cookie 才能正确落在 fdm.lntu.edu.cn 域(wec-* 任务接口所在域)。
|
||||||
|
func lntuCasLoginEntry(school *SchoolDetailInfo) string {
|
||||||
|
if school == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
if school.IdsUrl != "" && strings.Contains(school.IdsUrl, "/iap") {
|
||||||
|
return school.IdsUrl
|
||||||
|
}
|
||||||
|
if school.WecloudUrl != "" {
|
||||||
|
return strings.TrimSuffix(school.WecloudUrl, "/") + "/iap"
|
||||||
|
}
|
||||||
|
return school.AmpUrl
|
||||||
|
}
|
||||||
|
|
||||||
|
// lntuCasLogin 辽宁工程技术大学强制走 CAS:
|
||||||
|
// 平台接口返回 joinType=CLOUD(IsIap()=true),但网关 /iap/doLogin 不存在(HTTP 405),
|
||||||
|
// IAP 账密提交必然失败并报 FAIL_UPNOTMATCH("用户名或密码不匹配",误导用户);
|
||||||
|
// 实际链路为 IAP 入口(fdm.lntu.edu.cn/iap)302 → 学校 CAS 账密 → 回调
|
||||||
|
// /iap/loginSuccess 建立网关会话。因此强制走 CAS 流程(同许昌学院 xcu 模式)。
|
||||||
|
func lntuCasLogin(ctx context.Context, school *SchoolDetailInfo, username, password string) ([]*http.Cookie, *MFAContext, error) {
|
||||||
|
lntu := *school
|
||||||
|
if entry := lntuCasLoginEntry(school); entry != "" {
|
||||||
|
lntu.AmpUrl = entry
|
||||||
|
}
|
||||||
|
return casLogin(ctx, &lntu, username, password)
|
||||||
|
}
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
package cpdaily
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
// TestLntuCasLoginEntry 覆盖辽宁工程技术大学 CAS 登录入口计算逻辑
|
||||||
|
func TestLntuCasLoginEntry(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
school *SchoolDetailInfo
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "正常idsUrl(/iap 网关入口)",
|
||||||
|
school: &SchoolDetailInfo{IdsUrl: "https://fdm.lntu.edu.cn/iap"},
|
||||||
|
want: "https://fdm.lntu.edu.cn/iap",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "idsUrl缺失回退wecloud+/iap",
|
||||||
|
school: &SchoolDetailInfo{WecloudUrl: "https://fdm.lntu.edu.cn"},
|
||||||
|
want: "https://fdm.lntu.edu.cn/iap",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "wecloud带尾斜杠",
|
||||||
|
school: &SchoolDetailInfo{WecloudUrl: "https://fdm.lntu.edu.cn/"},
|
||||||
|
want: "https://fdm.lntu.edu.cn/iap",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "idsUrl非iap路径回退wecloud",
|
||||||
|
school: &SchoolDetailInfo{IdsUrl: "https://ids.lntu.edu.cn/authserver", WecloudUrl: "https://fdm.lntu.edu.cn"},
|
||||||
|
want: "https://fdm.lntu.edu.cn/iap",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "均缺失回退ampUrl",
|
||||||
|
school: &SchoolDetailInfo{AmpUrl: "https://fdm.lntu.edu.cn/wec-portal-mobile/client"},
|
||||||
|
want: "https://fdm.lntu.edu.cn/wec-portal-mobile/client",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "均为空",
|
||||||
|
school: &SchoolDetailInfo{},
|
||||||
|
want: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "nil学校",
|
||||||
|
school: nil,
|
||||||
|
want: "",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, c := range cases {
|
||||||
|
t.Run(c.name, func(t *testing.T) {
|
||||||
|
if got := lntuCasLoginEntry(c.school); got != c.want {
|
||||||
|
t.Errorf("lntuCasLoginEntry() = %q, want %q", got, c.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestLntuCasLoginEntryOverridesAmpUrl 确认 lntuCasLogin 用网关入口覆盖 ampUrl 后走 CAS 流程
|
||||||
|
func TestLntuCasLoginEntryOverridesAmpUrl(t *testing.T) {
|
||||||
|
school := &SchoolDetailInfo{
|
||||||
|
ID: LntuSchoolId,
|
||||||
|
Name: "辽宁工程技术大学",
|
||||||
|
JoinType: "CLOUD", // 平台标记为 CLOUD,但 IAP 不可用,需强制 CAS
|
||||||
|
IdsUrl: "https://fdm.lntu.edu.cn/iap",
|
||||||
|
WecloudUrl: "https://fdm.lntu.edu.cn",
|
||||||
|
AmpUrl: "https://fdm.lntu.edu.cn/wec-portal-mobile/client",
|
||||||
|
}
|
||||||
|
// 模拟 lntuCasLogin 内部的入口覆盖(不发起网络请求)
|
||||||
|
clone := *school
|
||||||
|
clone.AmpUrl = lntuCasLoginEntry(school)
|
||||||
|
if got := getCasValidLoginUrl(&clone); got != "https://fdm.lntu.edu.cn/iap" {
|
||||||
|
t.Errorf("getCasValidLoginUrl() = %q, want 网关 /iap 入口", got)
|
||||||
|
}
|
||||||
|
// 原对象不被修改
|
||||||
|
if school.AmpUrl != "https://fdm.lntu.edu.cn/wec-portal-mobile/client" {
|
||||||
|
t.Errorf("原 school 对象 ampUrl 被意外修改: %q", school.AmpUrl)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestLntuIsSliderCaptchaSchool 确认 lntu 归入滑块验证码学校需通过配置声明
|
||||||
|
// (captchaSwitch=2 + createSliderCaptcha → /common/toSliderCaptcha.htl,与 hqdx 同协议)
|
||||||
|
func TestLntuSliderProtocolSameAsHqu(t *testing.T) {
|
||||||
|
// 协议一致性由配置驱动:lntu 与 hqdx 共用 resolveWiseduSliderCaptcha
|
||||||
|
//(openSliderCaptcha.htl 取图 → 打码 → verifySliderCaptcha.htl sign 提交)。
|
||||||
|
// 此处仅断言常量存在且不与其他学校 ID 冲突。
|
||||||
|
if LntuSchoolId == "" || LntuSchoolId != "lntu" {
|
||||||
|
t.Errorf("LntuSchoolId = %q, want lntu", LntuSchoolId)
|
||||||
|
}
|
||||||
|
if HquSchoolId == LntuSchoolId {
|
||||||
|
t.Errorf("lntu 与 hqdx 学校 ID 冲突")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -176,6 +176,12 @@ func CpdailyLogin(ctx context.Context, school *SchoolDetailInfo, username, passw
|
|||||||
if school.ID == XcuSchoolId {
|
if school.ID == XcuSchoolId {
|
||||||
return xcuCasLogin(ctx, school, username, password)
|
return xcuCasLogin(ctx, school, username, password)
|
||||||
}
|
}
|
||||||
|
// 辽宁工程技术大学:平台配置 joinType=CLOUD(IsIap=true),但网关 /iap/doLogin 不存在(405),
|
||||||
|
// IAP 提交报 FAIL_UPNOTMATCH 误导为"密码错误";实际链路为 /iap 302 → 学校 CAS(authserver.lntu.edu.cn)
|
||||||
|
// → 回调 /iap/loginSuccess 建立网关会话,同 xcu 模式单独强制走 CAS(详见 login_lntu.go)
|
||||||
|
if school.ID == LntuSchoolId {
|
||||||
|
return lntuCasLogin(ctx, school, username, password)
|
||||||
|
}
|
||||||
// 华侨大学:写死强制走通用 CAS 流程——CAS 账密登录需 wisedu 定制滑块验证码
|
// 华侨大学:写死强制走通用 CAS 流程——CAS 账密登录需 wisedu 定制滑块验证码
|
||||||
//(casLogin 内按 sliderIds 配置分流,见 slider.go),IAP 账密接口不兼容(FAIL_UPNOTMATCH)
|
//(casLogin 内按 sliderIds 配置分流,见 slider.go),IAP 账密接口不兼容(FAIL_UPNOTMATCH)
|
||||||
if school.ID == HquSchoolId {
|
if school.ID == HquSchoolId {
|
||||||
|
|||||||
Reference in New Issue
Block a user