golang使用resty库实现模拟请求正方教务

本文涉及的产品
密钥管理服务KMS,1000个密钥,100个凭据,1个月
简介: 本文主要讲解了如何使用golang模拟请求正方教务

resty库介绍

resty是Go语言中的一个流行的HTTP客户端库,主要特征有:

  • 简单的API接口,易于上手使用。
  • 支持设置请求头、查询参数、表单数据、JSON数据等。
  • 支持同步和异步两种请求方式。
  • 支持重试请求、追踪请求、设置超时、绑定请求上下文等功能。
  • 默认启用了KeepAlive,可以重用TCP连接提高效率。
  • 支持中间件,可以针对请求和响应进行自定义处理。
  • 轻量级设计,没有外部依赖。

resty通过组合基础的net/http库提供了更高层级的抽象,可以方便地发送HTTP请求,而不需要自行处理诸如连接池、请求构造等细节。它的API设计简洁易用,可以很容易地集成到Go语言的web应用或者服务中。

安装resty库

go get -u github.com/go-resty/resty/v2

基本使用方式如下:

import "github.com/go-resty/resty/v2"

client := resty.New()
resp, err := client.R().
        Get("http://example.com/api/items")

resty通过链式调用组织请求参数,表现力强而且灵活。它在Go语言的HTTP客户端库中使用广泛,是构建Go Web应用的一个不错选择。

正方教务获取SessionID

清除Cookie打开某大学教务官网

这里我们检查一下监听到的网络请求
image.png

首先第一个请求就是获取一个未登录状态的cookie,接下来我们模拟登陆一下,看一下会发生什么请求。

登录详解

点击登陆后的第一个请求是一个防止用户用户同时登陆的登出请求,这个不会对我们的爬取过程造成影响,可以忽略。

image.png
我们来看第二个请求,其中的参数有:

  • time:query参数,一个13位时间戳
  • csrftoken:会话验证token,用于防范跨站请求。
  • language:语言,不重要
  • yhm:学号
  • mm:加密过后的密码

注意:这个上面显示有两个重复的mm,实测只写一个mm不会影响登陆验证。

如果登录成功,会相应一个302重定向,此处注意,发送请求时要设置禁用重定向,重定向后就获取不到这次请求的响应cookie了,登陆成功响应cookie的内容是验证所有接口的校验,有了这段sessionID,就可以查询教务的任意信息了。

csrftoken

这个我们直接右键检查页面源代码,搜索就可以找到了,后续可以使用正则表达式进行匹配获取。

image.png

密码加密

从上面的可以看出,密码是加密后进行验证的,那我们如何做到加密密码呢。

搜索一下mm,我们发现这个前端进行了一个rsa加密,其中有两个重要参数modulus(模数)exponent(指数)

转存失败,建议直接上传图片文件接下来搜索modulus,找到公匙的来源,我们定位到这个请求。

转存失败,建议直接上传图片文件

转存失败,建议直接上传图片文件转存失败,建议直接上传图片文件这个请求携带cookie并有两个13位时间戳的参数。

总结登录过程

  1. 进入登录页面请求,获取cookie与csrftoken
  2. 通过这个token获取rsa公匙
  3. 密码加密,携带参数请求登录,拿到登陆状态的cookie

代码实现

基本的结构体对象

const baseUrl = "https://jxw.sylu.edu.cn/xtgl"

var client *resty.Client

type SyluClient struct {
   
   
    Username  string
    Client    *resty.Client
    Cookie    *http.Cookie
    Csrftoken string
    Time      string
    PublicKey PublicKeyReponse
}

type PublicKeyReponse struct {
   
   
    Modulus  string `json:"modulus"`
    Exponent string `json:"exponent"`
}

初始化

  1. 创建SyluClient结构体对象syluclient,用于保存登录会话信息。
  2. 使用resty.New()创建一个resty客户端对象client。
  3. 发送初始化请求,获取Cookie和csrftoken。
  4. 使用正则表达式提取csrftoken保存到syluclient中。
  5. 构造获取公钥的请求,包含时间戳、随机数查询参数,以及刚才获取的Cookie。
  6. 发送请求获取公钥,并解析响应保存到syluclient。
  7. 返回构造好的带有会话信息的syluclient对象。
func New() (*SyluClient, error) {

    //创建登录会话结构体
    syluclient := new(SyluClient)

    client = resty.New()

    //请求主页面,获取初始cookie与csrftoken
    initResp, err := client.R().SetHeaders(pkg.BaseHttpHeaders()).Get(baseUrl + "/login_slogin.html")
    if err != nil {
        fmt.Println("init请求失败" + err.Error())
        return nil, err
    }

    syluclient.Cookie = initResp.Cookies()[0]

    Findcsrftoken := regexp.MustCompile(`id="csrftoken" name="csrftoken" value="([^"]+)"`)
    syluclient.Csrftoken = Findcsrftoken.FindStringSubmatch(string(initResp.Body()))[1]

    //获取公匙
    time := pkg.NowTime()
    getPublicKeyResp, err := client.R().SetHeaders(pkg.BaseHttpHeaders()).
        SetQueryParams(map[string]string{
            "time": time,
            "_":    time,
        }).SetCookie(syluclient.Cookie).Get(baseUrl + "/login_getPublicKey.html")
    if err != nil {
        fmt.Println("获取公匙失败" + err.Error())
    }

    json.Unmarshal([]byte(getPublicKeyResp.String()), &syluclient.PublicKey)

    return syluclient, nil
}

获取登陆状态cookie

  1. 从参数中获取用户名和密码。
  2. 使用RSA公钥对密码进行加密,得到加密后的密码enpassword。
  3. 构造登录表单数据,包含csrftoken、用户名、加密后的密码等。
  4. 设置禁止重定向,发送登录POST请求。
  5. 根据响应结果判断登录是否成功:
  • 如果返回重定向错误,说明登录成功,则更新登录后得到的新Cookie。
  • 如果返回网络错误,返回服务器连接失败。
  • 否则登录失败,返回账号或密码错误。
  1. 保存登录状态,返回结果。
func (syluclient *SyluClient) Login(username string, password string) error {
   
   
    syluclient.Username = username
    enpassword, err := pkg.Rsa(password, syluclient.PublicKey.Modulus, syluclient.PublicKey.Exponent)
    if err != nil {
   
   
        return err
    }
    //发送登录请求,并禁止重定向
    loginresponse, err := client.SetRedirectPolicy(resty.NoRedirectPolicy()).R().SetFormData(map[string]string{
   
   
        "csrftoken": syluclient.Csrftoken,
        "language":  "zh_CN",
        "yhm":       username,
        "mm":        enpassword,
    }).SetQueryParam("time", pkg.NowTime()).SetCookie(syluclient.Cookie).SetHeaders(pkg.BaseHttpHeaders()).
        Post(baseUrl + "/login_slogin.html")

    //如果重定向了,代表登陆成功]

    if err != nil && err.Error() == "Post "/xtgl/login_slogin.html": auto redirect is disabled" {
   
   
        fmt.Println("登陆成功")
        syluclient.Cookie = loginresponse.Cookies()[1]
        fmt.Println(loginresponse.Cookies()[1].String())
        return nil
    } else if err != nil {
   
   
        return errors.New("服务器连接失败:" + err.Error())
    } else {
   
   
        return errors.New("账号或密码错误")
    }

}

工具函数

  1. BaseHttpHeaders 函数构造了一个通用的HTTP请求头,包含User-Agent、Content-Type等信息,可以设置到resty的请求中。
  2. NowTime 函数利用time包生成当前时间的毫秒级时间戳,可以作为各种请求的时间参数。
  3. Rsa 函数实现了RSA加密逻辑,主要步骤是:
  • Base64解码获得modulus和exponent
  • 构造公钥
  • 生成随机数
  • 使用公钥对密码加密
  • Base64编码并返回加密后的密码
func BaseHttpHeaders() map[string]string {
   
   
    return map[string]string {
   
   
        "User-Agent":    "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:29.0) Gecko/20100101 Firefox/29.0",
        "Content-Type":  "application/x-www-form-urlencoded;charset=uft-8",
        "Cache-Control": "no-cache",
    }
}

func NowTime() string {
   
   
    // 获取当前时间的毫秒级时间戳
    timestamp := time.Now().UnixNano() / 1000000

    // 返回毫秒级时间戳
    return strconv.FormatInt(timestamp, 10)
}

func Rsa(password string, modulus string, exponent string) (string,error) {
   
   
    modulusBytes, err := base64.StdEncoding.DecodeString(modulus)
    if err != nil {
   
   
        fmt.Println("modulus err" + err.Error())
        return "",err
    }

    exponentBytes, err := base64.StdEncoding.DecodeString(exponent)
    if err != nil {
   
   
        fmt.Println("exponent err" + err.Error())
        return "",err
    }

    // 解析公钥
    pubKey := &rsa.PublicKey{
   
   
        N: new(big.Int).SetBytes(modulusBytes),
        E: int(new(big.Int).SetBytes(exponentBytes).Int64()),
    }

    // 加密密码
    bypassword := []byte(password)
    encryptedBytes, err := rsa.EncryptPKCS1v15(rand.Reader, pubKey, bypassword)
    if err != nil {
   
   
        panic(err)
    }

    // Base64 编码加密后的密码
    encryptedPassword := base64.StdEncoding.EncodeToString(encryptedBytes)

    return encryptedPassword,nil
}
目录
相关文章
|
5月前
|
测试技术 Go 开发者
go-carbon v2.3.8 发布,轻量级、语义化、对开发者友好的 golang 时间处理库
carbon 是一个轻量级、语义化、对开发者友好的 golang 时间处理库,支持链式调用。
54 0
|
5月前
|
测试技术 Go 开发者
go-carbon v2.3.7 发布,轻量级、语义化、对开发者友好的 golang 时间处理库
carbon 是一个轻量级、语义化、对开发者友好的 golang 时间处理库,支持链式调用。
31 2
|
1月前
|
Unix Go
Golang语言标准库time之日期和时间相关函数
这篇文章是关于Go语言日期和时间处理的文章,介绍了如何使用Go标准库中的time包来处理日期和时间。
30 3
|
2月前
|
存储 物联网 测试技术
Golang中的HTTP请求凝聚器
Golang中的HTTP请求凝聚器
|
2月前
|
JSON Go API
一文搞懂 Golang 高性能日志库 - Zap
一文搞懂 Golang 高性能日志库 - Zap
63 2
|
2月前
|
存储 JSON Go
一文搞懂 Golang 高性能日志库 Zerolog
一文搞懂 Golang 高性能日志库 Zerolog
90 0
|
2月前
|
JSON Go 数据格式
[golang]标准库-json
[golang]标准库-json
|
4月前
|
SQL NoSQL Go
技术经验分享:Golang标准库:errors包应用
技术经验分享:Golang标准库:errors包应用
36 0
|
5月前
|
Go
Golang标准库sync的使用
Golang标准库sync的使用
49 2
|
5月前
|
存储 网络协议 Go
7天玩转 Golang 标准库之 http/net
7天玩转 Golang 标准库之 http/net
52 2
下一篇
无影云桌面