質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

89.71%

1つのWEBサービスにログインしているユーザーを別のサービスにもログインさせたい

受付中

回答 2

投稿 編集

  • 評価
  • クリップ 1
  • VIEW 899

monjasfv

score 4

実現したいこと

PHPで複数のWEBサービスを運営しています。
実現したいのはサービスAでログインしているユーザーをサービスBにもログインさせることです。
自分なりに方法を考えてみましたが、これは安全でしょうか?
また、別にどのような方法が考えられるでしょうか?
よろしくお願いします。

前提

  • AとBどちらもID・パスワードでログインする
  • 同一人物がAとB両方に登録している場合、ログインID・パスワードはAとBで共通
  • AとBは別サーバー、別ドメインであるためセッションは共有できない
  • AとBのDBサーバーは同じ(DBは別)
  • Aにログインすると同時にBでログイン状態になる必要はない(AからBにログインするときはリンクをクリックする)

追記

  • ログイン後にA⇔Bで行き来したい(「Aへ」「Bへ」のようなリンクで移動できればOK)
  • AとBのDBは別である必要がある(AでもBでもない別DBから共通のviewは作成できる)

追記ここまで

自分なりに考えた方法

ログインIDとトークンを使用する方法を考えました。

A→Bにログインする場合

最初にトークンを保存するテーブルを作成します。
テーブル構造

  • id: autoincrement 主キー
  • login_id: varchar
  • content: varchar ランダムな文字列 md5(uniqid(mt_rand(), true))
  • expiration_date: datetime トークンの有効期限(トークン作成から10秒後)
  • is_used: tinyint 未使用=0, 使用済み=1

Bにtokenを発行するapiを追加

他者にapiを使われないようにurlに?key=abcd1234のような固定のキーを持たせ、一致した場合のみ処理をするようにします。
以下のようなイメージです。

function createTokenAction($request)
{
    $key = $request->get('key');
    $loginId = $request->get('login_id');

    if ($key !== self::KEY) {
        throw new \Exception();
    }

    $token = new Token;
    $token->loginId = $loginId;
    $token->content = md5(uniqid(mt_rand(), true));
    $token->expirationDate = date('Y-m-d H:i:s', strtotime('+10 seconds'))
    $token->isUsed = 0;
    $token->save();

    return json_encode(array(
        'token' => $token->content,
    ));
}

BにログインIDとtokenを使用したログインを追加

URL: b.com/loginWithToken/?login_id=hoge&token=abcd1234

functin loginWithTokenAction($request)
{
    $loginId = $request->get('login_id');
    $tokenContent = $request->get('token');

    $token = Token::findBy(array(
        'loginId' => $loginId,
        'content' => $tokenContent,
    ));

    if (!$token) {
        throw new \NotFoundExpcetion();
    }

    $now = new \DateTime();
    if ($now > $token->expirationDate || $token->isUsed) {
        throw new \Exception();
    }

    $token->isUsed = 1;
    $token->save();

    // ログインさせる
    Auth::login($loginId);

    $this->redirectHome();
}

AにBへのログイン処理を追加

Bのトークン作成apiでトークンを取得し、BのloginWithTokenにリダイレクトします。
URL: a.com/loginToB

function loginToBAction($request)
{
    $key = self::KEY;
    $loginId = $this->getUser()->loginId;
    $url = sprintf('https://b.com/createToken/?key=%s&login_id=%s', $key, $loginId);
    $json = file_get_contents($url);
    $data = json_decode($json, true);
    $token = $data['token'];
    $url = sprintf('https://b.com/loginWithToken/?login_id=%s&token=%s', $loginId, $token);
    return $this->redirect($url);
}

以上のような方法でBへのログインが実現できそうですが、この方法が安全なのか、また、他にもっといい方法が無いか、教えていただきたいです。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • CHERRY

    2017/08/25 12:03

    方法に関しては、キーワードとしては、「シングルサインオン」で検索すれば、いろいろ見つかると思います。

    キャンセル

  • m.ts10806

    2017/08/25 14:32

    ログイン後にA⇔Bで行き来することはあるかどうかも前提条件に加えた方が良いと思います。また、DBが別ではなく同じなのはダメかどうかも。

    キャンセル

回答 2

+3

シングルサインオンとかシングルサインインとか言われているものです。

認証周りは正常系は簡単にできますが異常系を考慮するのが難しく、自作は避けるのが定石です。出来合いのライブラリを使う事を強くお勧めします。

シングルサインオンについてはSoftware DesignかWeb+DB Pressで特集記事があったと思います。バックナンバー探してみてください。


コードも見てみましたが、「ユーザーID」と「キー」さえ取得できればAとは無関係に任意にトークンを発行しBにログインできます。事実上Bの認証を固定パスワードに変更しただけで、シングルサインオンとは言えないです。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/08/26 23:00

    ご回答ありがとうございます。
    >事実上Bの認証を固定パスワードに変更しただけ
    おっしゃる通りですが、URLが公開されていないこと、十分に長いキーを使用することで安全であるとはいえないでしょうか?

    また、より安全な方法を考えました。

    - 別DBにtokenを保存するテーブルを作成
    - そのビューをAとB両方のDBに作成
    - AからBへログインするとき、Aでtokenを作成して保存、BのログインURLにloginIdとtokenを渡す
    - BでURLのloginIdとtokenを使用してログイン

    以上の方法で何か危険はあるでしょうか?ご回答いただけると嬉しいです。よろしくお願いします。

    キャンセル

  • 2017/08/27 00:54

    自作はやめましょうと言っています。思いつきレベルでは安全なものを作るのは絶対に不可能です。どうしてもやりたいのであればまずはWebセキュリティの基礎から勉強が必要です。

    キャンセル

  • 2017/08/27 23:38

    suzukisさん、これ、SSOのどの方式なら実現できそうとイメージしています?

    キャンセル

+2

そもそも論ですが、双方のサービスで使用する login_id を一意に保つために、認証用のテーブルは共通である必要があると思います。
そうであるならば、特に複雑な機構は考える必要はなく、そのテーブルを使用して認証すれば良いだけかと。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 89.71%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる