PHP项目Socialite多提供商

wen PHP项目 1

本文目录导读:

PHP项目Socialite多提供商

  1. 安装与配置
  2. 路由配置
  3. 控制器实现
  4. 前端视图
  5. 高级功能实现
  6. 异常处理与日志
  7. 环境变量配置 (.env)
  8. 注意事项

我来帮您梳理PHP项目中使用Socialite实现多提供商登录的完整方案。

安装与配置

安装Socialite

composer require laravel/socialite

配置文件 (config/services.php)

return [
    'github' => [
        'client_id' => env('GITHUB_CLIENT_ID'),
        'client_secret' => env('GITHUB_CLIENT_SECRET'),
        'redirect' => env('GITHUB_REDIRECT_URI'),
    ],
    'google' => [
        'client_id' => env('GOOGLE_CLIENT_ID'),
        'client_secret' => env('GOOGLE_CLIENT_SECRET'),
        'redirect' => env('GOOGLE_REDIRECT_URI'),
    ],
    'facebook' => [
        'client_id' => env('FACEBOOK_CLIENT_ID'),
        'client_secret' => env('FACEBOOK_CLIENT_SECRET'),
        'redirect' => env('FACEBOOK_REDIRECT_URI'),
    ],
    // 添加更多提供商...
];

路由配置

// routes/web.php
Route::prefix('auth')->group(function () {
    // 通用重定向路由
    Route::get('{provider}/redirect', [AuthController::class, 'redirect'])->name('auth.provider.redirect');
    // 通用回调路由
    Route::get('{provider}/callback', [AuthController::class, 'callback'])->name('auth.provider.callback');
});

控制器实现

基础控制器

<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Laravel\Socialite\Facades\Socialite;
use App\Models\User;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Str;
class AuthController extends Controller
{
    // 支持的提供商列表
    protected $providers = [
        'github', 'google', 'facebook', 'twitter', 'linkedin',
        'qq', 'wechat', 'weibo'
    ];
    /**
     * 重定向到提供商
     */
    public function redirect($provider)
    {
        // 验证提供商是否支持
        if (!in_array($provider, $this->providers)) {
            return redirect()->route('login')->with('error', '不支持的登录方式');
        }
        // 特殊处理微信扫码
        if ($provider === 'wechat') {
            return Socialite::with($provider)->scopes(['snsapi_login'])->redirect();
        }
        return Socialite::driver($provider)->redirect();
    }
    /**
     * 回调处理
     */
    public function callback($provider)
    {
        try {
            // 验证提供商
            if (!in_array($provider, $this->providers)) {
                throw new \Exception('不支持的登录方式');
            }
            // 获取第三方用户信息
            $socialUser = Socialite::driver($provider)->user();
            // 处理用户登录/注册
            $user = $this->findOrCreateUser($socialUser, $provider);
            // 登录用户
            Auth::login($user, true);
            // 重定向到首页
            return redirect()->intended('/dashboard');
        } catch (\Exception $e) {
            return redirect()->route('login')->with('error', '登录失败: ' . $e->getMessage());
        }
    }
    /**
     * 查找或创建用户
     */
    protected function findOrCreateUser($socialUser, $provider)
    {
        // 先尝试通过提供商ID查找
        $user = User::where('provider', $provider)
                    ->where('provider_id', $socialUser->getId())
                    ->first();
        if ($user) {
            // 更新用户信息
            $this->updateUserInfo($user, $socialUser, $provider);
            return $user;
        }
        // 尝试通过邮箱查找
        if ($socialUser->getEmail()) {
            $user = User::where('email', $socialUser->getEmail())->first();
            if ($user) {
                // 绑定提供商信息
                $user->update([
                    'provider' => $provider,
                    'provider_id' => $socialUser->getId(),
                ]);
                $this->updateUserInfo($user, $socialUser, $provider);
                return $user;
            }
        }
        // 创建新用户
        return $this->createUser($socialUser, $provider);
    }
    /**
     * 创建新用户
     */
    protected function createUser($socialUser, $provider)
    {
        $user = User::create([
            'name' => $socialUser->getName() ?? $socialUser->getNickname(),
            'email' => $socialUser->getEmail(),
            'provider' => $provider,
            'provider_id' => $socialUser->getId(),
            'avatar' => $socialUser->getAvatar(),
            'password' => bcrypt(Str::random(16)), // 随机密码
        ]);
        return $user;
    }
    /**
     * 更新用户信息
     */
    protected function updateUserInfo($user, $socialUser, $provider)
    {
        $updateData = [
            'avatar' => $socialUser->getAvatar() ?? $user->avatar,
            'provider' => $provider,
            'provider_id' => $socialUser->getId(),
        ];
        // 更新昵称(如果用户没有设置过)
        if (!$user->name || $user->name === 'User') {
            $updateData['name'] = $socialUser->getName() ?? $socialUser->getNickname();
        }
        $user->update($updateData);
    }
}

用户模型迁移

// database/migrations/xxxx_add_social_fields_to_users_table.php
Schema::table('users', function (Blueprint $table) {
    $table->string('provider')->nullable()->after('email');
    $table->string('provider_id')->nullable()->after('provider');
    $table->string('avatar')->nullable()->after('provider_id');
    // 添加索引
    $table->unique(['provider', 'provider_id']);
});

前端视图

登录页面

{{-- resources/views/auth/login.blade.php --}}
@extends('layouts.app')
@section('content')
<div class="container">
    <div class="row justify-content-center">
        <div class="col-md-8">
            <div class="card">
                <div class="card-header">{{ __('登录') }}</div>
                <div class="card-body">
                    {{-- 社交登录按钮 --}}
                    <div class="mb-4">
                        <h5>快捷登录</h5>
                        <div class="d-flex flex-wrap gap-2">
                            <a href="{{ route('auth.provider.redirect', 'github') }}" class="btn btn-dark">
                                <i class="fab fa-github"></i> GitHub
                            </a>
                            <a href="{{ route('auth.provider.redirect', 'google') }}" class="btn btn-danger">
                                <i class="fab fa-google"></i> Google
                            </a>
                            <a href="{{ route('auth.provider.redirect', 'facebook') }}" class="btn btn-primary">
                                <i class="fab fa-facebook"></i> Facebook
                            </a>
                            <a href="{{ route('auth.provider.redirect', 'wechat') }}" class="btn btn-success">
                                <i class="fab fa-weixin"></i> 微信
                            </a>
                        </div>
                    </div>
                    {{-- 传统登录表单 --}}
                    <form method="POST" action="{{ route('login') }}">
                        @csrf
                        {{-- 表单内容 --}}
                    </form>
                </div>
            </div>
        </div>
    </div>
</div>
@endsection

高级功能实现

令牌刷新中间件

<?php
namespace App\Http\Middleware;
use Closure;
use Laravel\Socialite\Facades\Socialite;
use Illuminate\Support\Facades\Cache;
class RefreshSocialToken
{
    public function handle($request, Closure $next)
    {
        $user = auth()->user();
        if ($user && $user->provider) {
            // 检查token是否需要刷新
            $tokenExpiry = Cache::get("token_expiry_{$user->id}");
            if ($tokenExpiry && now()->gt($tokenExpiry)) {
                // 刷新token
                $this->refreshToken($user);
            }
        }
        return $next($request);
    }
    protected function refreshToken($user)
    {
        try {
            $provider = Socialite::driver($user->provider);
            // 获取新的token
            $newToken = $provider->refreshToken($user->social_token);
            // 更新用户token
            $user->update([
                'social_token' => $newToken->access_token,
            ]);
            // 设置新的过期时间
            Cache::put(
                "token_expiry_{$user->id}", 
                now()->addSeconds($newToken->expires_in)
            );
        } catch (\Exception $e) {
            Log::error("Token refresh failed for user {$user->id}: " . $e->getMessage());
        }
    }
}

多提供商管理控制器

<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Laravel\Socialite\Facades\Socialite;
class ProviderManagementController extends Controller
{
    /**
     * 查看已绑定的提供商
     */
    public function index()
    {
        $user = auth()->user();
        $boundProviders = $this->getBoundProviders($user);
        return view('auth.providers', compact('boundProviders'));
    }
    /**
     * 绑定新的提供商
     */
    public function bind($provider)
    {
        session(['bind_provider' => true]);
        return Socialite::driver($provider)->redirect();
    }
    /**
     * 处理绑定回调
     */
    public function bindCallback($provider)
    {
        $socialUser = Socialite::driver($provider)->user();
        $user = auth()->user();
        // 检查是否已被其他用户绑定
        $existingUser = User::where('provider', $provider)
                           ->where('provider_id', $socialUser->getId())
                           ->first();
        if ($existingUser && $existingUser->id !== $user->id) {
            return redirect()->back()->with('error', '该账号已被其他用户绑定');
        }
        // 绑定提供商到当前用户
        $user->update([
            'provider' => $provider,
            'provider_id' => $socialUser->getId(),
        ]);
        return redirect()->route('auth.providers')->with('success', '绑定成功');
    }
    /**
     * 解绑提供商
     */
    public function unbind($provider)
    {
        $user = auth()->user();
        // 防止解绑最后一个登录方式
        if ($user->password === null && $this->getBoundProviders($user)->count() <= 1) {
            return redirect()->back()->with('error', '至少保留一种登录方式');
        }
        $user->update([
            'provider' => null,
            'provider_id' => null,
        ]);
        return redirect()->route('auth.providers')->with('success', '解绑成功');
    }
    /**
     * 获取用户绑定的提供商
     */
    protected function getBoundProviders($user)
    {
        $providers = collect();
        if ($user->provider) {
            $providers->push([
                'name' => $user->provider,
                'bound_at' => $user->updated_at,
            ]);
        }
        return $providers;
    }
}

异常处理与日志

// app/Exceptions/Handler.php
public function register()
{
    $this->reportable(function (InvalidStateException $e) {
        Log::error('Socialite state mismatch', [
            'ip' => request()->ip(),
            'user_agent' => request()->userAgent(),
        ]);
        return redirect()->route('login')->with('error', '登录状态失效,请重试');
    });
}

环境变量配置 (.env)

# GitHub
GITHUB_CLIENT_ID=your_github_client_id
GITHUB_CLIENT_SECRET=your_github_client_secret
GITHUB_REDIRECT_URI=${APP_URL}/auth/github/callback
# Google
GOOGLE_CLIENT_ID=your_google_client_id
GOOGLE_CLIENT_SECRET=your_google_client_secret
GOOGLE_REDIRECT_URI=${APP_URL}/auth/google/callback
# Facebook
FACEBOOK_CLIENT_ID=your_facebook_app_id
FACEBOOK_CLIENT_SECRET=your_facebook_app_secret
FACEBOOK_REDIRECT_URI=${APP_URL}/auth/facebook/callback

注意事项

  1. 安全性: 使用HTTPS保证数据传输安全
  2. 状态验证: Socialite自动处理状态验证,防止CSRF
  3. 邮箱验证: 部分提供商可能不提供邮箱,需要额外处理
  4. 错误处理: 完善异常捕获和用户提示
  5. 限流: 对第三方登录接口做限流处理

这个方案支持多种社交媒体登录,包括GitHub、Google、Facebook、微信等,并且具有良好的扩展性,如果需要添加新的提供商,只需在配置文件中添加相应的配置即可。

抱歉,评论功能暂时关闭!