相对应的客户端文档可以先阅读这里:
https://developers.facebook.com/docs/facebook-login/limited-login/ios
如果你选择的是
https://developers.facebook.com/docs/facebook-login/ios?locale=zh_CN
这个登录,是没有接入服务端逻辑的,赶快停止code,请选择上面的有限登录。
接入后,客户端会获得facebook返回的一个token和userId,接下来就该服务端进行校验了。
一、验证流程
Facebook的服务端校验逻辑与apple几乎一样
- 获取appId(注册应用时拿到的);
- 获取公钥列表(可以直接缓存起来,然后等解析token的时候拿出来使用);
- 使用公钥解析token;
- 把userId 、 appId等信息与解析出的token参数做对比,校验完成。
服务端文档见:https://developers.facebook.com/docs/facebook-login/limited-login/token/validating
二、go实现
package third_party_jwt_util
import (
"crypto/rsa"
"encoding/base64"
"encoding/json"
"errors"
"github.com/dgrijalva/jwt-go"
log "github.com/sirupsen/logrus"
"io/ioutil"
"math/big"
// 这里的三个包可以进行替换使用
e "错误处理的包"
"缓存的包"
httpPool "http的包"
"strings"
)
const (
PublicKeyReqUrl = "https://appleid.apple.com/auth/keys"
MetaPublicKeyReqUrl = "https://www.facebook.com/.well-known/oauth/openid/jwks"
)
type JwtClaims struct {
jwt.StandardClaims
}
type JwtHeader struct {
Kid string `json:"kid"`
Alg string `json:"alg"`
}
// VerifyIdentityToken / 认证客户端传递过来的token是否有效 ///
// AppleIssuer = "https://appleid.apple.com"
// MetaIssuer = "https://www.facebook.com"
// clientId = 申请到的appId
// token = 前端获取的token
// cliUserId = 前端传过来的userId
func VerifyIdentityToken(issuer string, clientId string, cliToken string, cliUserID string) error {
// 数据由 头部、载荷、签名 三部分组成
log.Debug(clientId, cliToken, cliUserID)
cliTokenArr := strings.Split(cliToken, ".")
if len(cliTokenArr) < 3 {
return errors.New("cliToken Split err")
}
// 解析cliToken的header获取kid
cliHeader, err := jwt.DecodeSegment(cliTokenArr[0])
if err != nil {
log.Error(err)
return err
}
var jHeader JwtHeader
err = json.Unmarshal(cliHeader, &jHeader)
if err != nil {
log.Error(err)
return err
}
println(jHeader.Kid)
// 效验pubKey 及 token
token, err := jwt.ParseWithClaims(cliToken, &JwtClaims{}, func(token *jwt.Token) (interface{}, error) {
return GetPublicKey(jHeader.Kid), nil
})
if err != nil {
log.Error(err)
return err
}
// 信息验证
if claims, ok := token.Claims.(*JwtClaims); ok && token.Valid {
log.Debug("Audience:" + claims.Audience)
log.Debug("Issuer:" + claims.Issuer)
log.Debug("Subject:" + claims.Subject)
log.Debug(claims.ExpiresAt)
if claims.Issuer != issuer || claims.Audience != clientId || claims.Subject != cliUserID {
log.Error("校验不通过")
return errors.New("verify token info fail, info is not match")
}
// here is verified ok !
} else {
return errors.New("token claims parse fail")
}
return nil
}
/ 以下⬇️获取key /
type JwtKeys struct {
Kty string `json:"kty"`
Kid string `json:"kid"`
Use string `json:"use"`
Alg string `json:"alg"`
N string `json:"n"`
E string `json:"e"`
}
func GetPublicKey(keyId string) *rsa.PublicKey {
log.Debug("get public key, key id:" + keyId)
// 获取验证所需的公钥
var pubKey rsa.PublicKey
var keys JwtKeys
if key, err := cache.BigCache.Get(keyId); err == nil {
keys = key.(JwtKeys)
nBin, _ := base64.RawURLEncoding.DecodeString(keys.N)
nData := new(big.Int).SetBytes(nBin)
eBin, _ := base64.RawURLEncoding.DecodeString(keys.E)
eData := new(big.Int).SetBytes(eBin)
pubKey.N = nData
pubKey.E = int(eData.Uint64())
return &pubKey
} else {
panic(e.NewApiError(e.WrongAppId))
}
}
// 项目启动执行
func AppleKeyInit() {
log.Info("Apple Key Init...")
// 把key 按keyId+对象的形式 放在缓存里
response, err := httpPool.Client.Get(PublicKeyReqUrl)
if err != nil {
panic(err)
}
defer response.Body.Close()
data, err := ioutil.ReadAll(response.Body)
var jKeys map[string][]JwtKeys
err = json.Unmarshal(data, &jKeys)
if err != nil {
panic(err)
}
for _, keys := range jKeys {
for _, key := range keys {
cache.BigCache.Set(key.Kid, key)
}
}
}
// 项目启动执行
func MetaKeyInit() {
log.Info("Meta Key Init...")
// 把key 按keyId+对象的形式 放在缓存里
response, err := httpPool.Client.Get(MetaPublicKeyReqUrl)
if err != nil {
panic(err)
}
defer response.Body.Close()
data, err := ioutil.ReadAll(response.Body)
var jKeys map[string][]JwtKeys
err = json.Unmarshal(data, &jKeys)
if err != nil {
panic(err)
}
for _, keys := range jKeys {
for _, key := range keys {
println(key.Kid)
cache.BigCache.Set(key.Kid, key)
}
}
}