-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
8 changed files
with
400 additions
and
67 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"` | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
package main | ||
|
||
import ( | ||
"errors" | ||
"github.com/zjutjh/WeJH-SDK/oauth" | ||
"github.com/zjutjh/WeJH-SDK/oauth/oauthException" | ||
"testing" | ||
) | ||
|
||
const ( | ||
// 更换为自己的统一账号密码 | ||
username = "username" | ||
password = "password" | ||
wrongUsername = username + "114514" | ||
wrongPassword = password + "1919810" | ||
) | ||
|
||
func testBase(username, password string, expect error, t *testing.T) { | ||
cookies, userInfo, err := oauth.GetUserInfo(username, password) | ||
if errors.Is(err, expect) { | ||
t.Log(cookies, userInfo, err) | ||
} else { | ||
t.Errorf("测试点未通过, 期望值:%s, 实际值:%s", expect, err) | ||
} | ||
} | ||
|
||
func TestLogin(t *testing.T) { | ||
// 登陆成功 | ||
testBase(username, password, nil, t) | ||
// 账号错误 | ||
testBase(wrongUsername, password, oauthException.WrongAccount, t) | ||
// 密码错误 | ||
testBase(username, wrongPassword, oauthException.WrongPassword, t) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |