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 协议》,转载必须注明作者和本文链接
本帖由系统于 1年前 自动加精
自由与温暖是遥不可及的梦想
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 10

先收藏了

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

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

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

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

配置是一样的 我检查过了

Kernel 如下:

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

录屏如下

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

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

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

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

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

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

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

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

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

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

1年前 评论
xiaoAgiao

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

1年前 评论

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

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

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