Laravel Socialite 對接微信公眾號成功實踐

一畝三分地兒發表於2019-03-01

幾年前,為了接入微信,用C#研究了一個禮拜才做出個對接元件。在 Laravel 使用 Socialite 兩天搞定,喜出望外的是用這種方式貌似可以對接所有的OAuth認證系統,真是事半功倍。關於 Laravel Socialite 中文的英文的自己去找。我寫我自己感覺需要注意的地方。

我的目標是公眾號的頁面分為兩種,一種是隨便可以訪問的,不需要認證,甚至可以在瀏覽器裡檢視,另一種是隻有通過微信認證授權才能訪問的頁面。在C#中我做了兩個Page的抽象類,一個是普通的頁面,另一個是發現還沒有 openid 就跳轉到認證伺服器兌換到 openid 後再繼續的頁面。本著這個目標繼續折騰。

走過的彎路沒有參考價值就不多寫了。下邊就把 Evernote 中的內容貼出來。
參考的網址有:
https://www.open-open.com/lib/view/open147...
https://learnku.com/docs/laravel/5.7/socialite
https://github.com/SocialiteProviders/Prov...
https://socialiteproviders.netlify.com
https://socialiteproviders.netlify.com/pro...

//先安裝了
composer require laravel/socialite
//又安裝了(後來感覺只安裝這個就行了,上邊那個應該不需要,似乎這個裡已經包含了上一個)
composer require socialiteproviders/weixin
//然後按照 https://socialiteproviders.netlify.com/pro... 中的說明修改程式碼
第一處修改參照:2. Service Provider
第二處修改參照:3. Event Listener
第三處修改參照:4. Configuration setup
修改到這裡就開始 5. Usage,哥感動地都快哭了
再 參照 https://learnku.com/docs/laravel/5.7/socialite 寫了路由

Route::get('login/github', 'Auth\LoginController@redirectToProvider');
Route::get('login/github/callback', 'Auth\LoginController@handleProviderCallback');

在 redirectToProvider 寫了:
return Socialite::with('Weixin')->redirect();
如果在電腦瀏覽器中訪問,出現了熟悉的綠色提示在微信中訪問
有點小激動
馬上公眾號註冊選單試著方法在方法 handleProviderCallback 等待 $user 中的資料
handleProviderCallback中是這麼寫的:

public function handleProviderCallback()
{
    $user = Socialite::driver('github')->user();
    // $user->token;
}

手機訪問報錯(如果不報錯,就見鬼了)
錯誤提示:The bootstrap/cache directory must be present and writable.
執行 php artisan cache:clear 清緩問題解決

接著第二個錯 InvalidStateException
或曰:修改程式碼如下:
$user = Socialite::driver('weixin')->stateless()->user();

接著報第三個錯:cURL error 60: SSL certificate problem: unable to get local issuer certificate
到這裡心有點發涼了,這尼瑪超扯越遠,繼續折騰才三個錯了
https://curl.haxx.se/ca/cacert.pem 下載最新的cacert.pem
修改 PHP.ini 檔案
curl.cainfo=C:\Program Files\PHP\7.2.13\cacert.pem
或曰:這個證照不行需要下載另一個
再一試好了。三個錯就通了。 $user 中取到了個人微信的資訊,我渴望的openid也在其中。
後來發現的:只要有錯誤都會報 InvalidStateException 這個錯誤,其實第二個錯是由第三個錯引起來的,因為無意中刪除掉 ->stateless 也不會報 InvalidStateException。還是我自己的程式碼寫錯也會報 InvalidStateException。

心情是喜悅的,但不對呀我的目標還沒達到。
這種機制為什麼 redirect 的 Callback 是個固定的地址呢,我是希望這麼寫
return Socialite::driver('weixin')->redirect(<這裡輸入一個地址,就是授權後回的地址>);
我用C#編寫的元件就是可以指定的。這樣可以實現:訪問一個授權頁面發現沒有授權然後去授權,授權後再回到這個頁面。

任重道遠繼續折騰,這是我最怕的:我的需求不在人家的考慮範圍之內,甚至與人家的理念違背。
經過折騰無意發現在 handleProviderCallback 中通過如下程式碼可取到授權以前的地址。

$previous = $session->get('_previous')['url'];

程式碼雖簡單,由於本人PHP還在入門水平折騰了快一天,翻看Socialite的原始碼,還是無意中發現的
大家熟悉的PHP函式我需要查百度才知道,大家別笑哈。
最後我的實現方法是
建立一個 BaseController 頁面的 Controller 從這個類繼承,如果需要授權保護則繼承它並 呼叫 redirectToProvider

/**
 * 需要使用者 OPENID 的控制器的基類
 *
 * Class BaseController
 * @package App\Http\Controllers\WeiXin
 */
class BaseController extends Controller
{
    /**
     * 重定向到認證伺服器
     *
     * @param Request $request
     * @param \Closure $getView
     * @return mixed
     */
    protected function redirectToProvider(Request $request, \Closure $getView)
    {
        $session = $request->session();
        $openid = $session->get('openid');
        if ($openid) {
            Log::info("網頁取到OPENID:{$openid},顯示當前頁面內容");
            return $getView();
        } else {
            $session->flush();
            Log::info("沒有取到OPENID,重新定位到微信認證伺服器");
            return Socialite::driver('weixin')->redirect();
        }
    }
}

子類是這樣的:

class HomeController extends BaseController
{
    public function home(Request $request){
        return $this->redirectToProvider($request, function (){
            return view('child', ['name' => '這是路由器裡定義的變數']);
        });
    }
}

負責捕獲回撥的單獨在一個Controller

    /**
     *
     * @param Request $request
     * @return \Illuminate\Http\RedirectResponse
     */
    public function handleProviderCallback(Request $request)
    {
        $user = Socialite::driver('weixin')->user();
        if ($user) Log::info("已經取到使用者資訊");
        //TODO:儲存使用者資訊到資料庫中

        //設定全域性 openid 到 session 中
        $openid = $user->id;
        if ($openid) Log::info("已經取到使用者OPENID:$openid");
        $session = $request->session();
        $session->put('openid', $openid);

        //定位到之前的網頁
        $previous = $session->get('_previous')['url'];
        Log::info("定位到原來的地址:$previous");
        return redirect()->away($previous);
   }

到這裡就結束了,目前看上去沒有問題。用著再說吧。
上次一個朋友批評了我不分享的行為。這此把我的感覺寫出來,希望對另人有一點幫助。
由於本人PHP小白一個,有什麼更好的方法,望不吝賜教!!!

相關文章