CQUPT_CAS_SDK

package module
v0.0.0-...-a7c528a Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 5, 2026 License: MIT Imports: 5 Imported by: 0

README

CQUPT-CAS-SDK

重庆邮电大学 统一身份认证平台 SDK

本项目是 Auto CQUPT Plan 的一部分

[!warning]

本项目仅供学习,请勿用于非法用途,否则后果自负!!!

images

1. 项目简介

CQUPT CAS SDK 是由 Golang 编写的 重庆邮电大学统一身份认证中心SDK,实现了登录功能。

2. 使用方法

单元测试文件:

package amin

import "testing"

func Test_Login(t *testing.T) {
    // 获取SDK实例
    sdk := GetSDK("username", "password")

    // 执行登录
    loc, err := sdk.Login("http://jwzx.cqupt.edu.cn/tysfrz/navication.php?id=user")

    if err != nil {
        t.Errorf("Error: %v", err.Error())
    }

    t.Logf("Location: %v", loc)
}

输出:

go test -v
=== RUN   Test_Login
    sdk_test.go:16: Location: http://jwzx.cqupt.edu.cn/tysfrz/navication.php?id=user&ticket=ST-1088667-4ObMbxr-xxxxxxxxxxxx-xxxxauthserver2
--- PASS: Test_Login (0.35s)
PASS
ok      github.com/Auto-CQUPT-Plan/CQUPT-CAS-SDK    0.359s

参数说明:

参数 说明
username 用户账号
password 密码
service 要登陆服务的URL
3. 认证流程
func (r *HttpSpyderCore) GetGlobalCookie() error {
    finalURL := fmt.Sprintf("%s?service=%s", r.baseUrl, r.targetService)

    req, err := http.NewRequest("GET", finalURL, nil)
    if err != nil {
        return err
    }

    for key, val := range r.headers {
        req.Header.Set(key, val)
    }

    resp, err := r.client.Do(req)
    if err != nil {
        return err
    }

    var cookies []string
    for _, cookieHeader := range resp.Header["Set-Cookie"] {
        if idx := strings.Index(cookieHeader, ";"); idx != -1 {
            cookieHeader = cookieHeader[:idx]
        }
        // 去除可能的空格
        cookieHeader = strings.TrimSpace(cookieHeader)
        if cookieHeader != "" {
            cookies = append(cookies, cookieHeader)
        }
    }

    r.cookies = strings.Join(cookies, "; ")
    r.headers["Cookie"] = r.cookies

    _ = resp.Body.Close()

    return nil
}
3.2 获取 pwdEncryptSaltexecution 两个参数
  • 获取源码
func (r *HttpSpyderCore) GetBasicData() (*BasicData, error) {
    data := &BasicData{}

    finalURL := fmt.Sprintf("%s?service=%s", r.baseUrl, r.targetService)

    req, err := http.NewRequest("GET", finalURL, nil)
    if err != nil {
        return nil, err
    }

    for key, val := range r.headers {
        req.Header.Set(key, val)
    }

    resp, err := r.client.Do(req)
    if err != nil {
        return nil, err
    }

    bodyBytes, err := io.ReadAll(resp.Body)
    if err != nil {
        return nil, err
    }

    data.Execution = extractor.ExtractInputValue(bodyBytes, `id="execution"`, `name="execution"`)
    data.PwdEncryptSalt = extractor.ExtractInputValue(bodyBytes, `id="pwdEncryptSalt"`)

    _ = resp.Body.Close()

    return data, nil
}
  • 正则匹配
func ExtractInputValue(html []byte, attrs ...string) string {
    htmlStr := string(html)

    inputPattern := `<input[^>]+`
    for _, attr := range attrs {
        inputPattern += `[^>]*` + attr + `[^>]*`
    }
    inputPattern += `[^>]*>`

    re := regexp.MustCompile(`(?i)` + inputPattern)
    match := re.FindString(htmlStr)
    if match == "" {
        return ""
    }

    valueRe := regexp.MustCompile(`(?i)value\s*=\s*["']([^"']*)["']`)
    valueMatch := valueRe.FindStringSubmatch(match)
    if len(valueMatch) > 1 {
        return valueMatch[1]
    }

    return ""
}
3.3 用 pwdEncryptSalt 加密 password
var aesChars = "ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678"

func randomStringGo(length int) (string, error) {
    result := make([]byte, length)
    charsLen := big.NewInt(int64(len(aesChars)))
    for i := 0; i < length; i++ {
        num, err := rand.Int(rand.Reader, charsLen)
        if err != nil {
            return "", err
        }
        result[i] = aesChars[num.Int64()]
    }
    return string(result), nil
}

func pkcs7Padding(data []byte, blockSize int) []byte {
    padding := blockSize - len(data)%blockSize
    padtext := make([]byte, padding)
    for i := range padtext {
        padtext[i] = byte(padding)
    }
    return append(data, padtext...)
}

func aesEncryptGo(plaintext, key, iv []byte) ([]byte, error) {
    block, err := aes.NewCipher(key)
    if err != nil {
        return nil, err
    }

    if len(iv) != block.BlockSize() {
        return nil, errors.New("invalid iv size")
    }

    plaintext = pkcs7Padding(plaintext, block.BlockSize())
    ciphertext := make([]byte, len(plaintext))
    mode := cipher.NewCBCEncrypter(block, iv)
    mode.CryptBlocks(ciphertext, plaintext)

    return ciphertext, nil
}

func EncryptPasssword(password, salt string) (string, error) {
    randomStr, err := randomStringGo(64)
    if err != nil {
        return "", fmt.Errorf("random error: %w", err)
    }

    passwordStr := randomStr + password

    key := []byte(strings.TrimSpace(salt))
    if len(key) < 16 {
        key = append(key, make([]byte, 16-len(key))...)
    } else if len(key) > 16 {
        key = key[:16]
    }

    iv, err := randomStringGo(16)
    if err != nil {
        return "", fmt.Errorf("gen IV fail: %w", err)
    }

    encrypted, err := aesEncryptGo([]byte(passwordStr), key, []byte(iv))
    if err != nil {
        return "", fmt.Errorf("AES encrypt fail: %w", err)
    }

    encryptedBase64 := base64.StdEncoding.EncodeToString(encrypted)

    encryptedURL := url.QueryEscape(encryptedBase64)

    return encryptedURL, nil
}
3.4 执行登陆动作
func (r *HttpSpyderCore) GetLocation(body *strings.Reader) (string, error) {
    finalURL := fmt.Sprintf("%s?service=%s", r.baseUrl, r.targetService)

    req, err := http.NewRequest("POST", finalURL, body)
    if err != nil {
        return "", err
    }

    for key, val := range r.headers {
        req.Header.Set(key, val)
    }

    resp, err := r.client.Do(req)
    if err != nil {
        return "", err
    }

    _ = resp.Body.Close()

    for key, val := range resp.Header {
        if key == "Location" {
            return val[0], nil
        }
    }

    return "", nil
}

Documentation

Index

Constants

This section is empty.

Variables

View Source
var ErrCaptchaRequired = errors.New("ids: captcha required but no CaptchaSolver registered")

ErrCaptchaRequired 表示 IDS 要求验证码,但 SDK 未注册 CaptchaSolver。

Functions

This section is empty.

Types

type CaptchaSolver

type CaptchaSolver func(image []byte) (string, error)

CaptchaSolver 由调用方提供,负责把验证码图片(通常是 JPEG 字节流) 转换为最终提交给 IDS 的字符串。

典型实现:

  • 人工输入:把图片落盘后在 TUI 提示用户输入
  • 本地 OCR:用 gosseract / paddleocr-go 等识别
  • 远程打码:调云端打码平台 HTTP API

返回空字符串视为放弃,登录会因验证码错误而失败。

type SDK

type SDK struct {
	UserName string
	Password string

	// CaptchaSolver 可选;若为 nil 且 IDS 要求验证码,则 Login 会返回错误。
	CaptchaSolver CaptchaSolver
}

func GetSDK

func GetSDK(username string, password string) *SDK

func (*SDK) Login

func (r *SDK) Login(service string) (location string, err error)

func (*SDK) WithCaptchaSolver

func (r *SDK) WithCaptchaSolver(solver CaptchaSolver) *SDK

WithCaptchaSolver 链式注册验证码解决方案,返回自身便于链式调用。

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL