package Apple import ( "crypto/ecdsa" "crypto/x509" "encoding/json" "encoding/pem" "errors" "fmt" "net/http" "time" "gitea.ddegame.cn/open/servicebase/pkg/common/HyTools" "github.com/anxpp/beego/logs" "github.com/dgrijalva/jwt-go" "github.com/spf13/viper" ) const ( AppleServerApiUrl = "https://appleid.apple.com/auth/token" ) // 苹果登录 type AppleClient struct { } func NewAppleClient() *AppleClient { return &AppleClient{} } type Claims struct { jwt.StandardClaims Iss string `json:"iss"` Aud string `json:"aud"` Exp int64 `json:"exp"` Iat int64 `json:"iat"` Sub string `json:"sub"` AtHash string `json:"at_hash"` Email string `json:"email"` EmailVerified string `json:"email_verified"` AuthTime int64 `json:"auth_time"` NonceSupported bool `json:"nonce_supported"` } type ResJwt struct { Token string `json:"access_token"` Type string `json:"token_type"` ExpiresIn int64 `json:"expires_in"` RefreshToken string `json:"refresh_token"` IdToken string `json:"id_token"` } // 验证code func (client *AppleClient) VerifyAppleAuthCode(authCode string) (appleUserId string, resultErr error) { apiUrl := AppleServerApiUrl clientId := viper.GetString("apple.IOSBundleId") param := make(map[string]string, 0) param["client_id"] = clientId param["code"] = authCode param["grant_type"] = viper.GetString("apple.GrantType") // 生成密钥 secret := client.GenerateClientSecret() if len(secret) == 0 { resultErr = errors.New("生成密钥出错") return } param["client_secret"] = secret paramString := "" for k, v := range param { kv := k + "=" + v + "&" paramString += kv } if len(paramString) > 0 { paramString = HyTools.Substr(paramString, 0, len(paramString)-1) } logs.Info("signWithApple params:" + paramString) headerMap := make(map[string]string, 0) headerMap["Content-Type"] = "application/x-www-form-urlencoded;charset=utf-8" result, err := HyTools.HttpDo(http.MethodPost, apiUrl, headerMap, paramString) if err != nil { logs.Error("苹果登录失败,请求API失败:" + err.Error()) resultErr = err return } logs.Info("signWithApple result:" + result) var resultDTO ResJwt e := json.Unmarshal([]byte(result), &resultDTO) if e != nil { logs.Error("苹果登录失败,JSON:" + e.Error()) resultErr = err return } token, _ := jwt.ParseWithClaims(resultDTO.IdToken, &Claims{}, func(token *jwt.Token) (any, error) { // Don't forget to validate the alg is what you expect: if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok { return nil, fmt.Errorf("Unexpected signing method: %v ", token.Header["alg"]) } return token, nil }) if claims, ok := token.Claims.(*Claims); ok { appleUserId = claims.Sub } else { resultErr = errors.New("jwt error, token not correct") } return } // 生成jwt密钥 func (client *AppleClient) GenerateClientSecret() (secret string) { token := &jwt.Token{ Header: map[string]any{ "alg": "ES256", "kid": viper.GetString("apple.KeyId"), }, Claims: jwt.MapClaims{ "iss": viper.GetString("apple.TeamId"), "iat": time.Now().Unix(), // constraint: exp - iat <= 180 days "exp": time.Now().Add(24 * time.Hour).Unix(), "aud": "https://appleid.apple.com", "sub": viper.GetString("apple.IOSBundleId"), }, Method: jwt.SigningMethodES256, } ecdsaKey, _ := AuthKeyFromBytes([]byte(viper.GetString("apple.PrivateKey"))) ss, _ := token.SignedString(ecdsaKey) secret = ss return } func AuthKeyFromBytes(key []byte) (*ecdsa.PrivateKey, error) { var err error // Parse PEM block var block *pem.Block if block, _ = pem.Decode(key); block == nil { return nil, errors.New("token: AuthKey must be a valid .p8 PEM file") } // Parse the key var parsedKey any if parsedKey, err = x509.ParsePKCS8PrivateKey(block.Bytes); err != nil { return nil, err } var pkey *ecdsa.PrivateKey var ok bool if pkey, ok = parsedKey.(*ecdsa.PrivateKey); !ok { return nil, errors.New("token: AuthKey must be of type ecdsa.PrivateKey") } return pkey, nil }