Laravel 社交名流 - 重定向前保存 URL
Laravel Socialite - Save URL before redirection
我正在使用 Laravel 5.4
和 Socialite
,因此我的用户可以使用 Facebook
登录。
我的网站使用子域 newyork.example.com
、paris.example.com
example.com
回调登录的 Facebook URL
重定向必须是 absolute
所以我设置 http://example.com
login/facebook
public function redirectToProvider()
{
$_SESSION['originalURL'] ="http://paris.example.com";
return Socialite::driver('facebook')
->scopes(['rsvp_event', 'public_profile'])
->redirect();
}
login/facebook/回调
public function handleProviderCallback(SocialAccountService $service)
{
$user = $service->createOrGetUser(Socialite::driver('facebook')->user());
// $user->token;
Auth::login($user, true);
$originalURL = $_SESSION['originalURL'];
return redirect()->to($originalURL);
}
问题
当我在route
login/facebook时我可以查看原URLparis.example.com
和HTTP_POST
当我在路线 login/facebook/callback
中时,HTTP_POST
是 example.com
,因为重定向的 URL 是 example.com
。我尝试将 URL 保存在 session var
中,但 $_SESSION
是空的。
问题
如何在 facebook 登录回调重定向后获取原始 url。 ?因此,如果我使用 paris.example.com
开始登录过程,我将被重定向到 example.com
,然后我将保存 url 重定向到
sessions.php
'cookie' => 'laravel_session',
/*
|--------------------------------------------------------------------------
| Session Cookie Path
|--------------------------------------------------------------------------
|
| The session cookie path determines the path for which the cookie will
| be regarded as available. Typically, this will be the root path of
| your application but you are free to change this when necessary.
|
*/
'path' => '/',
/*
|--------------------------------------------------------------------------
| Session Cookie Domain
|--------------------------------------------------------------------------
|
| Here you may change the domain of the cookie used to identify a session
| in your application. This will determine which domains the cookie is
| available to in your application. A sensible default has been set.
|
*/
'domain' => env('SESSION_DOMAIN', '.localhost.com'),
/*
|--------------------------------------------------------------------------
| HTTPS Only Cookies
|--------------------------------------------------------------------------
|
| By setting this option to true, session cookies will only be sent back
| to the server if the browser has a HTTPS connection. This will keep
| the cookie from being sent to you if it can not be done securely.
|
*/
'secure' => env('SESSION_SECURE_COOKIE', false),
/*
|--------------------------------------------------------------------------
| HTTP Access Only
|--------------------------------------------------------------------------
|
| Setting this value to true will prevent JavaScript from accessing the
| value of the cookie and the cookie will only be accessible through
| the HTTP protocol. You are free to modify this option if needed.
|
*/
'http_only' => true,
关键不是用$_SESSION
而是session(['city' => 'paris'])
;和 session('city')
检索值。
如果您不能在网站之间共享会话,这里有一种方法可以使用 state
OAuth 参数来携带值。
此代码已在自定义 OAuth 提供程序中测试。这是 Facebook 实现的样子(此特定代码未经测试)。
更改 state
的工作方式时需要牢记安全隐患。这是一篇关于 http://www.thread-safe.com/2014/05/the-correct-use-of-state-parameter-in.html There's even an IETF draft on how one would store and sign data into the state
parameter https://datatracker.ietf.org/doc/html/draft-bradley-oauth-jwt-encoded-state-00 的有趣文章(我下面的实现使用 JSON 并且未签名)。
<?php
namespace App\Socialite;
use Laravel\Socialite\Two\FacebookProvider;
use Laravel\Socialite\Two\User;
class CustomFacebookProvider extends FacebookProvider
{
protected $statePreviousUrl = null;
public function withPreviousUrl($url)
{
$this->statePreviousUrl = $url;
return $this;
}
protected function getState()
{
// The state becomes a JSON object with both the XRSF protection token and the url
return json_encode([
'state' => parent::getState(),
'url' => $this->statePreviousUrl,
]);
}
protected function hasInvalidState()
{
if ($this->isStateless()) {
return false;
}
$storedState = $this->request->session()->pull('state');
$requestState = $this->request->input('state');
$requestStateData = json_decode($requestState, true);
// If the JSON is valid we extract the url here
if (!is_null($requestStateData) && array_key_exists('url', $requestStateData)) {
// Don't forget, this value is unsafe. Do additional checks before redirecting to that url
$this->statePreviousUrl = $requestStateData['url'];
}
// If you don't share your session between your instances you can play it "stateless" by always returning false here
// Doing so you loose all XRSF protection ! (but this might be the only way if you don't share your cookies)
// return false;
// If the session is shared, we finish by checking the full state
// We compare the full json objects, no need to extract the state parameter
return ! (strlen($storedState) > 0 && $requestState === $storedState);
}
protected function mapUserToObject(array $user)
{
return (new User)->setRaw($user)->map([
// Data here will vary from provider to provider. The Facebook one is a bit more complex
'id' => $user['id'],
'email' => $user['email'],
// We add the extracted URL here so it can be access from the controller
'previous_url' => $this->statePreviousUrl,
]);
}
}
注册自定义控制器:
<?php
namespace App\Socialite;
use Illuminate\Support\ServiceProvider as BaseServiceProvider;
use Laravel\Socialite\Contracts\Factory;
class ServiceProvider extends BaseServiceProvider
{
public function boot()
{
// @see https://medium.com/laravel-news/adding-auth-providers-to-laravel-socialite-ca0335929e42
$socialite = $this->app->make(Factory::class);
$socialite->extend(
'custom-facebook',
function ($app) use ($socialite) {
$config = $app['config']['services.facebook'];
return $socialite->buildProvider(CustomFacebookProvider::class, $config);
}
);
}
}
用法:
<?php
namespace App\Http\Controllers;
use App\User;
use Laravel\Socialite\Contracts\Factory;
class FacebookLoginController extends Controller
{
/**
* @var Factory
*/
protected $socialite;
public function __construct(Factory $socialite)
{
$this->socialite = $socialite;
}
public function redirectToProvider()
{
return $this->socialite->driver('custom-facebook')->withPreviousUrl('https://paris.example.com/')->redirect();
}
public function handleProviderCallback()
{
$data = $this->socialite->driver('custom-facebook')->user();
dd($data->previous_url);
}
}
我是这样用的,很管用
public function socialConnectRedirect($type, Request $request)
{
Session::put('redirect', $request->input('redirectTo'));
if($type=='facebook'){
return Socialite::driver($type)->scopes(['email', 'public_profile', 'user_birthday', 'user_location'])->redirect();
}
return Socialite::driver($type)->redirect();
}
在 handleSocialCallback 函数中,用户登录后
Auth::login($checkUser);
return redirect(Session::get('redirect'));
Session::forget('redirect');
我正在使用 Laravel 5.4
和 Socialite
,因此我的用户可以使用 Facebook
登录。
我的网站使用子域 newyork.example.com
、paris.example.com
example.com
回调登录的 Facebook URL
重定向必须是 absolute
所以我设置 http://example.com
login/facebook
public function redirectToProvider()
{
$_SESSION['originalURL'] ="http://paris.example.com";
return Socialite::driver('facebook')
->scopes(['rsvp_event', 'public_profile'])
->redirect();
}
login/facebook/回调
public function handleProviderCallback(SocialAccountService $service)
{
$user = $service->createOrGetUser(Socialite::driver('facebook')->user());
// $user->token;
Auth::login($user, true);
$originalURL = $_SESSION['originalURL'];
return redirect()->to($originalURL);
}
问题
当我在route
login/facebook时我可以查看原URLparis.example.com
和HTTP_POST
当我在路线 login/facebook/callback
中时,HTTP_POST
是 example.com
,因为重定向的 URL 是 example.com
。我尝试将 URL 保存在 session var
中,但 $_SESSION
是空的。
问题
如何在 facebook 登录回调重定向后获取原始 url。 ?因此,如果我使用 paris.example.com
开始登录过程,我将被重定向到 example.com
,然后我将保存 url 重定向到
sessions.php
'cookie' => 'laravel_session',
/*
|--------------------------------------------------------------------------
| Session Cookie Path
|--------------------------------------------------------------------------
|
| The session cookie path determines the path for which the cookie will
| be regarded as available. Typically, this will be the root path of
| your application but you are free to change this when necessary.
|
*/
'path' => '/',
/*
|--------------------------------------------------------------------------
| Session Cookie Domain
|--------------------------------------------------------------------------
|
| Here you may change the domain of the cookie used to identify a session
| in your application. This will determine which domains the cookie is
| available to in your application. A sensible default has been set.
|
*/
'domain' => env('SESSION_DOMAIN', '.localhost.com'),
/*
|--------------------------------------------------------------------------
| HTTPS Only Cookies
|--------------------------------------------------------------------------
|
| By setting this option to true, session cookies will only be sent back
| to the server if the browser has a HTTPS connection. This will keep
| the cookie from being sent to you if it can not be done securely.
|
*/
'secure' => env('SESSION_SECURE_COOKIE', false),
/*
|--------------------------------------------------------------------------
| HTTP Access Only
|--------------------------------------------------------------------------
|
| Setting this value to true will prevent JavaScript from accessing the
| value of the cookie and the cookie will only be accessible through
| the HTTP protocol. You are free to modify this option if needed.
|
*/
'http_only' => true,
关键不是用$_SESSION
而是session(['city' => 'paris'])
;和 session('city')
检索值。
如果您不能在网站之间共享会话,这里有一种方法可以使用 state
OAuth 参数来携带值。
此代码已在自定义 OAuth 提供程序中测试。这是 Facebook 实现的样子(此特定代码未经测试)。
更改 state
的工作方式时需要牢记安全隐患。这是一篇关于 http://www.thread-safe.com/2014/05/the-correct-use-of-state-parameter-in.html There's even an IETF draft on how one would store and sign data into the state
parameter https://datatracker.ietf.org/doc/html/draft-bradley-oauth-jwt-encoded-state-00 的有趣文章(我下面的实现使用 JSON 并且未签名)。
<?php
namespace App\Socialite;
use Laravel\Socialite\Two\FacebookProvider;
use Laravel\Socialite\Two\User;
class CustomFacebookProvider extends FacebookProvider
{
protected $statePreviousUrl = null;
public function withPreviousUrl($url)
{
$this->statePreviousUrl = $url;
return $this;
}
protected function getState()
{
// The state becomes a JSON object with both the XRSF protection token and the url
return json_encode([
'state' => parent::getState(),
'url' => $this->statePreviousUrl,
]);
}
protected function hasInvalidState()
{
if ($this->isStateless()) {
return false;
}
$storedState = $this->request->session()->pull('state');
$requestState = $this->request->input('state');
$requestStateData = json_decode($requestState, true);
// If the JSON is valid we extract the url here
if (!is_null($requestStateData) && array_key_exists('url', $requestStateData)) {
// Don't forget, this value is unsafe. Do additional checks before redirecting to that url
$this->statePreviousUrl = $requestStateData['url'];
}
// If you don't share your session between your instances you can play it "stateless" by always returning false here
// Doing so you loose all XRSF protection ! (but this might be the only way if you don't share your cookies)
// return false;
// If the session is shared, we finish by checking the full state
// We compare the full json objects, no need to extract the state parameter
return ! (strlen($storedState) > 0 && $requestState === $storedState);
}
protected function mapUserToObject(array $user)
{
return (new User)->setRaw($user)->map([
// Data here will vary from provider to provider. The Facebook one is a bit more complex
'id' => $user['id'],
'email' => $user['email'],
// We add the extracted URL here so it can be access from the controller
'previous_url' => $this->statePreviousUrl,
]);
}
}
注册自定义控制器:
<?php
namespace App\Socialite;
use Illuminate\Support\ServiceProvider as BaseServiceProvider;
use Laravel\Socialite\Contracts\Factory;
class ServiceProvider extends BaseServiceProvider
{
public function boot()
{
// @see https://medium.com/laravel-news/adding-auth-providers-to-laravel-socialite-ca0335929e42
$socialite = $this->app->make(Factory::class);
$socialite->extend(
'custom-facebook',
function ($app) use ($socialite) {
$config = $app['config']['services.facebook'];
return $socialite->buildProvider(CustomFacebookProvider::class, $config);
}
);
}
}
用法:
<?php
namespace App\Http\Controllers;
use App\User;
use Laravel\Socialite\Contracts\Factory;
class FacebookLoginController extends Controller
{
/**
* @var Factory
*/
protected $socialite;
public function __construct(Factory $socialite)
{
$this->socialite = $socialite;
}
public function redirectToProvider()
{
return $this->socialite->driver('custom-facebook')->withPreviousUrl('https://paris.example.com/')->redirect();
}
public function handleProviderCallback()
{
$data = $this->socialite->driver('custom-facebook')->user();
dd($data->previous_url);
}
}
我是这样用的,很管用
public function socialConnectRedirect($type, Request $request)
{
Session::put('redirect', $request->input('redirectTo'));
if($type=='facebook'){
return Socialite::driver($type)->scopes(['email', 'public_profile', 'user_birthday', 'user_location'])->redirect();
}
return Socialite::driver($type)->redirect();
}
在 handleSocialCallback 函数中,用户登录后
Auth::login($checkUser);
return redirect(Session::get('redirect'));
Session::forget('redirect');