🐛 修复淮南联合大学登录功能
This commit is contained in:
+2
-2
@@ -19,8 +19,8 @@ balanceMigrateUrl = "http://localhost:3200/balance-migrate/"
|
|||||||
mode = "dev"
|
mode = "dev"
|
||||||
|
|
||||||
[captcha]
|
[captcha]
|
||||||
baseUrl = "http://captcha-resolver.out"
|
#baseUrl = "http://captcha-resolver.out"
|
||||||
#baseUrl = "http://localhost:8890"
|
baseUrl = "http://localhost:8890"
|
||||||
[redis]
|
[redis]
|
||||||
url = "redis://:jhkdjhkjdhsIUTYURTU_pEGmFP@database.out:56379"
|
url = "redis://:jhkdjhkjdhsIUTYURTU_pEGmFP@database.out:56379"
|
||||||
|
|
||||||
|
|||||||
@@ -51,6 +51,24 @@ func ApiAutoOCRVerification(bytes []byte) string {
|
|||||||
result := post.String()
|
result := post.String()
|
||||||
return gjson.Get(result, "data").String()
|
return gjson.Get(result, "data").String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ApiAutoCalcVerification(b64 string) string {
|
||||||
|
ctx := gctx.GetInitCtx()
|
||||||
|
baseUrl := g.Config().MustGetWithEnv(ctx, cst.ConfigCaptchaBaseUrl).String() + "/calc"
|
||||||
|
client := httpclient.Cpdaily.Clone(ctx)
|
||||||
|
client.SetDoNotParseResponse(false)
|
||||||
|
post, err := client.R().SetBody(map[string]any{
|
||||||
|
"base64": b64,
|
||||||
|
}).Post(baseUrl)
|
||||||
|
utils.OkOrPanic(err, func(err error) error {
|
||||||
|
return oops.
|
||||||
|
With("AutoOCRVerification", baseUrl).
|
||||||
|
WithContext(ctx).
|
||||||
|
Wrap(err)
|
||||||
|
})
|
||||||
|
result := post.String()
|
||||||
|
return gjson.Get(result, "data").String()
|
||||||
|
}
|
||||||
func Bytes2Base64(data []byte) string {
|
func Bytes2Base64(data []byte) string {
|
||||||
return base64.StdEncoding.EncodeToString(data)
|
return base64.StdEncoding.EncodeToString(data)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ func GetCpdailyLoginBySchoolInfo(ctx context.Context, info *school.SchoolDetailI
|
|||||||
// 河北石油职业技术大学
|
// 河北石油职业技术大学
|
||||||
case "01e270d1-00e2-4ce9-b0af-5d5fbd462b8a":
|
case "01e270d1-00e2-4ce9-b0af-5d5fbd462b8a":
|
||||||
return NewIap(ctx)
|
return NewIap(ctx)
|
||||||
|
case "676c9395-a29c-4068-97a8-b48e05dae499":
|
||||||
|
return NewHnuu(ctx)
|
||||||
default:
|
default:
|
||||||
if info.IsIap() {
|
if info.IsIap() {
|
||||||
return NewIap(ctx)
|
return NewIap(ctx)
|
||||||
|
|||||||
@@ -0,0 +1,180 @@
|
|||||||
|
package login
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"likedream-goframe/internal/application/cookiejar"
|
||||||
|
"likedream-goframe/internal/application/cpdaily/cryptoutil"
|
||||||
|
"likedream-goframe/internal/application/cpdaily/school"
|
||||||
|
"likedream-goframe/internal/application/httpclient"
|
||||||
|
"likedream-goframe/internal/common/errors"
|
||||||
|
"likedream-goframe/internal/utils"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"github.com/PuerkitoBio/goquery"
|
||||||
|
"github.com/samber/oops"
|
||||||
|
"resty.dev/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
var RSA_KEY_MAP = map[string]string{
|
||||||
|
"public_exponent": "010001",
|
||||||
|
"private_exponent": "413798867d69babed22e0dd3d4031c635f3e9dbca0fa50a32974a0e230787b7f7ba78caefbee828a051c690357a8cc31dba8efc738b4db22e887571ef1ec5a5a55b6d866f6a67527f6a7d78a127c9f687008bb540228b50aa2d1ca5a4ff71107234f936b611ac46432a26da9c302eaa7180820df70593353b3f8c0247fe97a45",
|
||||||
|
"modulus": "00b5eeb166e069920e80bebd1fea4829d3d1f3216f2aabe79b6c47a3c18dcee5fd22c2e7ac519cab59198ece036dcf289ea8201e2a0b9ded307f8fb704136eaeb670286f5ad44e691005ba9ea5af04ada5367cd724b5a26fdb5120cc95b6431604bd219c6b7d83a6f8f24b43918ea988a76f93c333aa5a20991493d4eb1117e7b1",
|
||||||
|
}
|
||||||
|
|
||||||
|
// 淮南联合大学登录实现 RSA加密 & 验证码必出(加法验证码)
|
||||||
|
type CpdailyHnuuLogin struct {
|
||||||
|
client *resty.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c CpdailyHnuuLogin) Login(ctx context.Context, schoolDetail *school.SchoolDetailInfo, username, password string) {
|
||||||
|
jar := cookiejar.NewPersistCookieJar()
|
||||||
|
|
||||||
|
c.client.SetCookieJar(jar).SetRedirectPolicy(resty.FlexibleRedirectPolicy(20))
|
||||||
|
// 获取合法的登录URL
|
||||||
|
loginUrl := getCasValidLoginUrl(schoolDetail)
|
||||||
|
|
||||||
|
// 请求登录页面并获取真正的登录URL
|
||||||
|
loginPageResponse, err := c.client.R().
|
||||||
|
WithContext(ctx).
|
||||||
|
Get(loginUrl)
|
||||||
|
|
||||||
|
utils.OkOrPanic(err)
|
||||||
|
|
||||||
|
defer loginPageResponse.Body.Close()
|
||||||
|
loginPageBytes, err := io.ReadAll(loginPageResponse.Body)
|
||||||
|
utils.OkOrPanic(err)
|
||||||
|
loginResponseUrl := loginPageResponse.RawResponse.Request.URL
|
||||||
|
// 解析登录页面
|
||||||
|
loginPage, err := goquery.NewDocumentFromReader(bytes.NewReader(loginPageBytes))
|
||||||
|
utils.OkOrPanic(err)
|
||||||
|
// 判断登录类型
|
||||||
|
var (
|
||||||
|
allInputs = map[string]string{}
|
||||||
|
formNode = loginPage.Find(".ant-form").First()
|
||||||
|
)
|
||||||
|
inputs := formNode.Find("input")
|
||||||
|
for _, input := range inputs.EachIter() {
|
||||||
|
name, exists := input.Attr("name")
|
||||||
|
if !exists {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
value, _ := input.Attr("value")
|
||||||
|
allInputs[name] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
allInputs["username"] = username
|
||||||
|
pair := cryptoutil.NewNutRSAKeyPair(RSA_KEY_MAP["public_exponent"], "", RSA_KEY_MAP["modulus"])
|
||||||
|
// 处理密码
|
||||||
|
allInputs["password"] = pair.EncryptedString(password)
|
||||||
|
|
||||||
|
// 解决验证码
|
||||||
|
// 解决常规图片验证码
|
||||||
|
captchaUrl := getHnuuCaptchaUrl(loginResponseUrl)
|
||||||
|
code, uid := ResolveCalcCaptcha(ctx, captchaUrl, c.client)
|
||||||
|
|
||||||
|
allInputs["code"] = code
|
||||||
|
allInputs["id"] = uid
|
||||||
|
jar = cookiejar.NewPersistCookieJar()
|
||||||
|
c.client.SetCookieJar(jar)
|
||||||
|
// 登录提交
|
||||||
|
loginPostResponse, err := c.client.R().
|
||||||
|
SetFormData(allInputs).
|
||||||
|
WithContext(ctx).
|
||||||
|
Post(getHnuuLoginPostUrl(loginResponseUrl))
|
||||||
|
utils.OkOrPanic(err, func(err error) error {
|
||||||
|
return oops.
|
||||||
|
WithContext(ctx).
|
||||||
|
With("loginResponseUrl", loginResponseUrl).
|
||||||
|
With("allInputs", allInputs).
|
||||||
|
Wrap(err)
|
||||||
|
})
|
||||||
|
defer loginPostResponse.Body.Close()
|
||||||
|
|
||||||
|
type LoginResponse struct {
|
||||||
|
Meta struct {
|
||||||
|
Success bool `json:"success"`
|
||||||
|
StatusCode int `json:"statusCode"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
} `json:"meta"`
|
||||||
|
Data struct {
|
||||||
|
Code string `json:"code"`
|
||||||
|
} `json:"data"`
|
||||||
|
Tgt string `json:"tgt"`
|
||||||
|
Ticket string `json:"ticket"`
|
||||||
|
}
|
||||||
|
json := utils.ReadJson[LoginResponse](loginPostResponse.Body)
|
||||||
|
if json.Tgt != "" {
|
||||||
|
handleHnuuLoginSuccess(ctx, c.client, loginResponseUrl.Query().Get("service"), json.Ticket, json.Tgt, schoolDetail.WecloudUrl)
|
||||||
|
} else {
|
||||||
|
errMsg := ""
|
||||||
|
if json.Data.Code == "CODEFALSE" {
|
||||||
|
errMsg = "验证码错误"
|
||||||
|
} else if json.Data.Code == "FALSE" {
|
||||||
|
errMsg = "请检查账号密码是否正确"
|
||||||
|
} else if json.Data.Code != "PASSERROR" {
|
||||||
|
errMsg = "密码错误"
|
||||||
|
} else if json.Data.Code != "NOUSER" {
|
||||||
|
errMsg = "账号不存在"
|
||||||
|
} else if json.Data.Code != "USERLOCK" {
|
||||||
|
errMsg = "账号锁定"
|
||||||
|
} else {
|
||||||
|
errMsg = "未知错误" + json.Data.Code
|
||||||
|
}
|
||||||
|
utils.OkOrPanic(errors.NewBusinessError(500, errMsg))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleHnuuLoginSuccess(ctx context.Context, client *resty.Client, url, ticket string, tgt string, host string) {
|
||||||
|
redirectUrl := fmt.Sprintf("%s&ticket=%s", url, ticket)
|
||||||
|
client.R().SetHeader("Referer", "https://cas.hnuu.edu.cn/").Get(redirectUrl)
|
||||||
|
response, err2 := client.R().SetHeader("Referer", "https://cas.hnuu.edu.cn/").Get("https://hnuu.campusphere.net/iap/login")
|
||||||
|
utils.OkOrPanic(err2)
|
||||||
|
newUrl := response.RawResponse.Request.URL
|
||||||
|
ticketUrl := fmt.Sprintf("https://cas.hnuu.edu.cn/lyuapServer/v1/tickets/%s", tgt)
|
||||||
|
get, err := client.R().SetFormData(map[string]string{
|
||||||
|
"service": url,
|
||||||
|
"loginToken": "loginToken",
|
||||||
|
}).Post(ticketUrl)
|
||||||
|
utils.OkOrPanic(err)
|
||||||
|
defer get.Body.Close()
|
||||||
|
ticket = utils.ReadString(get.Body)
|
||||||
|
redirectUrl = fmt.Sprintf("%s&ticket=%s", newUrl.Query().Get("service"), ticket)
|
||||||
|
client.R().SetHeader("Referer", "https://cas.hnuu.edu.cn/").Get(redirectUrl)
|
||||||
|
ActivateSession(ctx, host, client)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getHnuuLoginPostUrl(url *url.URL) string {
|
||||||
|
// https://cas.hnuu.edu.cn/lyuapServer/v1/tickets
|
||||||
|
return fmt.Sprintf("%s://%s/lyuapServer/v1/tickets", url.Scheme, url.Host)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getHnuuCaptchaUrl(url *url.URL) string {
|
||||||
|
// https://cas.hnuu.edu.cn/lyuapServer/kaptcha?_t=1766803822&uid=
|
||||||
|
return fmt.Sprintf("%s://%s/lyuapServer/kaptcha?_t=%d&uid=", url.Scheme, url.Host, utils.GetCurrentTimeMillis())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c CpdailyHnuuLogin) GetCookieJar() http.CookieJar {
|
||||||
|
return c.client.CookieJar()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c CpdailyHnuuLogin) GetCookieString() string {
|
||||||
|
cookieJar := c.GetCookieJar()
|
||||||
|
serializableJar := cookieJar.(*cookiejar.PersistCookieJar)
|
||||||
|
return serializableJar.Serialize()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c CpdailyHnuuLogin) GetClient() *resty.Client {
|
||||||
|
return c.client
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHnuu(ctx context.Context) CpdailyLogin {
|
||||||
|
return &CpdailyHnuuLogin{
|
||||||
|
client: httpclient.NewCpdailyClient(),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,6 +15,32 @@ func ResolveOcrCaptcha(ctx context.Context, url string, client *resty.Client) st
|
|||||||
return captcha.ApiAutoOCRVerification(byteData)
|
return captcha.ApiAutoOCRVerification(byteData)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ResolveCalcCaptcha(ctx context.Context, url string, client *resty.Client) (string, string) {
|
||||||
|
b64, uid := DownBase64(ctx, url, client)
|
||||||
|
return captcha.ApiAutoCalcVerification(b64), uid
|
||||||
|
}
|
||||||
|
|
||||||
|
func DownBase64(ctx context.Context, url string, client *resty.Client) (string, string) {
|
||||||
|
res, err := client.R().
|
||||||
|
WithContext(ctx).
|
||||||
|
Get(url)
|
||||||
|
utils.OkOrPanic(err, func(err error) error {
|
||||||
|
return oops.
|
||||||
|
With("captchaUrl", url).
|
||||||
|
WithContext(ctx).
|
||||||
|
Wrap(err)
|
||||||
|
})
|
||||||
|
defer res.Body.Close()
|
||||||
|
type CaptchResponse struct {
|
||||||
|
KaptchaType string `json:"kaptchaType"`
|
||||||
|
UID string `json:"uid"`
|
||||||
|
Content string `json:"content"`
|
||||||
|
Timeout int `json:"timeout"`
|
||||||
|
}
|
||||||
|
json := utils.ReadJson[CaptchResponse](res.Body)
|
||||||
|
return json.Content, json.UID
|
||||||
|
}
|
||||||
|
|
||||||
func DownCode2Byte(ctx context.Context, url string, client *resty.Client) []byte {
|
func DownCode2Byte(ctx context.Context, url string, client *resty.Client) []byte {
|
||||||
res, err := client.R().
|
res, err := client.R().
|
||||||
WithContext(ctx).
|
WithContext(ctx).
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
func GetCurrentTimeMillis() int64 {
|
||||||
|
return time.Now().UnixMilli()
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user