Files
servicebase/pkg/partner/Apple/AppleClient.go
2025-11-18 17:48:20 +08:00

167 lines
4.0 KiB
Go

package Apple
import (
"crypto/ecdsa"
"crypto/x509"
"encoding/json"
"encoding/pem"
"errors"
"fmt"
"net/http"
"servicebase/pkg/common/HyTools"
"time"
"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) (interface{}, 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]interface{}{
"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 interface{}
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
}