168 lines
4.0 KiB
Go
168 lines
4.0 KiB
Go
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
|
|
}
|