這次只是實現應用的curd和公私鑰的校驗以及第三方的通知dmeo項目,大家可以拓開視野來編寫
進入主題
項目鏈接:桌角的眼鏡/develop_auth_platform
直接下拉并運行就行 回調應用代碼在test包中
回調應用測試代碼
package mainimport ("encoding/json""fmt""io""log""net/http""time"
)func main() {// 設置路由http.HandleFunc("/callback", callbackHandler)http.HandleFunc("/", homeHandler)// 配置服務器server := &http.Server{Addr: ":8089",ReadTimeout: 5 * time.Second,WriteTimeout: 10 * time.Second,}// 啟動服務器fmt.Printf("🚀 服務已啟動,監聽端口 8089\n")fmt.Printf("👉 測試接口: curl -X POST http://localhost:8089/callback -d '{\"message\":\"test\"}'\n")if err := server.ListenAndServe(); err != nil {log.Fatalf("? 服務器啟動失敗: %v", err)}
}// 回調接口處理器
func callbackHandler(w http.ResponseWriter, r *http.Request) {// 打印請求基本信息fmt.Printf("\n=== 收到回調請求 ===\n")fmt.Printf("時間: %s\n", time.Now().Format(time.RFC3339))fmt.Printf("方法: %s\n", r.Method)fmt.Printf("來源IP: %s\n", r.RemoteAddr)fmt.Printf("請求頭: %v\n", r.Header)// 根據Content-Type處理不同格式的請求體contentType := r.Header.Get("Content-Type")var body interface{}switch contentType {case "application/json":var jsonBody map[string]interface{}if err := json.NewDecoder(r.Body).Decode(&jsonBody); err != nil {http.Error(w, "無效的JSON數據", http.StatusBadRequest)return}body = jsonBodydefault:// 其他類型直接讀取原始數據data, err := io.ReadAll(r.Body)if err != nil {http.Error(w, "讀取請求體失敗", http.StatusBadRequest)return}body = string(data)}// 打印請求體fmt.Printf("請求體: %+v\n", body)fmt.Printf("===================\n")// 返回成功響應w.Header().Set("Content-Type", "application/json")json.NewEncoder(w).Encode(map[string]interface{}{"status": "success","message": "回調已接收","data": body,})
}// 首頁處理器
func homeHandler(w http.ResponseWriter, r *http.Request) {if r.URL.Path != "/" {http.NotFound(w, r)return}w.Header().Set("Content-Type", "text/plain")fmt.Fprintf(w, "回調服務運行中\n請訪問 /callback 接口")
}
?項目的簡易結構
核心部分?
Application的curd
/api/v1/下的application.go
package v1import ("github.com/gin-gonic/gin""github.com/spectacleCase/develop_auth_platform/global""github.com/spectacleCase/develop_auth_platform/models"request "github.com/spectacleCase/develop_auth_platform/models/request"
)type Application struct{}func (Application) Create() gin.HandlerFunc {return func(c *gin.Context) {var NewApp request.Createif err := c.ShouldBind(&NewApp); err != nil {models.FailWithMessage("參數有誤", c)return}id, err := models.GenerateID()if err != nil {models.FailWithMessage(err.Error(), c)return}// 得到公私鑰prKey, puKey, err := models.GetDefaultKeyPair()if err != nil {models.FailWithMessage(err.Error(), c)return}global.ApplicationList = append(global.ApplicationList, &models.Application{Id: id,Name: NewApp.Name,CallbackUrl: NewApp.CallbackUrl,PrKey: prKey,PuKey: puKey,})models.Ok(c)return}
}func (Application) Get() gin.HandlerFunc {return func(c *gin.Context) {models.OkWithData(global.ApplicationList, c)return}
}func (Application) Update() gin.HandlerFunc {return func(c *gin.Context) {var updateApp request.Updateif err := c.ShouldBind(&updateApp); err != nil {models.FailWithMessage("參數有誤", c)return}for index, app := range global.ApplicationList {if app == nil {continue // 跳過 nil 指針}if app.Id == updateApp.Id {app.Name = updateApp.Nameapp.CallbackUrl = updateApp.CallbackUrlglobal.ApplicationList[index] = app}models.Ok(c)return}models.FailWithMessage("錯誤的參數", c)return}
}func (Application) Delete() gin.HandlerFunc {return func(c *gin.Context) {var delApp request.Deleteif err := c.ShouldBind(&delApp); err != nil {models.FailWithMessage("參數有誤", c)return}for index, app := range global.ApplicationList {if app == nil {continue // 跳過 nil 指針}if app.Id == delApp.Id {global.ApplicationList[index] = nil}models.Ok(c)return}models.FailWithMessage("錯誤的參數", c)return}
}
/models下的application.go?
package modelsimport ("crypto/rand""crypto/rsa""crypto/x509""encoding/base64""encoding/pem""fmt"
)type Application struct {Id string `json:"id"`Name string `json:"name"`PrKey string `json:"-"` // 私鑰(JSON 序列化時忽略)PuKey string `json:"puKey"` // 公鑰SerPuKey string `json:"serPuKey"`CallbackUrl string `json:"callbackUrl"` // 回調地址
}// GenerateID 生成唯一應用ID (UUID簡化版)
func GenerateID() (string, error) {const length = 16 // 16字節 = 128位b := make([]byte, length)if _, err := rand.Read(b); err != nil {return "", fmt.Errorf("生成ID失敗: %v", err)}return base64.URLEncoding.EncodeToString(b), nil
}// GenerateKeyPair 生成RSA公私鑰對
func GenerateKeyPair(bits int) (prKey, puKey string, err error) {privateKey, err := rsa.GenerateKey(rand.Reader, bits)if err != nil {return "", "", fmt.Errorf("密鑰生成失敗: %v", err)}// 編碼私鑰privateKeyBytes := x509.MarshalPKCS1PrivateKey(privateKey)privateKeyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY",Bytes: privateKeyBytes,})// 編碼公鑰publicKeyBytes, err := x509.MarshalPKIXPublicKey(&privateKey.PublicKey)if err != nil {return "", "", fmt.Errorf("公鑰編碼失敗: %v", err)}publicKeyPEM := pem.EncodeToMemory(&pem.Block{Type: "PUBLIC KEY",Bytes: publicKeyBytes,})return string(privateKeyPEM), string(publicKeyPEM), nil
}// GetDefaultKeyPair 獲取默認強度的密鑰對 (2048位)
func GetDefaultKeyPair() (string, string, error) {return GenerateKeyPair(2048)
}// VerifyKeyPair 驗證公私鑰是否匹配
func VerifyKeyPair(prKey, puKey string) bool {block, _ := pem.Decode([]byte(prKey))if block == nil {return false}privateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)if err != nil {return false}pubBlock, _ := pem.Decode([]byte(puKey))if pubBlock == nil {return false}pubKey, err := x509.ParsePKIXPublicKey(pubBlock.Bytes)if err != nil {return false}rsaPubKey, ok := pubKey.(*rsa.PublicKey)if !ok {return false}return privateKey.PublicKey.Equal(rsaPubKey)
}
測試截圖
?