Skip to content

Commit e9018fd

Browse files
committed
Feat.
1 parent 10dd1fc commit e9018fd

File tree

4 files changed

+200
-0
lines changed

4 files changed

+200
-0
lines changed

.DS_Store

6 KB
Binary file not shown.

README.md

+43
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,45 @@
11
# appleTools
22
苹果登陆相关工具
3+
4+
开发APP苹果登陆功能时参考各种渠道的代码然后顺手封装一下。
5+
6+
主要参考:https://github.com/tptpp/sign-in-with-apple
7+
8+
在此基础上增加了解密苹果最终返回数据的id_token,用于验证客户端post过来的userIdentifier
9+
10+
其中id_token解密后有个sub字段,该字段一般和userIdentifier一致
11+
12+
通过对比这两个字段是否相等来处理后续登陆业务
13+
14+
比如如下使用方式
15+
16+
```go
17+
// @Summary 苹果登陆
18+
// @Produce application/json
19+
// @Param data body request.AppleLoginCode true "苹果登陆"
20+
// @Success 200 {string} string "{"success":true,"data":{},"msg":"登陆成功"}"
21+
// @Router /appLogin/AppleLoginCode [post]
22+
func AppleLogin(c *gin.Context) {
23+
//一个请求信息的结构体,接收authorizationCode和userIdentifier
24+
var A request.AppleLoginCodeAndId
25+
_ = c.ShouldBindJSON(&A)
26+
27+
//生成client_secret,参数:苹果账户的KeyId,TeamId, ClientID, KeySecret
28+
clientSecret := appleTools.GetAppleSecret(KeyId,TeamId, ClientID, KeySecret)
29+
30+
//获取用户信息,参数:ClientID, 上面的clientSecret, authorizationCode
31+
data, err := appleTools.GetAppleLoginData(ClientID, clientSecret, A.Code)
32+
33+
//检查用户信息,参数:上面的data,客户端传过来的userIdentifier,上面的err
34+
check := appleTools.CheckAppleID(data, A.ID, err)
35+
if check == false {
36+
response.FailWithMessage("APPLE登陆失败[error 1],请重试或使用其他方式登陆", c)
37+
return
38+
}
39+
40+
fmt.Println("苹果校验结果OK")
41+
42+
//你的登陆业务
43+
44+
}
45+
```

applefunc.go

+134
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
package appleTools
2+
3+
import (
4+
"crypto/ecdsa"
5+
"crypto/x509"
6+
"encoding/json"
7+
"encoding/pem"
8+
"github.com/dgrijalva/jwt-go"
9+
"github.com/pkg/errors"
10+
"io/ioutil"
11+
"net/http"
12+
"net/url"
13+
"strings"
14+
"time"
15+
)
16+
17+
//请求苹果用户信息
18+
func GetAppleLoginData(clientId string, clientSecret string, code string) ([]byte, error) {
19+
params := map[string]string{
20+
"client_id": clientId,
21+
"client_secret": clientSecret,
22+
"code": code,
23+
"grant_type": "authorization_code",
24+
"redirect_uri": "",
25+
}
26+
form := url.Values{}
27+
for k, v := range params {
28+
form.Set(k, v)
29+
}
30+
31+
var request *http.Request
32+
var err error
33+
if request, err = http.NewRequest("POST", "https://appleid.apple.com/auth/token",
34+
strings.NewReader(form.Encode())); err != nil {
35+
return nil, err
36+
}
37+
38+
request.Header.Set("Content-Type", "application/x-www-form-urlencoded")
39+
40+
var response *http.Response
41+
if response, err = http.DefaultClient.Do(request); nil != err {
42+
return nil, err
43+
}
44+
defer response.Body.Close()
45+
46+
data, err := ioutil.ReadAll(response.Body)
47+
48+
return data, err
49+
}
50+
51+
//生成client_secret
52+
func GetAppleSecret(keyId string, teamId string, clientId string, keySecret string) string {
53+
token := &jwt.Token{
54+
Header: map[string]interface{}{
55+
"alg": "ES256",
56+
"kid": keyId,
57+
},
58+
Claims: jwt.MapClaims{
59+
"iss": teamId,
60+
"iat": time.Now().Unix(),
61+
// constraint: exp - iat <= 180 days
62+
"exp": time.Now().Add(24 * time.Hour).Unix(),
63+
"aud": "https://appleid.apple.com",
64+
"sub": clientId,
65+
},
66+
Method: jwt.SigningMethodES256,
67+
}
68+
69+
ecdsaKey, _ := AuthKeyFromBytes([]byte(keySecret))
70+
ss, _ := token.SignedString(ecdsaKey)
71+
return ss
72+
}
73+
74+
//JWT加密
75+
func AuthKeyFromBytes(key []byte) (*ecdsa.PrivateKey, error) {
76+
var err error
77+
78+
// Parse PEM block
79+
var block *pem.Block
80+
if block, _ = pem.Decode(key); block == nil {
81+
return nil, errors.New("token: AuthKey must be a valid .p8 PEM file")
82+
}
83+
84+
// Parse the key
85+
var parsedKey interface{}
86+
if parsedKey, err = x509.ParsePKCS8PrivateKey(block.Bytes); err != nil {
87+
return nil, err
88+
}
89+
90+
var pkey *ecdsa.PrivateKey
91+
var ok bool
92+
if pkey, ok = parsedKey.(*ecdsa.PrivateKey); !ok {
93+
return nil, errors.New("token: AuthKey must be of type ecdsa.PrivateKey")
94+
}
95+
96+
return pkey, nil
97+
}
98+
99+
//解密苹果返回的data中的id_token中的用户id和客户端post的id否一致
100+
func CheckAppleID(data []byte, id string, err error) bool {
101+
//fmt.Println("苹果服务器返回信息为:", string(data))
102+
if err != nil || strings.Contains(string(data), "error") {
103+
//fmt.Println("APPLE登陆失败[error 1],请重试或使用其他方式登陆")
104+
return false
105+
}
106+
//fmt.Println("苹果服务器返回信息OK")
107+
Au := AppleAuth{}
108+
err = json.Unmarshal(data, &Au)
109+
if err != nil {
110+
//fmt.Println("APPLE登陆失败[error 2],请重试或使用其他方式登陆")
111+
return false
112+
}
113+
//fmt.Println("结构体赋值OK", Au)
114+
var userDecode []byte
115+
parts := strings.Split(Au.IdToken, ".")
116+
userDecode, err = jwt.DecodeSegment(parts[1])
117+
if err != nil {
118+
//fmt.Println("APPLE登陆失败[error 3],请重试或使用其他方式登陆")
119+
return false
120+
}
121+
//fmt.Println("Au.IdToken解码OK", userDecode)
122+
It := AppleIdToken{}
123+
err = json.Unmarshal(userDecode, &It)
124+
if err != nil {
125+
//fmt.Println("APPLE登陆失败[error 4],请重试或使用其他方式登陆")
126+
return false
127+
}
128+
//fmt.Println("结构体赋值OK", It)
129+
if It.Sub != id {
130+
//fmt.Println("APPLE登陆失败[error 5],请重试或使用其他方式登陆")
131+
return false
132+
}
133+
return true
134+
}

model.go

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package appleTools
2+
3+
type AppleIdToken struct {
4+
Iss string `json:"iss,omitempty"`
5+
Aud string `json:"aud,omitempty"`
6+
Exp int `json:"exp,omitempty"`
7+
Iat int `json:"iat,omitempty"`
8+
Sub string `json:"sub,omitempty"`
9+
AtHash string `json:"at_hash,omitempty"`
10+
Email string `json:"email,omitempty"`
11+
EmailVerified string `json:"email_verified,omitempty"`
12+
IsPrivateEmail string `json:"is_private_email,omitempty"`
13+
AuthTime int `json:"auth_time,omitempty"`
14+
NonceSupported bool `json:"nonce_supported,omitempty"`
15+
}
16+
17+
type AppleAuth struct {
18+
AccessToken string `json:"access_token,omitempty"`
19+
TokenType string `json:"token_type,omitempty"`
20+
ExpiresIn int `json:"expires_in,omitempty"`
21+
RefreshToken string `json:"refresh_token,omitempty"`
22+
IdToken string `json:"id_token,omitempty"`
23+
}

0 commit comments

Comments
 (0)