Skip to content

Commit

Permalink
Feat.
Browse files Browse the repository at this point in the history
  • Loading branch information
LLemonGreen committed Jul 30, 2020
1 parent 10dd1fc commit e9018fd
Show file tree
Hide file tree
Showing 4 changed files with 200 additions and 0 deletions.
Binary file added .DS_Store
Binary file not shown.
43 changes: 43 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,45 @@
# appleTools
苹果登陆相关工具

开发APP苹果登陆功能时参考各种渠道的代码然后顺手封装一下。

主要参考:https://github.com/tptpp/sign-in-with-apple

在此基础上增加了解密苹果最终返回数据的id_token,用于验证客户端post过来的userIdentifier

其中id_token解密后有个sub字段,该字段一般和userIdentifier一致

通过对比这两个字段是否相等来处理后续登陆业务

比如如下使用方式

```go
// @Summary 苹果登陆
// @Produce application/json
// @Param data body request.AppleLoginCode true "苹果登陆"
// @Success 200 {string} string "{"success":true,"data":{},"msg":"登陆成功"}"
// @Router /appLogin/AppleLoginCode [post]
func AppleLogin(c *gin.Context) {
//一个请求信息的结构体,接收authorizationCode和userIdentifier
var A request.AppleLoginCodeAndId
_ = c.ShouldBindJSON(&A)

//生成client_secret,参数:苹果账户的KeyId,TeamId, ClientID, KeySecret
clientSecret := appleTools.GetAppleSecret(KeyId,TeamId, ClientID, KeySecret)

//获取用户信息,参数:ClientID, 上面的clientSecret, authorizationCode
data, err := appleTools.GetAppleLoginData(ClientID, clientSecret, A.Code)

//检查用户信息,参数:上面的data,客户端传过来的userIdentifier,上面的err
check := appleTools.CheckAppleID(data, A.ID, err)
if check == false {
response.FailWithMessage("APPLE登陆失败[error 1],请重试或使用其他方式登陆", c)
return
}

fmt.Println("苹果校验结果OK")

//你的登陆业务

}
```
134 changes: 134 additions & 0 deletions applefunc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
package appleTools

import (
"crypto/ecdsa"
"crypto/x509"
"encoding/json"
"encoding/pem"
"github.com/dgrijalva/jwt-go"
"github.com/pkg/errors"
"io/ioutil"
"net/http"
"net/url"
"strings"
"time"
)

//请求苹果用户信息
func GetAppleLoginData(clientId string, clientSecret string, code string) ([]byte, error) {
params := map[string]string{
"client_id": clientId,
"client_secret": clientSecret,
"code": code,
"grant_type": "authorization_code",
"redirect_uri": "",
}
form := url.Values{}
for k, v := range params {
form.Set(k, v)
}

var request *http.Request
var err error
if request, err = http.NewRequest("POST", "https://appleid.apple.com/auth/token",
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)

return data, err
}

//生成client_secret
func GetAppleSecret(keyId string, teamId string, clientId string, keySecret string) 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": clientId,
},
Method: jwt.SigningMethodES256,
}

ecdsaKey, _ := AuthKeyFromBytes([]byte(keySecret))
ss, _ := token.SignedString(ecdsaKey)
return ss
}

//JWT加密
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
}

//解密苹果返回的data中的id_token中的用户id和客户端post的id否一致
func CheckAppleID(data []byte, id string, err error) bool {
//fmt.Println("苹果服务器返回信息为:", string(data))
if err != nil || strings.Contains(string(data), "error") {
//fmt.Println("APPLE登陆失败[error 1],请重试或使用其他方式登陆")
return false
}
//fmt.Println("苹果服务器返回信息OK")
Au := AppleAuth{}
err = json.Unmarshal(data, &Au)
if err != nil {
//fmt.Println("APPLE登陆失败[error 2],请重试或使用其他方式登陆")
return false
}
//fmt.Println("结构体赋值OK", Au)
var userDecode []byte
parts := strings.Split(Au.IdToken, ".")
userDecode, err = jwt.DecodeSegment(parts[1])
if err != nil {
//fmt.Println("APPLE登陆失败[error 3],请重试或使用其他方式登陆")
return false
}
//fmt.Println("Au.IdToken解码OK", userDecode)
It := AppleIdToken{}
err = json.Unmarshal(userDecode, &It)
if err != nil {
//fmt.Println("APPLE登陆失败[error 4],请重试或使用其他方式登陆")
return false
}
//fmt.Println("结构体赋值OK", It)
if It.Sub != id {
//fmt.Println("APPLE登陆失败[error 5],请重试或使用其他方式登陆")
return false
}
return true
}
23 changes: 23 additions & 0 deletions model.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package appleTools

type AppleIdToken struct {
Iss string `json:"iss,omitempty"`
Aud string `json:"aud,omitempty"`
Exp int `json:"exp,omitempty"`
Iat int `json:"iat,omitempty"`
Sub string `json:"sub,omitempty"`
AtHash string `json:"at_hash,omitempty"`
Email string `json:"email,omitempty"`
EmailVerified string `json:"email_verified,omitempty"`
IsPrivateEmail string `json:"is_private_email,omitempty"`
AuthTime int `json:"auth_time,omitempty"`
NonceSupported bool `json:"nonce_supported,omitempty"`
}

type AppleAuth struct {
AccessToken string `json:"access_token,omitempty"`
TokenType string `json:"token_type,omitempty"`
ExpiresIn int `json:"expires_in,omitempty"`
RefreshToken string `json:"refresh_token,omitempty"`
IdToken string `json:"id_token,omitempty"`
}

0 comments on commit e9018fd

Please sign in to comment.