今日は明薬祭がありますね。コロンちゃんの和服姿が見れるらしいですね。(何)
行きたかったんですが
遊んでる余裕がまじでないので、欲望を振り切ってプログラムを書きます。
正直な気持ちとしては一秒でも早くプログラマやめたいんですけど、いつになったらやめられるんですかね・・・・・・。
ということで、今日はソーシャルログインです。
ソーシャルログインに関しては
一ヶ月前に作ったし、
Twitterの認証もできるようにしました。
今日はそこから一歩進んで、SNSアカウントでユーザー登録も済ませて、必要な情報を抜き取ってくるとかがやりたいですね、っていう。
こんなん余裕じゃろ、って思いながら結構ハマったのでメモを。
まぁ基本的な流れとしてはSocialiteを使う、って感じですね。
TwitterもSocialite使えば良かったなぁーと思いつつ、まぁ既に実装があるならそれを使いましょうって感じですね。そんなコアな部分まで関わってこないので認証さえできれば今は充分です。
まず一つ、ソーシャルログインを可能にするためにデータベーステーブルを正規化します。
最初は全部usersにぶっ込んでいたんですが、SNSログインの場合、
同一ユーザーなのに2つのアカウントがあるという状況が発生しやすく、統合しやすいような形にしなくてはならないのでテーブルを分けたいと思います。
下準備は、composerを走らせインストールし、.envに必要な情報を書き入れる、って感じですかね。まぁこの辺は
マニュアル見ればすぐですね。このお手軽さが魅力です。
そしてFacebook側でキーとか取得して云々、って感じですね。これはこの前やりましたね。
じゃあ、ざっくりとしたコードを。
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Laravel\Socialite\Facades\Socialite;
use App\DAO\usersDAO;
use Auth;
use Redirect;
use App\DAO\snsDAO;
use App\Classes\Userdata;
use DB;//Geometry生成用
class FacebookController extends Controller
{
public function login()
{
//認証ページに飛ぶ
if(Auth::check()){
//既にログインしているのにログインしようとしないで(笑)
Redirect::route('home');
}
return Socialite::driver('facebook')->redirect();
}
public function signin()
{
//認証ページに飛ぶ
return Socialite::driver('facebook')->redirect();
}
public function redirectToFacebookProvider()
{
return Socialite::driver('facebook')->redirect();
}
static private function upsert($user,$user_id){
//指定されたIDで新規登録
$get_by_id_query=(new snsDAO())->get_by_id($user_id);
$array = [
"user_id"=>$user_id,
"facebook_id" => $user->getId(),
"facebook_name"=>$user->getName(),
"facebook_nickname"=>$user->getNickname(),
"facebook_token"=>$user->token
];
if( empty($get_by_id_query->value("user_id")) ){
//まだそのIDに紐づくデータがない
$get_by_id_query->insert($array);
}else{
//紐付いたデータが存在する
$get_by_id_query->update($array);
}
}
static private function integration($user,$user_id=0):array{
//$user_id番のユーザーの情報を統合する,$userはfacebookのインスタンス
//データがないところはfacebookの情報を抜き取ってupdateするための配列を作る
$result=[];
$db=(new usersDAO($user_id))->first();
if(empty($db) || empty($db->email)){
$result["email"] = $user->getEmail();
}
if(empty($db) || empty($db->nickname)){
$result["nickname"] = $user->getNickname();
}
if(empty($db) || empty($db->name_first)){
list($result["name_last"],$result["name_first"])=explode(" ", $user->getName());
}
if(empty($db) || $db->sex === null){
$result["sex"]=(empty($user->user["gender"]))?null:($user->user["gender"]=="男性")?0:1;
}
if(empty($db)){
$result["geometry"] = DB::raw('GeomFromText('POINT(0 0)')');//nullが入れられないので
}
return $result;
}
static private function signin_exe($user){
//IDでユーザーアカウントと紐付ける
/*略*/
}
public function callback()
{
try{
//ユーザーインスタンスを取得
$user = Socialite::driver('facebook')->user();
//正常に取得できた
}catch(Exception $e){
return redirect("/");
}
if(Auth::id()){
//既にログイン=>アカウント認証
self::signin_exe($user);
return;
}
$user_id=(new snsDAO())
->where_facebook_id($user->getId())
->value("user_id");
if(!empty($user_id)){
//そのIDが既にある->ソーシャルログイン
if(!(new Userdata($user_id))->check_required()){
return;//必須項目が不足、入力(現状ここに来ることがないのでこの画面はない)
}else{
//ソーシャルログイン実行
Auth::loginUsingId($user_id, true);
return Redirect::route('home');
}
}
//未ログインでかつIDが存在しない=>新規登録
//users操作
//emailの存在確認
$user_id=(new usersDAO())->get_by_email($user->getEmail())->value("id");
if (!empty ($user_id) ){
//既に登録されているメールアドレスであれば情報を統合
(new usersDAO())->update(self::integration($user));
}else{
//メールアドレスが見つからない=>新規登録
$user_id=(new usersDAO())->insertGetId(self::integration($user));
}
//sns操作
//snsデータも追加
self::upsert($user,$user_id);
//認証ができたのでログイン
if (!Auth::loginUsingId($user_id, true)) {
// 認証に失敗した
return;//めんどいから画面作ってない笑
}
return Redirect::route('home');
}
public function signout()
{
//データベースを更新する
(new snsDAO())->get_by_id(Auth::id())
->update([
"facebook_id" => null,
"facebook_name"=>null,
"facebook_nickname"=>null,
"facebook_token"=>null
]);
return Redirect::route('profile')->with(['facebook'=> "signout","tab"=>"tab_sns"]);
}
}
今はこんなコードです。センスのなさが光ります。()
あ、ちなみに
新規ログイン部分以外まだテストしてないです←
コールバック先のURLを自由に指定できればログイン時と認証時の処理で分けられるんですけど、それを調べるのがめんどくさいので今は一緒くたにしてます。問題が出てきたら方法を考えようと思います。
だから問題を先延ばしにする癖はやめろって言ってるんだけどな
まぁ、僕の望みは
早いとこプログラマから足を洗って人に丸投げしたいって感じなので、もっと有能な人が書くべきだと思うのです・・・・・・僕はプログラマ向いてないから笑
さて、このソーシャルログイン、基本的にハマるようなところはないのですが、知らないとハマるところが一箇所だけ。
SNSログインした後、きちんとAuthでログインさせる操作を挟まないと、認証でしかなくログインにならないので挟もうとしたんですが、通常通りAuth::attempt()メソッドでログインしようとすると
パスワードが必須なんですよねこれ。
で、当然ながらパスワードなんて平文で持ってるわけないのでデータベースから取ってこれませんし、非可逆暗号なので戻すこともできません。
SNSログインした後にパスワードを求めるような実装はさすがにあり得ないと思いますので、普通どうやって作るんだろうと探してみても出てくるのはEloquentモデルの話ばっかり・・・・・・。
僕はクエリービルダーユーザーなのでEloquent使えないんですよ、、、Laravelのいいところを敢えて無視してる感あるんですが、クエリービルダーは直感的で分かりやすいんですわ(笑)
んで、3時間ぐらいあーでもないこーでもないみたいなことやってたんですが、マニュアルを見たら載ってました。(ここ笑うとこです!)
ちゃんとIDだけで強制ログインできる機構を持ち合わせていました。悲しみ。
Twitterも似たような感じで実装すればいけます。簡単ですね。
問題となるのが
ユーザーデータが中途半端に統合されると激マズってとこなので、ユーザーデータを書き換える時に該当トランザクションのユーザーIDを全部書き換えるぐらいやんないとまずいことに今気づきましたね・・・・・・後で実装しなくちゃ、、、