基于CAS构建通用的单点登录解决策略(一):CAS原理及技术端搭建

原创
小哥 3年前 (2022-11-02) 阅读数 10 #PHP
文章标签 PHPLaravel

什么是 CAS

上篇教程 我们将介绍单点登录和如何建立 Cookie 要实现简单的单点登录,本教程,我们将基于。 CAS 实现更通用的单点登录解决方案,不再受域名的限制。

CAS(Central Authentication Service)是耶鲁大学的一个开源项目,旨在提供 Web 该应用系统提供了可靠的单点登录解决方案。采行 CAS 最大的因素是,从安全的角度来看,用户是进来的。 CAS 服务器输入用户名和密码,然后通过 Ticket 身份验证在不同的系统之间执行,密码不会在线传输,从而确保安全性。

CAS 具有以下特点:

  • 开源通用企业级单点登录解决方案;
  • 一个 CAS Server,多个 CAS Client(经认证 Web 应用)。
  • CAS Client 支持很多客户端,包括 Java、.Net、PHP、Ruby 等。

CAS 单点登录原则

典型的 CAS 单点登录实施至少涉及三个方面:CAS Server、CAS Client(经认证 Web 应用程序)、客户端浏览器。

我们先来看看。 CAS 单点登录的实现过程。

首先,用户访问客户端浏览器。 Web 应用程序(我们称其为应用程序)。 A),发起登录请求,Web 应用 A 将重定向身份验证请求 CAS Server在写一篇 Cookie,CAS Server 将验证用户是否已通过身份验证,如果没有身份验证,则将进行身份验证(通常通过数据库进行匹配),并且 Ticket(将其保存以供后续验证); CAS Server 会过去的 Ticket 回调地址被重定向回。 Web 应用 A,此时 Web 应用 A 我不知道用户是否已通过身份验证,是否会再次通过此验证。 Ticket 去 CAS Server 检查,如果检查通过,删除 Ticket并将经过身份验证的用户信息返回到 Web 应用 A,Web 应用 A 根据用户信息和初始书写 Cookie 设置 Session此时,用户已进入。 Web 应用 A 在中完成登录身份验证。

注意:请注意这一点很重要 Ticket 是一次性的,与指定的用户和服务关联,使用过一次,然后丢弃。

如果还有另一个 Web 应用程序(称其为应用程序 B)此时也发起了登录请求,同样将重定向身份验证请求 CAS Server在写一篇 Cookie此时,用户已进入。 CAS Server 它已经处于登录状态(如果退出需要重新登录),但它是由不同的应用程序发起的身份验证服务,因此将生成一个新的服务。 Tikcet然后重定向回此 Web 应用 B,与上述申请相同 A 身份验证逻辑是相同的,这是。 Web 应用 B 也会通过这个 Ticket 去 CAS Server 验证身份验证状态并在验证成功后将其丢弃。 Ticket,返回用户信息 Web 应用 B,应用 B 基于用户信息和初始写作。 Cookie 设置 Session,这样 Web 应用 B 单点登录也已完成。

因此,实际上,真正的登录操作在 CAS Server(登录中心)实施,客户端 Web 应用 A、B 都是通过 Ticket 进行登录状态验证,每一组都经过验证。 Session 完成各自系统的身份验证,实现单点登录,即单点登录。 CAS Server。

当用户退出时,例如从。 Web 应用 A 为了发起退出请求, A 系统首先退出,然后通知 CAS Server 用户退出,因此,CAS Server 在服务器端(登录中心)退出用户,并将退出消息传递给其他子系统,由其他子系统分别完成退出操作,从而完成所有系统的用户退出操作。

在整个过程中,子系统与 CAS 采用服务端之间的交互。 SSL 协议(HTTPS),从而保证了数据传输的安全性。我们通过以下流程图演示上述单点登录过程:

整个过程应该是非常清楚的。

如果你只提出这个理论,你可能仍然有点困惑。让我们演示如何基于特定的示例。 Laravel 基于框架实施 CAS 单点登录。

服务器端配置

让我们先构建 CAS Server。

安装配置

常见的 CAS Server 都是基于 Java + Tomcat 为了实现,应有 Laravel 框架基于 PHP 语言发展,所以我们需要通过 PHP 来实现 CAS Server幸运的是,已经有人为我们探索了这条路,而且有一条现成的路。 Laravel 扩展包 leo108/laravel_cas_server ,可用于 Laravel 框架中的实施 CAS 服务端的认证。

但是,此扩展包目前不支持最新版本 Laravel 5.7,因此您需要传递一个 Laravel 5.5 或 Laravel 5.6 对项目的版本进行测试:

composer create-project --prefer-dist laravel/laravel blog56 5.6.*

安装完成后,在 .env 在中配置了数据库信息,以便以后可以实施基于数据库的用户身份验证。然后安装 leo108/laravel_cas_server 扩展包:

composer require leo108/laravel_cas_server

接下来, CAS 扩展包配置文件 cas.php 发布到 config 目录下:

php artisan vendor:publish --provider="Leo108\CAS\CASServerServiceProvider"

该配置文件包含 CAS 服务器的总体配置一目了然。

接下来,您可以运行数据库迁移命令来创建相关数据表:

php artisan migrate

命令执行后,数据库中会生成如下数据表:

其中以 cas_ 开始都是相关的 CAS 认证相关数据表:

  • cas_tickets 用于存储所有生成的 Ticket;
  • cas_servicescas_service_hosts 用于存储所有需要认证的内容。 Web 应用程序和相应的服务需要提前注册才能进行单点登录。
  • cas_proxy_granting_tickets 用于存放 CAS 与代理相关的配置,本例中未使用。

最后,我们配置了该项目的域名 blog.test 然后打开 HTTPS。

实现 CAS 用户模型类

修改 User 模型类,让它实现 Leo108CASContractsModelsUserModel 接口,然后在该接口中实现该方法,如下所示:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Leo108\CAS\Contracts\Models\UserModel;

class User extends Authenticatable implements UserModel
{
...

/**

  • 获取用户名
  • 需要确保用户名位于 CAS 系统的独特性,
  • 我们在 CAS Client 用户由其价值唯一确定。
  • @return string
    */
    public function getName()
    {
    return $this->name;
    }

/**

  • 获取用户属性
  • @return array
    */
    public function getCASAttributes()
    {
    return $this->attributesToArray();
    }

/**

  • 获取用户模型实例
  • @return Model
    */
    public function getEloquentModel()
    {
    return $this;
    }
    }

编写 CAS 用户身份验证服务类

CAS 扩展包提供了 CAS 与单点登录相关的路由定义和实施:

基于 CAS 登录操作对应于逻辑 Leo108CASHttpControllersSecurityController ,与用户身份验证相关的逻辑在此传递。 Leo108CASContractsInteractionsUserLogin 接口是依赖注入的,因此我们需要编写接口的实现。

创建一个 AppServicesCASUserLogin 类来实现 Leo108CASContractsInteractionsUserLogin 并按如下方式编写此类:

<?php
namespace App\Services\CAS;

use Illuminate\Foundation\Auth\ThrottlesLogins;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\ValidationException;
use Leo108\CAS\Contracts\Interactions\UserLogin as UserLoginContract;

class UserLogin implements UserLoginContract
{
use ThrottlesLogins;

public function login(Request $request)
{
$this->validateLogin($request);

if ($this->attemptLogin($request)) {
$request->session()->regenerate();
$this->clearLoginAttempts($request);
return $this->guard()->user();
}

// If the login attempt was unsuccessful we will increment the number of attempts
// to login and redirect the user back to the login form. Of course, when this
// user surpasses their maximum number of attempts they will get locked out.
$this->incrementLoginAttempts($request);

return null;
}

public function getCurrentUser(Request $request)
{
return $request->user();
}

public function showAuthenticateFailed(Request $request)
{
// TODO: Implement showAuthenticateFailed() method.
}

public function showLoginWarnPage(Request $request, $jumpUrl, $service)
{
// TODO: Implement showLoginWarnPage() method.
}

public function showLoginPage(Request $request, array $errors = [])
{
$post_login_url = route(cas.login.post);
if ($request->getQueryString()) {
$post_login_url .= ? . $request->getQueryString();
}
return view(auth.login, [post_login_url => $post_login_url]);
}

public function redirectToHome(array $errors = [])
{
return redirect(/home);
}

public function logout(Request $request)
{
$this->guard()->logout();

$request->session()->invalidate();

return redirect(/);
}

public function showLoggedOut(Request $request)
{
// TODO: Implement showLoggedOut() method.
}

/**

  • Validate the user login request.
  • @param  \Illuminate\Http\Request  $request
  • @return void
    */
    protected function validateLogin(Request $request)
    {
    Validator::make($request->all(), [
    $this->username() => required|string,
    password => required|string,
    ])->validate();
    }

/**

  • Attempt to log the user into the application.
  • @param  \Illuminate\Http\Request  $request
  • @return bool
    */
    protected function attemptLogin(Request $request)
    {
    return $this->guard()->attempt(
    $this->credentials($request), $request->filled(remember)
    );
    }

/**

  • Get the needed authorization credentials from the request.
  • @param  \Illuminate\Http\Request  $request
  • @return array
    */
    protected function credentials(Request $request)
    {
    return $request->only($this->username(), password);
    }

/**

  • Get the login username to be used by the controller.
  • @return string
    */
    public function username()
    {
    return email;
    }

/**

  • Get the failed login response instance.
  • @param  \Illuminate\Http\Request  $request
  • @return \Symfony\Component\HttpFoundation\Response
  • @throws \Illuminate\Validation\ValidationException
    */
    protected function sendFailedLoginResponse(Request $request)
    {
    throw ValidationException::withMessages([
    $this->username() => [trans(auth.failed)],
    ]);
    }

/**

  • Get the guard to be used during authentication.
  • @return \Illuminate\Contracts\Auth\StatefulGuard
    */
    protected function guard()
    {
    return Auth::guard();
    }
    }

然后,创建 AppServicesCASTicketLocker 类实现 Leo108CASContractsTicketLocker 接口,该接口也在 SecurityController 在控制器中进行依赖注入,以获取和释放锁,以避免并发问题(测试项目中没有并发问题,只是返回) true ):

<?php
namespace App\Services\CAS;

use Leo108\CAS\Contracts\TicketLocker as TicketLockerContract;

class TicketLocker implements TicketLockerContract
{
public function acquireLock($key, $timeout)
{
return true;
}

public function releaseLock($key)
{
return true;
}
}

下一步,有必要 AppServiceProviderregister 上述接口在方法中注册,并绑定到已实现的服务容器:

//在文件的顶部引入以下命名空间
use App\Services\CAS\TicketLocker;
use App\Services\CAS\UserLogin;
use Leo108\CAS\Contracts\Interactions\UserLogin as UserLoginContract;
use Leo108\CAS\Contracts\TicketLocker as TicketLockerContract;

public function register()
{
$this->app->singleton(UserLoginContract::class, function ($app) {
return new UserLogin();
});
$this->app->singleton(TicketLockerContract::class, function ($app) {
return new TicketLocker();
});
}

我们在 CAS 服务器端或在帮助下 Laravel 自包含的身份验证视图用于登录身份验证和操作。 php artisan make:auth 生成经过认证的脚手架视图。然后在浏览器中访问它。 https://blog56.test/cas/login ,出现如下界面,表示配置成功:

到目前为止,我们 CAS Server 实际上,安装配置现在已经结束。 blog56 该项目已经有一个 CAS Server 客户端的所有功能都在等待客户端应用程序访问并完成单点登录。 下一篇 我们将建造它 CAS 客户端,并演示完整的单点登录实施过程。

版权声明

所有资源都来源于爬虫采集,如有侵权请联系我们,我们将立即删除