🐛 iap新版本登录
This commit is contained in:
@@ -2,6 +2,9 @@ package cryptoutil
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"math/big"
|
||||
@@ -36,6 +39,34 @@ func NewRSAKeyPair(exponent, privateExponent, modulus string) (*RSAKeyPair, erro
|
||||
}, nil
|
||||
}
|
||||
|
||||
// NewRSAKeyPairFromPublicKey 从 X.509 格式的 base64 公钥字符串创建 RSA 密钥对
|
||||
// publicKeyBase64: X.509 DER 格式的 base64 编码公钥字符串
|
||||
func NewRSAKeyPairFromPublicKey(publicKeyBase64 string) (*RSAKeyPair, error) {
|
||||
// 解码 base64
|
||||
derBytes, err := base64.StdEncoding.DecodeString(publicKeyBase64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode base64: %w", err)
|
||||
}
|
||||
|
||||
// 解析 X.509 格式的公钥
|
||||
pubKey, err := x509.ParsePKIXPublicKey(derBytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse public key: %w", err)
|
||||
}
|
||||
|
||||
// 转换为 RSA 公钥
|
||||
rsaPubKey, ok := pubKey.(*rsa.PublicKey)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("not an RSA public key")
|
||||
}
|
||||
|
||||
// 创建密钥对
|
||||
return &RSAKeyPair{
|
||||
n: rsaPubKey.N,
|
||||
e: big.NewInt(int64(rsaPubKey.E)),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Encrypt 使用公钥加密数据,返回十六进制字符串
|
||||
func (k *RSAKeyPair) Encrypt(plaintext string) (string, error) {
|
||||
ciphertext, err := k.EncryptBytes([]byte(plaintext))
|
||||
|
||||
@@ -52,3 +52,36 @@ func TestRSAKeyPair_EncryptBytes(t *testing.T) {
|
||||
|
||||
t.Logf("密文长度: %d bytes", len(ciphertext))
|
||||
}
|
||||
|
||||
func TestNewRSAKeyPairFromPublicKey(t *testing.T) {
|
||||
// 使用你提供的 X.509 格式的 base64 公钥
|
||||
publicKey := "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDCpbRy8ZoyQvRPpUDIXycglTwVDcYrLcv7P9HI4E7/TAPZiI3GN0ckTUVVWRvoo/re3uCOOJK+F+Ufh19XiRIdoPYCULsSyEjcLLiyAS09mVOzhAQco84E/lM24T7rRTVID7LIgWPewyN8Nd5vzHET4Q3qTthoL8n82BwC1iXuVQIDAQAB"
|
||||
|
||||
keyPair, err := NewRSAKeyPairFromPublicKey(publicKey)
|
||||
if err != nil {
|
||||
t.Fatalf("从公钥创建 RSA 密钥对失败: %v", err)
|
||||
}
|
||||
|
||||
// 测试加密
|
||||
plaintext := "241703070163"
|
||||
ciphertext, err := keyPair.Encrypt(plaintext)
|
||||
if err != nil {
|
||||
t.Fatalf("加密失败: %v", err)
|
||||
}
|
||||
|
||||
t.Logf("明文: %s", plaintext)
|
||||
t.Logf("密文(十六进制): %s", ciphertext)
|
||||
|
||||
// 验证密文不为空
|
||||
if len(ciphertext) == 0 {
|
||||
t.Error("密文为空")
|
||||
}
|
||||
|
||||
// 验证密文长度(1024位密钥应该产生128字节 = 256个十六进制字符)
|
||||
expectedHexLen := (keyPair.GetModulus().BitLen() + 7) / 8 * 2
|
||||
if len(ciphertext) != expectedHexLen {
|
||||
t.Errorf("密文长度不正确: 期望 %d, 实际 %d", expectedHexLen, len(ciphertext))
|
||||
}
|
||||
|
||||
t.Logf("密钥长度: %d bits", keyPair.GetModulus().BitLen())
|
||||
}
|
||||
|
||||
@@ -5,18 +5,22 @@ import (
|
||||
"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"
|
||||
"strings"
|
||||
|
||||
"github.com/samber/oops"
|
||||
"github.com/tidwall/gjson"
|
||||
"resty.dev/v3"
|
||||
)
|
||||
|
||||
const iapRsaPubKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDCpbRy8ZoyQvRPpUDIXycglTwVDcYrLcv7P9HI4E7/TAPZiI3GN0ckTUVVWRvoo/re3uCOOJK+F+Ufh19XiRIdoPYCULsSyEjcLLiyAS09mVOzhAQco84E/lM24T7rRTVID7LIgWPewyN8Nd5vzHET4Q3qTthoL8n82BwC1iXuVQIDAQAB"
|
||||
|
||||
type CpdailyIapLogin struct {
|
||||
client *resty.Client
|
||||
}
|
||||
@@ -37,11 +41,10 @@ func (c *CpdailyIapLogin) Login(ctx context.Context, schoolDetail *school.School
|
||||
c.client.SetCookieJar(jar).
|
||||
SetRedirectPolicy(resty.NoRedirectPolicy())
|
||||
|
||||
ltInfoResponse, err := c.client.R().
|
||||
WithContext(ctx).
|
||||
SetBody(map[string]any{}).
|
||||
Post(fmt.Sprintf("%s/iap/security/lt", loginUrl))
|
||||
|
||||
// 获取基础lt
|
||||
preLoginUrl := loginUrl + "/portal/login"
|
||||
c.client.SetRedirectPolicy(resty.FlexibleRedirectPolicy(20))
|
||||
loginRedirectResponse, err := c.client.R().Get(preLoginUrl)
|
||||
utils.OkOrPanic(err, func(err error) error {
|
||||
return oops.
|
||||
In("CpdailyIapLogin.Login").
|
||||
@@ -50,6 +53,25 @@ func (c *CpdailyIapLogin) Login(ctx context.Context, schoolDetail *school.School
|
||||
With("password", password).
|
||||
Wrap(err)
|
||||
})
|
||||
u := loginRedirectResponse.RawResponse.Request.URL
|
||||
oldLt := strings.Split(u.String(), "=")[1]
|
||||
c.client.SetRedirectPolicy(resty.NoRedirectPolicy())
|
||||
ltInfoResponse, err := c.client.R().
|
||||
WithContext(ctx).
|
||||
SetFormData(map[string]string{
|
||||
"lt": oldLt,
|
||||
}).
|
||||
Post(fmt.Sprintf("%s/iap/security/lt", loginUrl))
|
||||
rsa, _ := cryptoutil.NewRSAKeyPairFromPublicKey(iapRsaPubKey)
|
||||
password, _ = rsa.Encrypt(password)
|
||||
utils.OkOrPanic(err, func(err error) error {
|
||||
return oops.
|
||||
In("CpdailyIapLogin.Login").
|
||||
With("schoolDetail", schoolDetail).
|
||||
With("username", username).
|
||||
With("password", "{rsa}"+password).
|
||||
Wrap(err)
|
||||
})
|
||||
|
||||
defer ltInfoResponse.Body.Close()
|
||||
|
||||
@@ -64,8 +86,9 @@ func (c *CpdailyIapLogin) Login(ctx context.Context, schoolDetail *school.School
|
||||
"rememberMe": "true",
|
||||
"dllt": "",
|
||||
"mobile": "",
|
||||
"captcha": "",
|
||||
"username": username,
|
||||
"password": password,
|
||||
"password": "{rsa}" + password,
|
||||
}
|
||||
|
||||
if isIapNeedCaptcha(ctx, loginUrl, username, c.client) {
|
||||
|
||||
Reference in New Issue
Block a user