golang 苹果一键登录 sing in with apple

2020-08-16  本文已影响0人  zfh_51d2

前言

本文是对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。

上一篇下一篇

猜你喜欢

热点阅读