基于CAS构建通用的单点登录解决策略(一):CAS原理及技术端搭建
原创什么是 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_services和cas_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;
}
}
下一步,有必要 AppServiceProvider 的 register 上述接口在方法中注册,并绑定到已实现的服务容器:
//在文件的顶部引入以下命名空间
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 客户端,并演示完整的单点登录实施过程。
版权声明
所有资源都来源于爬虫采集,如有侵权请联系我们,我们将立即删除
itfan123


