package providers import ( "context" "crypto/rand" "encoding/base64" "encoding/json" "fmt" "net/http" "net/url" "time" ) // FacebookProvider Facebook OAuth提供者 type FacebookProvider struct { AppID string AppSecret string RedirectURI string } // FacebookAuthURLResponse Facebook授权URL响应 type FacebookAuthURLResponse struct { URL string `json:"url"` State string `json:"state"` Redirect string `json:"redirect,omitempty"` } // FacebookTokenResponse Facebook Token响应 type FacebookTokenResponse struct { AccessToken string `json:"access_token"` TokenType string `json:"token_type"` ExpiresIn int `json:"expires_in"` } // FacebookUserInfo Facebook用户信息 type FacebookUserInfo struct { ID string `json:"id"` Name string `json:"name"` Email string `json:"email"` Picture struct { Data struct { URL string `json:"url"` Width int `json:"width"` Height int `json:"height"` IsSilhouette bool `json:"is_silhouette"` } `json:"data"` } `json:"picture"` } // NewFacebookProvider 创建Facebook OAuth提供者 func NewFacebookProvider(appID, appSecret, redirectURI string) *FacebookProvider { return &FacebookProvider{ AppID: appID, AppSecret: appSecret, RedirectURI: redirectURI, } } // GenerateState 生成随机状态码 func (f *FacebookProvider) GenerateState() (string, error) { b := make([]byte, 32) _, err := rand.Read(b) if err != nil { return "", err } return base64.URLEncoding.EncodeToString(b), nil } // GetAuthURL 获取Facebook授权URL func (f *FacebookProvider) GetAuthURL(state string) (*FacebookAuthURLResponse, error) { authURL := fmt.Sprintf( "https://www.facebook.com/v18.0/dialog/oauth?client_id=%s&redirect_uri=%s&scope=email,public_profile&response_type=code&state=%s", f.AppID, url.QueryEscape(f.RedirectURI), state, ) return &FacebookAuthURLResponse{ URL: authURL, State: state, Redirect: f.RedirectURI, }, nil } // ExchangeCode 用授权码换取访问令牌 func (f *FacebookProvider) ExchangeCode(ctx context.Context, code string) (*FacebookTokenResponse, error) { tokenURL := fmt.Sprintf( "https://graph.facebook.com/v18.0/oauth/access_token?client_id=%s&client_secret=%s&redirect_uri=%s&code=%s", f.AppID, f.AppSecret, url.QueryEscape(f.RedirectURI), code, ) req, err := http.NewRequestWithContext(ctx, "GET", tokenURL, nil) if err != nil { return nil, fmt.Errorf("create request failed: %w", err) } client := &http.Client{Timeout: 10 * time.Second} resp, err := client.Do(req) if err != nil { return nil, fmt.Errorf("request failed: %w", err) } defer resp.Body.Close() body, err := readOAuthResponseBody(resp) if err != nil { return nil, fmt.Errorf("read response failed: %w", err) } var tokenResp FacebookTokenResponse if err := json.Unmarshal(body, &tokenResp); err != nil { return nil, fmt.Errorf("parse token response failed: %w", err) } return &tokenResp, nil } // GetUserInfo 获取Facebook用户信息 func (f *FacebookProvider) GetUserInfo(ctx context.Context, accessToken string) (*FacebookUserInfo, error) { // 请求用户信息(包括头像) userInfoURL := fmt.Sprintf( "https://graph.facebook.com/v18.0/me?fields=id,name,email,picture&access_token=%s", accessToken, ) req, err := http.NewRequestWithContext(ctx, "GET", userInfoURL, nil) if err != nil { return nil, fmt.Errorf("create request failed: %w", err) } client := &http.Client{Timeout: 10 * time.Second} resp, err := client.Do(req) if err != nil { return nil, fmt.Errorf("request failed: %w", err) } defer resp.Body.Close() body, err := readOAuthResponseBody(resp) if err != nil { return nil, fmt.Errorf("read response failed: %w", err) } // Facebook错误响应 var errResp struct { Error struct { Message string `json:"message"` Type string `json:"type"` Code int `json:"code"` ErrorSubcode int `json:"error_subcode,omitempty"` } `json:"error"` } if err := json.Unmarshal(body, &errResp); err == nil && errResp.Error.Message != "" { return nil, fmt.Errorf("facebook api error: %s", errResp.Error.Message) } var userInfo FacebookUserInfo if err := json.Unmarshal(body, &userInfo); err != nil { return nil, fmt.Errorf("parse user info failed: %w", err) } return &userInfo, nil } // ValidateToken 验证访问令牌是否有效 func (f *FacebookProvider) ValidateToken(ctx context.Context, accessToken string) (bool, error) { userInfo, err := f.GetUserInfo(ctx, accessToken) if err != nil { return false, err } return userInfo != nil && userInfo.ID != "", nil } // GetLongLivedToken 获取长期有效的访问令牌(60天) func (f *FacebookProvider) GetLongLivedToken(ctx context.Context, shortLivedToken string) (*FacebookTokenResponse, error) { tokenURL := fmt.Sprintf( "https://graph.facebook.com/v18.0/oauth/access_token?grant_type=fb_exchange_token&client_id=%s&client_secret=%s&fb_exchange_token=%s", f.AppID, f.AppSecret, shortLivedToken, ) req, err := http.NewRequestWithContext(ctx, "GET", tokenURL, nil) if err != nil { return nil, fmt.Errorf("create request failed: %w", err) } client := &http.Client{Timeout: 10 * time.Second} resp, err := client.Do(req) if err != nil { return nil, fmt.Errorf("request failed: %w", err) } defer resp.Body.Close() body, err := readOAuthResponseBody(resp) if err != nil { return nil, fmt.Errorf("read response failed: %w", err) } var tokenResp FacebookTokenResponse if err := json.Unmarshal(body, &tokenResp); err != nil { return nil, fmt.Errorf("parse token response failed: %w", err) } return &tokenResp, nil }