【问题标题】:Socialite: Merge accounts that have the same email社交名流:合并具有相同电子邮件的帐户
【发布时间】:2020-05-07 14:09:40
【问题描述】:

我使用社交名流为用户提供使用 facebook 或 github 登录的选项。但是当用户使用 facebook 登录,然后使用 github 登录时,会创建 2 个单独的帐户。所以我的问题是,有没有办法将这两个帐户合并为一个?例如,如果一个用 facebook 登录的用户使用相同的电子邮件地址登录 github,则不会创建新帐户,他们只会登录

    <?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateUsersTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('users', function (Blueprint $table) {
            $table->increments('id');
            $table->string('name');
            $table->string('profile');
            $table->string('slug');
            $table->string('provider_id');
            $table->string('email')->unique();
            $table->string('password');
            $table->rememberToken();
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('users');
    }
}

登录/注册码

/**
 * Redirect the user to the provider authentication page.
 *
 * @return Response
 */
public function redirectToProvider($provider)
{
    return Socialite::driver($provider)->redirect();
}

/**
 * Obtain the user information from the provider.
 *
 * @return Response
 */
public function handleProviderCallback($provider)
{
    $SocialUser = Socialite::driver($provider)->stateless()->user();

    $user = $this -> findOrCreateUser($SocialUser,$provider);

    auth()->login($user,true);


    return redirect('/');

}

protected function findOrCreateUser($SocialUser,$provider)
{
    $user = User::firstOrNew(['provider_id' => $SocialUser->id]);

        if ($user->exists) return $user;
    $user->fill([
    'name' => $SocialUser->nickname?:$SocialUser->name,
    'slug' => str_slug($SocialUser->nickname?:$SocialUser->name).'-'.uniqid(),
        'email' => $SocialUser->email,
        'avatar' => $SocialUser->avatar,
        'profile' => Hash::make('no pic'),
        'password' => Hash::make('no need for password token based'),
    // 'website' => 'add a website',
    // 'github_profile' => 'add github profile',
    'email_notifications' => 1
    ])->save();
    $user->assignRole('user');
    \Mail::to($user)->send(new Welcome($user));
    session()->flash('message','Welcome to '.config('app.name').' '.$user->name);
    return $user;
}

}

【问题讨论】:

  • 嗨,克里斯蒂,你能发布一下你的用户表是什么样子的吗?
  • 嗨。刚刚在上面添加了我的用户表
  • 你可以做的是,不要只使用一个provider_id,而是使用facebook_idgithub_idtwitter_id分别存储它们
  • 嗨,Kristi,您能否添加一些重复数据的示例?
  • @kristitanellari 使用 User::where('email', $SocialUser-&gt;email)-&gt;first() 而不是检查 provider_id

标签: php laravel laravel-socialite


【解决方案1】:

我有一个解决方案,它利用了这个问题和答案中的一切精神。

/**
 * @param  string $provider
 * @param  \Laravel\Socialite\Contracts\User $sUser
 * @return \App\User|false
 */
protected function findOrCreateUser($provider, $sUser)
{
    $oauthProvider = OAuthProvider::where('provider', $provider)
        ->where('provider_user_id', $sUser->id)
        ->first();

    if ($oauthProvider) {
        $oauthProvider->update([
            'access_token' => $sUser->token,
            'refresh_token' => $sUser->refreshToken ?? null,
        ]);

        return $oauthProvider->user;
    }

    $user = User::firstWhere('email', $sUser->email);

    if ($user) {
        return $this->createUser($provider, $sUser, $user);
    }

    return $this->createUser($provider, $sUser);
}

/**
 * If a User already exists for the email, skip user creation
 * and add this provider to the list of `$user->oauthProviders`.
 * @param  string $provider
 * @param  \Laravel\Socialite\Contracts\User $sUser
 * @param \App\User $user
 * @return \App\User
 */
protected function createUser($provider, $sUser, User $user = null)
{
    if (!$user) {
        $user = User::create([
            'name' => $sUser->name,
            'email' => $sUser->email,
            'email_verified_at' => now(),
        ]);
    } else if ($user->email_verified_at === null) {
        $user->email_verified_at = now();
        $user->save();
    }

    $user->oauthProviders()->create([
        'provider' => $provider,
        'provider_user_id' => $sUser->id,
        'access_token' => $sUser->token,
        'refresh_token' => $sUser->refreshToken ?? null,
    ]);

    return $user;
}

之前,它会检查是否为User::where('email', $sUser-&gt;email),如果是,则使用“已接收电子邮件”消息拒绝该请求。

使用oauth_providers 表和$user-&gt;oauthProviders 关系(User hasMany OAuthProviders),而不是每次有人使用 oauth 时在 users 表中创建一个新用户,而是将该 oauth 记录附加到现有用户 $user = User::firstWhere('email', $sUser-&gt;email);

如果有人想要更多,我在这里修改了这个 repo 以使 GitHub 和 Twitter oauth 都能工作:https://github.com/cretueusebiu/laravel-vue-spa。以 OAuthController 为基础。

使用上面的代码,我可以通过注册表单注册一个用户来捕获一封电子邮件,然后以 GitHub 和 Twitter 的身份登录,让我的用户加上两个 oauth 提供者。

我的解决方案的大部分魔力来自createUser 的第三个参数。将 createUser 保留为始终创建,然后创建一个名为 addProviderToUser 的新方法是否更好,还有待观察。这可能会稍微多一些代码,但它也可能更简单,对单元测试更友好。

出于科学原因,这也是我的 oauth 重定向和回调方法:

/**
 * Redirect the user to the provider authentication page. Twitter uses OAuth1.0a, and does not support
 * Socialite::driver($provider)->stateless(), so library `abraham/twitteroauth` is used to handle everything.
 *
 * @param  string $provider
 * @return \Illuminate\Http\RedirectResponse
 */
public function redirectToProvider($provider)
{
    if ($provider === 'twitter') {
        $tempId = Str::random(40);

        $connection = new TwitterOAuth(config('services.twitter.client_id'), config('services.twitter.client_secret'));
        $requestToken = $connection->oauth('oauth/request_token', array('oauth_callback' => config('services.twitter.callback_url').'?user='.$tempId));

        \Cache::put($tempId, $requestToken['oauth_token_secret'], 86400); // 86400 seconds = 1 day

        $url = $connection->url('oauth/authorize', array('oauth_token' => $requestToken['oauth_token']));
    } else {
        $url = Socialite::driver($provider)->stateless()->redirect()->getTargetUrl();
    }

    return [
        'url' => $url,
    ];
}

/**
 * Obtain the user information from the provider.
 *
 * @param  string $driver
 * @return \Illuminate\Http\Response
 */
public function handleProviderCallback(Request $request, $provider)
{
    if ($provider === 'twitter') {
        $connection = new TwitterOAuth(config('services.twitter.client_id'), config('services.twitter.client_secret'), $request->oauth_token, \Cache::get($request->user));
        $access_token = $connection->oauth('oauth/access_token', ['oauth_verifier' => $request->oauth_verifier]);

        $connection = new TwitterOAuth(config('services.twitter.client_id'), config('services.twitter.client_secret'), $access_token['oauth_token'], $access_token['oauth_token_secret']);
        $user = $connection->get('account/verify_credentials', ['include_email' => 'true']);

        $user->token = $access_token['oauth_token'];
    } else {
        $user = Socialite::driver($provider)->stateless()->user();
    }
    $user = $this->findOrCreateUser($provider, $user);

    $this->guard()->setToken(
        $token = $this->guard()->login($user)
    );

    return view('oauth/callback', [
        'token' => $token,
        'token_type' => 'bearer',
        'expires_in' => $this->guard()->getPayload()->get('exp') - time(),
    ]);
}

config/services.php

'github' => [
    'client_id' => env('GITHUB_CLIENT_ID'),
    'client_secret' => env('GITHUB_CLIENT_SECRET'),
    'callback_url' => env('GITHUB_CALLBACK_URL'),
    'provider_name' => env('GITHUB_PROVIDER_NAME', 'GitHub'),
],

'twitter' => [
    'client_id' => env('TWITTER_CLIENT_ID'),
    'client_secret' => env('TWITTER_CLIENT_SECRET'),
    'callback_url' => env('TWITTER_CALLBACK_URL'),
    'provider_name' => env('TWITTER_PROVIDER_NAME', 'Twitter'),
],

.env

# localhost
GITHUB_CLIENT_ID=
GITHUB_CLIENT_SECRET=
GITHUB_CALLBACK_URL=https://valet.test/api/oauth/github

TWITTER_CLIENT_ID=
TWITTER_CLIENT_SECRET=
TWITTER_CALLBACK_URL=https://valet.test/api/oauth/twitter/callback

您必须查看上面的示例 repo 以了解这些环境变量是如何被使用的,但提示:查看 spa.blade.php、vuex 和 api.php

【讨论】:

    【解决方案2】:

    尝试将您的代码更改为:

    $user = User::where('email', $SocialUser->email)->first();
    
    if (!empty($user) && in_array($SocialUser->id, $user->provider_id) ) {
    
        session()->flash('message','Welcome to '.config('app.name').' '.$user->name);
    
        return $user;
    
    }
    
    if (empty($user) ) {
    
        $user = User::create([
            'name' => $SocialUser->nickname?:$SocialUser->name,
            'slug' => str_slug($SocialUser->nickname?:$SocialUser->name).'-'.uniqid(),
            'email' => $SocialUser->email,
            'avatar' => $SocialUser->avatar,
            'profile' => Hash::make('no pic'),
            'password' => Hash::make('no need for password token based'),
            // 'website' => 'add a website',
            // 'github_profile' => 'add github profile',
           'email_notifications' => 1,
           'provider_id' => [$SocialUser->id]
        ]);
    
        $user->assignRole('user');
    
        \Mail::to($user)->send(new Welcome($user));
    
        session()->flash('message','Welcome to '.config('app.name').' '.$user->name);
    
        return $user;
    
    }
    
    $providers = array_push($user->provider_id, $SocialUser->id);
    
    $user->update([
        'provider_id' => $providers
    ]);
    
    session()->flash('message','Welcome to '.config('app.name').' '.$user->name);
    
    return $user;
    

    您最好也将其添加到您的用户模型中:

    protected $casts = [
        'provider_id' => 'array'
    ];
    

    希望对你有帮助

    【讨论】:

      猜你喜欢
      • 2019-07-17
      • 1970-01-01
      • 2020-08-07
      • 2021-12-10
      • 1970-01-01
      • 2020-04-10
      • 1970-01-01
      • 1970-01-01
      • 2021-05-18
      相关资源
      最近更新 更多