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个月前 自动加精
自由与温暖是遥不可及的梦想
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 9

先收藏了

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个月前
TELstatic (作者) 4个月前
自由与温暖是遥不可及的梦想 (楼主) 4个月前
自由与温暖是遥不可及的梦想 (楼主) 4个月前
自由与温暖是遥不可及的梦想 (楼主) 4个月前

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

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

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

配置是一样的 我检查过了

Kernel 如下:

protected $middlewareGroups = [
      'web' => [
          ...
          \Laravel\Passport\Http\Middleware\CreateFreshApiToken::class,
      ],
]
4个月前 评论
自由与温暖是遥不可及的梦想 (楼主) 4个月前
自由与温暖是遥不可及的梦想 (楼主) 4个月前
TELstatic (作者) 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:

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

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

3个月前 评论
xiaoAgiao

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

3个月前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!