Laravel 通过 cookie 实现基于 session 的单点登录

单点登录说明#

单点登录(Single Sign On),简称为 SSO,意思是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的其它应用系统。一般常用于同一家公司的不同子系统之间的登录认证。

需要插件#

//  passport 
composer require laravel/passport
// predis
composer require predis/predis

首先我们创建三个项目#

composer create-project --prefer-dist laravel/laravel login.sso.test
composer create-project --prefer-dist laravel/laravel home.sso.test
composer create-project --prefer-dist laravel/laravel my.sso.test

env 配置#

APP_KEY=***

DB_CONNECTION=mysql
DB_HOST=192.168.10.10
DB_PORT=3306
DB_DATABASE=sso
DB_USERNAME=homestead
DB_PASSWORD=secret

BROADCAST_DRIVER=log
CACHE_DRIVER=redis
QUEUE_CONNECTION=redis
SESSION_DRIVER=redis
SESSION_LIFETIME=120
SESSION_DOMAIN=.sso.test

SESSION_DOMAIN 我们在这个 env 新增了这个配置 是为了设置 config/session.php 中配置项 domail 设置 Cookie 域名

三个项目的 APP_KEY 需要设置为一样否者不行

登录认证中心#

安装扩展包#

composer require laravel/passport
composer require predis/predis

执行数据库迁移#

php artisan migrate

运行 artisan 命令#

php artisan passport:install

安装 laravel 自带 认证中心#

composer require laravel/ui
npm install && npm run dev
// 会提示创建页面 不需要的页面可以不创建
composer require laravel/ui

config/auth.php 配置#

guards = [
        ...
         'web' => [
            'driver' => 'session',
            'provider' => 'users',
        ],
        'api' => [
            'driver' => 'passport',
            'provider' => 'users',
        ],
]

配置中间件#

protected  $middlewareGroups = [
   'web' => [
        ...
        \Laravel\Passport\Http\Middleware\CreateFreshApiToken::class,
   ],
   'api' => [
        ...
        'throttle:60,1',
        'bindings',
   ]
],

配置路由 routes/api.php#

// 为了方便测试 取消 token验证
Laravel\Passport\Passport::$ignoreCsrfToken = true;

Route::middleware('auth:api')->group(function () {
    Route::get('/user/{id}', function ($id) {
        return \App\User::find($id);
    });
});

登录认证 中心项目完成

home.sso.test 项目#

安装扩展包#

composer require laravel/passport
composer require predis/predis

安装 laravel 自带认证中心#

composer require laravel/ui
npm install && npm run dev
// 会提示创建页面 不需要的页面可以不创建
// 子系统不需要登录注册找回密码等页面 所以 全部选择 no
composer require laravel/ui

自定义 Provider#

我们需要自定一个 SsoUserProvider 来实现从主系统获取用户信息,在 app 目录下创建一个 Extensions 目录 创建
SsoUserProvider

// 创建文件夹
mkdir Extensions
// 进入目录
cd Extensions
// 创建文件
touch SsoUserProvider.php

SsoUserProvider.php 代码#

<?php

namespace App\Exceptions;

use GuzzleHttp\Client;
use GuzzleHttp\Cookie\CookieJar;
use Illuminate\Auth\EloquentUserProvider;

class SsoUserProvider extends EloquentUserProvider
{
    public function retrieveById($identifier)
    {
        /**
        * 确保百分之百有 laravel_token 如果没有继续去登录
        */
        if (isset($_COOKIE['laravel_token']) && $_COOKIE['laravel_token']) {
            $http = new Client();
            $cookies = CookieJar::fromArray(
                [
                    'laravel_token' => $_COOKIE['laravel_token'],
                ],
                '.sso.test'
            );

            $response = $http->request(
                'GET',
                'http://login.sso.test/api/user/'.$identifier,
                [
                    'cookies' => $cookies,
                ]
            );

            $user = json_decode($response->getBody()->getContents(), true);
            $model = $this->createModel();

            $model->forceFill($user);

            return $model;
        } else {
            return redirect('http://login.sso.test/login');
        }
}

以上域名 可以走 自定义配置文件
这个自定义类中,我们同过 API 接口获取用户信息并返回

然后设置 config/auth.phpproviders 配置项修改如下#

 'users' => [
        'driver' => 'sso',
        'model' => App\User::class,
],

最后我们在 AuthServiceProvider .phpboot 方法注册自定义的 UserProvider 类让它生效#

use Illuminate\Support\Facades\Auth;
use App\Exceptions\SsoUserProvider;
public function boot()
{
    ...
    Auth::provider(
        'sso',
        function ($app, $config) {
  return new SsoUserProvider($app->make('hash'), $config['model']);
        }
  );
}

然后们修改 app/Http/Controllers/Auth/LoginController.php 将登录重定向到登录系统

 public function showLoginForm()
 {
     return redirect('http://login.sso.test/login');
 }

注册 找回密码 同理

退出登录#

我们修改 app/Http/Controllers/Auth/LoginController.php 文件

public function logout(Request $request)
    {
        $http = new Client();
        $cookies = CookieJar::fromArray(
            [
            // 注意是 laravel_session 不是laravel_token
                'laravel_session' => $_COOKIE['laravel_session'],
                'XSRF-TOKEN' => $_COOKIE['XSRF-TOKEN'],
            ],
            '.sso.test'
        );

        $response = $http->request('POST', 'http://login.sso.test/logout', ['cookies' => $cookies]);

        if ($response->getStatusCode() == 200) {
            $request->session()->invalidate();

            return $this->loggedOut($request) ?: redirect('/');
        }
        abort(500);
    }

登录认证中心 主项目#

因为 post 提交需要 _token 这里为了方便 直接在 app/Http/Middleware/VerifyCsrfToken.php 中的 $except 新增退出路由 否者会 419 错误

 protected $except = [
        '/logout'
    ];

my.sso.test 同上 即可

欢迎评论 给出意见 及修改

本作品采用《CC 协议》,转载必须注明作者和本文链接
本帖由系统于 4年前 自动加精
自由与温暖是遥不可及的梦想
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
讨论数量: 10

先收藏了

4年前 评论
自由与温暖是遥不可及的梦想 (楼主) 4年前
  1. 认证 API 路由没有做权限判断,当前登录用户 1,访问 /user/2 可以取到用户 2 的信息
Laravel\Passport\Passport::$ignoreCsrfToken = true;

Route::middleware('auth:api')->group(function () {
    Route::get('/user/{id}', function ($id) {
        return \App\User::find($id);
    });
});
  1. 认证中心直接登录,跳转回子站点时报错
Undefined index: laravel_token
4年前 评论
自由与温暖是遥不可及的梦想 (楼主) 4年前
未定义 (作者) 4年前
自由与温暖是遥不可及的梦想 (楼主) 4年前
自由与温暖是遥不可及的梦想 (楼主) 4年前
自由与温暖是遥不可及的梦想 (楼主) 4年前

@自由与温暖是遥不可及的梦想

a 站 =》认证中心 b 站 =》子站点

a 站注销登录 b 站刷新会报错

配置是一样的 我检查过了

Kernel 如下:

protected $middlewareGroups = [
      'web' => [
          ...
          \Laravel\Passport\Http\Middleware\CreateFreshApiToken::class,
      ],
]
4年前 评论
自由与温暖是遥不可及的梦想 (楼主) 4年前
自由与温暖是遥不可及的梦想 (楼主) 4年前
未定义 (作者) 4年前
自由与温暖是遥不可及的梦想 (楼主) 4年前

录屏如下

https://cdn.telstatic.xyz/issue.mp4

4年前 评论
自由与温暖是遥不可及的梦想 (楼主) 4年前

跟我的想法差不多,但我是使用 sso 作为中转域,通过各子系统跳转至 sso 域进行 token 获取,然后回跳验证 token 后实现登录,目前公司项目在使用这个方式。 :yum:

4年前 评论
自由与温暖是遥不可及的梦想 (楼主) 4年前

composer require laravel/ui
npm install && npm run dev
// 会提示创建页面 不需要的页面可以不创建

composer require laravel/ui
上面那個你應該是想說下面這個吧?
php artisan ui vue –auth

4年前 评论
自由与温暖是遥不可及的梦想 (楼主) 4年前

如果有一个子系统是 java 或者其他语言开发的还能用吗? :flushed: :flushed:

4年前 评论
自由与温暖是遥不可及的梦想 (楼主) 4年前

直接用 jwt 来做单点登录会不会更简单呢?所有项目同一个 jwt 密钥,一个 token 访问所有项目

4年前 评论
xiaoAgiao

原理就是多个站点访问同一个 login flag

4年前 评论

如果域名不同是不是就不行了。 比如 sso.a.com 但是有个业务域名是 login.b.com

3年前 评论
自由与温暖是遥不可及的梦想 (楼主) 3年前
keer (作者) 3年前