Skip to content

Commit

Permalink
feat: 实现了统一的登陆与用户信息获取
Browse files Browse the repository at this point in the history
  • Loading branch information
MangoGovo committed Jan 19, 2025
1 parent d09dd5a commit d3a2dd0
Show file tree
Hide file tree
Showing 8 changed files with 402 additions and 67 deletions.
39 changes: 21 additions & 18 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,39 +3,41 @@ module github.com/zjutjh/WeJH-SDK
go 1.22.9

require (
github.com/gin-contrib/sessions v1.0.1
github.com/PuerkitoBio/goquery v1.9.3
github.com/gin-contrib/sessions v1.0.2
github.com/gin-gonic/gin v1.10.0
github.com/go-redis/redis/v8 v8.11.5
github.com/minio/minio-go/v7 v7.0.82
github.com/go-resty/resty/v2 v2.16.3
github.com/minio/minio-go/v7 v7.0.83
github.com/silenceper/wechat/v2 v2.1.7
go.uber.org/zap v1.27.0
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
gopkg.in/natefinch/lumberjack.v2 v2.2.1
)

require (
github.com/boj/redistore v0.0.0-20180917114910-cd5dcc76aeff // indirect
github.com/andybalholm/cascadia v1.3.3 // indirect
github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 // indirect
github.com/bytedance/sonic v1.12.6 // indirect
github.com/bytedance/sonic/loader v0.2.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/bytedance/sonic v1.11.6 // indirect
github.com/bytedance/sonic/loader v0.1.1 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/cloudwego/base64x v0.1.4 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/fatih/structs v1.1.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.7 // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-ini/ini v1.67.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.23.0 // indirect
github.com/go-playground/validator/v10 v10.20.0 // indirect
github.com/goccy/go-json v0.10.4 // indirect
github.com/gomodule/redigo v2.0.0+incompatible // indirect
github.com/gomodule/redigo v1.9.2 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/context v1.1.2 // indirect
github.com/gorilla/securecookie v1.1.2 // indirect
github.com/gorilla/sessions v1.3.0 // indirect
github.com/gorilla/sessions v1.2.2 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.17.11 // indirect
github.com/klauspost/cpuid/v2 v2.2.9 // indirect
Expand All @@ -44,22 +46,23 @@ require (
github.com/minio/md5-simd v1.1.2 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/rs/xid v1.6.0 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/spf13/cast v1.7.0 // indirect
github.com/tidwall/gjson v1.18.0 // indirect
github.com/sirupsen/logrus v1.9.0 // indirect
github.com/snowdreamtech/redistore v0.0.0-20231007100540-6364ca2c97b4 // indirect
github.com/spf13/cast v1.4.1 // indirect
github.com/tidwall/gjson v1.14.1 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.1 // indirect
github.com/tidwall/pretty v1.2.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect
go.uber.org/multierr v1.10.0 // indirect
golang.org/x/arch v0.12.0 // indirect
golang.org/x/arch v0.8.0 // indirect
golang.org/x/crypto v0.31.0 // indirect
golang.org/x/net v0.32.0 // indirect
golang.org/x/net v0.33.0 // indirect
golang.org/x/sys v0.28.0 // indirect
golang.org/x/text v0.21.0 // indirect
google.golang.org/protobuf v1.35.2 // indirect
google.golang.org/protobuf v1.34.1 // indirect
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
141 changes: 92 additions & 49 deletions go.sum

Large diffs are not rendered by default.

54 changes: 54 additions & 0 deletions oauth/check.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package oauth

import (
"bytes"
"regexp"

"github.com/PuerkitoBio/goquery"
"github.com/go-resty/resty/v2"
"github.com/zjutjh/WeJH-SDK/oauth/oauthException"
)

// getLoginMsg 获取登陆失败后页面上的提示语
func getLoginMsg(resp *resty.Response) string {
re := regexp.MustCompile(`<span\s+id="msg">(.+?)</span>`)
matches := re.FindStringSubmatch(resp.String())
if len(matches) == 0 {
return ""
}
return matches[1]
}

// checkLogin 用于判断登陆是否成功
func checkLogin(resp *resty.Response) error {
// 判断登陆是否成功
destination := resp.RawResponse.Request.URL.String()
if destination == PersonalCenterURL {
// 登陆成功后会跳转到用户中心
return nil
}

// 判断失败原因
msg := getLoginMsg(resp)
switch msg {
case WrongPasswordMsg:
return oauthException.WrongPassword
case WrongAccountMsg:
return oauthException.WrongAccount
}
return oauthException.OtherError
}

// checkIsClosed 判断统一是否关闭
func checkIsClosed(resp *resty.Response) error {
doc, err := goquery.NewDocumentFromReader(bytes.NewReader(resp.Body()))
if err != nil {
return err
}

title := doc.Find("title").Text()
if title == "Error 403.6" {
return oauthException.ClosedError
}
return nil
}
29 changes: 29 additions & 0 deletions oauth/common.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package oauth

// 用户中心
const (
MeZjutURL = "http://www.me.zjut.edu.cn"
PersonalCenterURL = MeZjutURL + "/personal-center"
UserInfoApi = MeZjutURL + "/api/basic/info"
)

// 统一登陆
const (
BaseUrl = "https://oauth.zjut.edu.cn/cas"
PublicKeyUrl = BaseUrl + "/v2/getPubKey"
LoginUrl = BaseUrl + "/login"
)

// 登陆错误对应在页面的提示信息
const (
WrongPasswordMsg = "用户名或密码错误" // #nosec G101
WrongAccountMsg = "当前账号无权登录"
)

// UserInfo 用户信息
type UserInfo struct {
College string `json:"bmmc"`
Grade string `json:"jsmc"`
Name string `json:"nc"`
StudentID string `json:"yhm"`
}
36 changes: 36 additions & 0 deletions oauth/example/oauth_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package main

import (
"github.com/zjutjh/WeJH-SDK/oauth"
"testing"
)

const (
// 更换为自己的统一账号密码
username = "302024315297"
password = "262626Mjj@"
wrongUsername = username + "114514"
wrongPassword = password + "1919810"
)

func TestLogin(t *testing.T) {
// 登陆成功
t.Log(oauth.Login(username, password))

// 账号错误
t.Log(oauth.Login(wrongUsername, password))

// 密码错误
t.Log(oauth.Login(username, wrongPassword))
}

func TestGetUserInfo(t *testing.T) {
// 获取信息成功
t.Log(oauth.GetUserInfo(username, password))

// 账号错误
t.Log(oauth.GetUserInfo(wrongUsername, password))

// 密码错误
t.Log(oauth.GetUserInfo(username, wrongPassword))
}
81 changes: 81 additions & 0 deletions oauth/login.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package oauth

import (
"bytes"
"net/http"
"net/http/cookiejar"
"net/url"

"github.com/PuerkitoBio/goquery"
"github.com/go-resty/resty/v2"
)

// Login 统一登陆
func Login(username, password string) ([]*http.Cookie, error) {
client := resty.New()
// 使用cookieJar管理cookie
cookieJar, _ := cookiejar.New(nil)
client.SetCookieJar(cookieJar)

// 1. 初始化请求
resp, err := client.R().
Get(LoginUrl)
if err != nil {
return nil, err
}
// 检查统一系统是否关闭
if err = checkIsClosed(resp); err != nil {
return nil, err
}

// 2. 登陆参数生成
// 解析execution
doc, err := goquery.NewDocumentFromReader(bytes.NewReader(resp.Body()))
if err != nil {
return nil, err
}
execution := doc.
Find("input[type=hidden][name=execution]").
AttrOr("value", "")
// 密码加密
encPwd, err := getEncryptedPassword(client, password)

loginParams := map[string]string{
"username": username,
"password": encPwd,
"execution": execution,
"_eventId": "submit",
}

// 3. 发送登陆请求
resp, err = client.R().
SetFormData(loginParams).
Post(LoginUrl)
if err != nil {
return nil, err
}
// 检查登陆信息
if err = checkLogin(resp); err != nil {
return nil, err
}

// 4. 提取指定域名下的session并构造cookie列表
u, _ := url.Parse(MeZjutURL)
return cookieJar.Cookies(u), nil
}

// GetUserInfo 登陆并获取用户信息
func GetUserInfo(username, password string) (cookies []*http.Cookie, userInfo UserInfo, err error) {
cookies, err = Login(username, password)
if err != nil {
return cookies, userInfo, err
}
userData := struct {
Data UserInfo `json:"data"`
}{}
_, err = resty.New().R().
SetCookies(cookies).
SetResult(&userData).
Get(UserInfoApi)
return cookies, userData.Data, err
}
26 changes: 26 additions & 0 deletions oauth/oauthException/exception.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package oauthException

// 用于维护一些 `统一验证` 相关的 errCode
var (
WrongAccount = newError(501, "账号错误")
WrongPassword = newError(502, "密码错误")
ClosedError = newError(503, "统一系统在夜间关闭")
OtherError = newError(599, "其他错误")
)

// Error 自定义错误
type Error struct {
Code int
Msg string
}

func (e Error) Error() string {
return e.Msg
}

func newError(code int, msg string) *Error {
return &Error{
Code: code,
Msg: msg,
}
}
63 changes: 63 additions & 0 deletions oauth/sign.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package oauth

import (
"crypto/rsa"
"encoding/hex"
"math/big"
"strconv"

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

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

// getPublicKey 获取加密密钥
func getPublicKey(client *resty.Client) (publicKey *rsa.PublicKey, err error) {
var data publicKeyData
_, err = client.R().
SetResult(&data).
Get(PublicKeyUrl)
if err != nil {
return nil, err
}
modulus := new(big.Int)
modulus.SetString(data.Modulus, 16)

e, err := strconv.ParseInt(data.Exponent, 16, 32)
if err != nil {
return nil, err
}
exponent := int(e)
return &rsa.PublicKey{
N: modulus,
E: exponent,
}, nil
}

// rsaEncrypt 加密
func rsaEncrypt(publicKey *rsa.PublicKey, text []byte) []byte {
chunkSize := 2 * (publicKey.N.BitLen()/16 - 1)
textLen := len(text)
result := make([][]byte, 0)
// 分块加密
for i := textLen; i > 0; i -= chunkSize {
textChunk := new(big.Int)
textChunk.SetBytes(text[max(i-chunkSize, 0):i])
textChunk.Exp(textChunk, big.NewInt(int64(publicKey.E)), publicKey.N)
result = append(result, textChunk.Bytes())
}

return result[0]
}

// getEncryptedPassword 密码加密
func getEncryptedPassword(client *resty.Client, password string) (string, error) {
key, err := getPublicKey(client)
if err != nil {
return "", err
}
return hex.EncodeToString(rsaEncrypt(key, []byte(password))), nil
}

0 comments on commit d3a2dd0

Please sign in to comment.