前言
本文是对AppleID登录接入的相关总结,希望对其他人能有帮助。
sing in with apple
“通过 Apple 登录”让用户能用自己的 Apple ID 轻松登录您的 app 和网站。用户不必填写表单、验证电子邮件地址和选择新密码,就可以使用“通过 Apple 登录”设置帐户并立即开始使用您的 app。所有帐户都通过双重认证受到保护,具有极高的安全性,Apple 亦不会跟踪用户在您的 app 或网站中的活动。
代码
package main
import (
"crypto/ecdsa"
"crypto/x509"
"encoding/json"
"encoding/pem"
"errors"
"github.com/dgrijalva/jwt-go"
"io/ioutil"
"net/http"
"net/url"
"strings"
"time"
)
var (
secret = `-----BEGIN PRIVATE KEY-----
your PRIVATE KEY-
-----END PRIVATE KEY-----`
keyId = "keyId"
teamId = "teamId"
clientID = "clientID" //网页授权登录填写的是Services Id,App端登录需要的是AppId
appID = "appID"
redirectUrl = ""
authTokenUrl = "https://appleid.apple.com/auth/token"
)
// create client_secret
func GetAppleSecret() string {
token := &jwt.Token{
Header: map[string]interface{}{
"alg": "ES256",
"kid": keyId,
},
Claims: jwt.MapClaims{
"iss": teamId,
"iat": time.Now().Unix(),
// constraint: exp - iat <= 180 days
"exp": time.Now().Add(24 * time.Hour).Unix(),
"aud": "https://appleid.apple.com",
"sub": appID,
},
Method: jwt.SigningMethodES256,
}
ecdsaKey, _ := AuthKeyFromBytes([]byte(secret))
ss, _ := token.SignedString(ecdsaKey)
return ss
}
func AuthKeyFromBytes(key []byte) (*ecdsa.PrivateKey, error) {
var err error
var block *pem.Block
if block, _ = pem.Decode(key); block == nil {
return nil, errors.New("token: AuthKey must be a valid .p8 PEM file")
}
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
}
type AppleAuthTokenRes struct {
Error string `json:"error"`
AccessToken string `json:"access_token"`
ExpiresIn int `json:"expires_in"`
IDToken string `json:"id_token"`
RefreshToken string `json:"refresh_token"`
TokenType string `json:"token_type"`
}
func AppleAuthToken(code string) (*AppleAuthTokenRes, error) {
form := url.Values{}
form.Set("client_id", appID)
form.Set("client_secret", GetAppleSecret())
form.Set("code", code)
form.Set("grant_type", "authorization_code")
form.Set("redirect_uri", redirectUrl)
var request *http.Request
var err error
if request, err = http.NewRequest("POST", authTokenUrl, strings.NewReader(form.Encode())); err != nil {
return nil, err
}
request.Header.Set("Content-Type", "application/x-www-form-urlencoded")
var response *http.Response
if response, err = http.DefaultClient.Do(request); nil != err {
return nil, err
}
defer response.Body.Close()
data, err := ioutil.ReadAll(response.Body)
if err != nil {
return nil, err
}
res := &AppleAuthTokenRes{}
err = json.Unmarshal(data, res)
if err != nil {
return nil, err
}
if res.Error != "" {
return res, errors.New(res.Error)
}
return res, nil
}
遇到的问题
报错invalid_grant
可能原因1:
前端传过来的code使用一次或者过几分钟就失效了,需要重新生成。
可能原因2:
网页授权登录填写的是Services Id,App端登录需要的是AppI。