[ Laravel 从入门到精通 ] 用户认证与授权系列 —— 基于 CAS 实现通用的单点登录解决方案(二):CAS 客户端搭建及单点登录测试

上篇教程学院君给大家介绍了 CAS 单点登录原理以及 CAS Server 端搭建,这篇教程我们书接上篇,着手客户端测试应用搭建和完整单点登录流程演示。

客户端配置

首先我们创建两个用于单点登录测试的客户端 Web 应用 testappotherapp(如果对应应用之前已经存在,则无需重复创建),下面我们以 testapp 为例,进行初始化配置和演示,otherapp 依葫芦画瓢即可。

和 CAS 服务端一样,我们需要在 Laravel 应用中通过 CAS PHP 客户端与 CAS Server 进行通信。CAS 官方为 PHP 语言提供了客户端实现 phpCAS,此外,Laravel 生态也有基于 phpCAS 实现的扩展包 subfission/cas,该扩展包封装了对 phpCAS 的操作,所以我们安装这个扩展包后,就可以在 testapp 客户端中通过 phpCAS 与服务端 blog56 应用进行单点登录交互了。

安装配置

首先通过 Composer 安装一个新的 testapp 应用:

composer create-project laravel/laravel testapp --prefer-dist -vvv

我们配置该应用的域名为 app.test。然后进入该项目根目录,安装 subfission/cas 扩展包:

composer require subfission/cas

接下来,在 config/app.php 中注册服务提供者和门面:

// 在 providers 中添加这个配置
/*
 * Package Service Providers...
 */
\Subfission\Cas\CasServiceProvider::class,

// 在 aliases 中添加这个配置
'Cas' => \Subfission\Cas\Facades\Cas::class,

添加如下两个中间件到 app/Http/Kernel.php$routeMiddleware 属性用于单点登录判断:

'cas.auth'  => \Subfission\Cas\Middleware\CASAuth::class,
'cas.guest' => \Subfission\Cas\Middleware\RedirectCASAuthenticated::class,

完成以上配置后,发布 CAS 客户端扩展包配置文件 cas.phpconfig 目录下:

php artisan vendor:publish --provider="Subfission\Cas\CasServiceProvider"

然后修改 .env 环境配置,以便可以完成 CAS 客户端配置:

CAS_HOSTNAME=blog56.test
CAS_REAL_HOSTS=blog56.test
CAS_LOGOUT_URL=https://blog56.test/cas/logout
CAS_LOGOUT_REDIRECT=http://app.test
CAS_REDIRECT_PATH=http://app.test/user
CAS_ENABLE_SAML=false

这里,我们配置了 CAS 服务端域名、退出 URL,以及服务端退出和登录后对应的客户端回跳地址,最后我们配置 CAS_ENABLE_SAMLfalse,因为目前服务端不支持 SAML。

路由定义

完成上述配置后,打开 routes/web.php, 定义认证相关路由如下:

Route::get('/login', function () {
    return cas()->authenticate();
});

Route::middleware('cas.auth')->get('/logout', function () {
    cas()->logout();
});

Route::middleware('cas.auth')->get('/user', function () {
    return cas()->user();
});

用户访问登录路由 /login 会进行认证判断,如果已经认证则跳转到已认证页面,否则跳转到 CAS 服务端进行判断。

用户访问退出路由 /logout 会先在本地退出,然后到 CAS 服务端退出,并且发送退出通知给其他系统,然后跳转到客户端退出后地址。

用户访问需要认证的路由 /user 时,如果没有认证,会先进行单点登录认证,如果已经认证则返回认证用户信息。

另一个测试应用

另一个测试应用 otherapp 和上面安装配置操作完全一样,唯一不同的是客户端域名为 other.test,将相应的配置值改查这个域名即可。

至此,我们已经完成了 CAS 服务端和客户端的所有搭建和配置工作,接下来,就可以测试单点登录流程了。

测试前的准备工作

修改服务端时区配置

由于在单点登录过程中生成的 Ticket 是有过期时间的,因此,需要在服务端 blog56 应用中修改 config/app.phptimezone 默认配置值:

'timezone' => 'Asia/Shanghai',

当然,最好把 testappotherapp 两个客户端应用的对应配置值也做这样的调整。这样,看起日志来就不会有时区错乱的问题了。

在服务端注册客户端应用

接下来,我们需要在 CAS 服务端中注册客户端应用服务,打开服务端数据库,在 cas_services 数据表中注册两个服务,分别是 otherapp_authapp_auth,用于这两个应用的单点登录:

然后在 cas_service_hosts 数据表中绑定上述服务对应域名:

在服务端创建测试用户

最后,在服务端通过 Tinker 创建一个用于测试的新用户:

这样,就完成了单点登录所需的所有环境、配置、数据等准备工作了,下面正式进入单点登录测试演示环节。

测试单点登录实现流程

在浏览器访问 CAS 客户端应用 testapp 中访问需要认证的路由:http://app.test/user,页面会重定向到 CAS 服务端登录页面:https://blog56.test/cas/login?service=http%3A%2F%2Fapp.test%2Fuser&gateway=true,并且在链接中自动带上了回跳地址:

此时,如果你访问 app.test 的话,会看到 Cookie 中新增了一个 CASAuth,该 Cookie 用于设置 CAS 单点登录认证的 Session ID。

回到 blog56.test 登录页面,填写上一步创建的测试用户账号信息,完成登录后,页面会跳转到 http://app.test/user,并打印出用户标识 ID —— 用户名信息,如果你想要获取完整的用户信息,可以通过在 CAS 服务端实现支持 SAML 协议的接口来实现,或者在客户端通过访问 CAS 服务端 API 来获取用户信息:

其中,在返回认证用户信息给客户端之前,还有一步我们之前在讲 CAS 单点登录实现原理中提到的验证操作。上述认证的完整实现过程是这样的:

  • CAS 服务端登录成功之后,会生成一个 Ticket,并将其保存到 cas_tickets 表(5分钟内有效);
  • 根据客户端重定向链接中提供的 service 参数和刚刚生成的 Ticket 组合成一个新的 URL:http://app.test/user?ticket={TicketValue},重定向回客户端;
  • 客户端通过这个 Ticket 请求 CAS 服务端验证接口:https://blog56.test/cas/serviceValidate?ticket={TicketValue}&service=http%3A%2F%2Fapp.test%2Fuser,对应的验证逻辑位于 \Leo108\CAS\Http\Controllers\ValidateController@v2ServiceValidateAction,根据 ticket 参数去数据库查询是否存在对应记录以及是否过期,如果不存在或已过期,则验证失败,否则验证成功,将数据表中对应 Ticket 记录删除,然后将认证用户信息返回给客户端应用;
  • 客户端应用验证成功后,根据 Cookie 中的 CASAuth 作为 Session ID 将返回的认证用户标识信息存储到 Session 中,至此,用户就完成了单点登录认证操作。下次用户在客户端应用访问认证路由时,就是一个基于客户端 CASAuth Cookie 实现的 Session 认证判断了,无需再到 CAS 服务端进行验证。

testapp 应用已经完成登录认证,下面我们到 otherapp 中进行认证操作。

我们在浏览器访问 http://otherapp.test/login,此时,由于 otherapp 还未进行登录认证,所以会重定向到 https://blog56.test/cas/login,同时在 otherapp 中写入一个 CASAuth Cookie,由于 blog56.test 还处于登录状态,所以会为 otherapp 应用生成一个 Ticket 并返回该客户端应用,相应流程和 testapp 完全一样,otherapp 根据这个 ticket 去 CAS 服务端验证接口 https://blog56.test/cas/serviceValidate 进行验证,验证通过后将认证用户信息返回给 otherappotherapp 以之前创建的 CASAuth Cookie 作为 Session ID,根据返回的认证用户信息设置 Session,从而完成 otherapp 的登录认证。

整个过程中,我们只是在第一次 testapp.test 中进行登录认证的时候在 CAS 服务端进行了一次登录操作,后续其它子系统的登录认证只需要通过服务端分配 Ticket 并基于该 Ticket 进行认证验证即可,所以也是一次登录,即可在信任的系统之间访问认证资源。这个信任是通过在 CAS 服务端注册审核客户端应用来完成授权的。而且基于 CAS 实现单点登录的好处是不受主域名必须相同的条件限制,只要是 CAS 服务端信任的客户端,都可以基于 CAS 服务端登录中心完成登录认证。

结合这个实例,回过头再去看上篇教程介绍的 CAS 单点登录原理,是不是可以完全理解了?不再感到云里雾里了?

下篇教程我们将继续围绕 CAS 单点登录展开,演示下如何实现 CAS 单点登录系统的统一退出操作。

学院君 has written 1242 articles

Laravel学院院长,终身学习者

积分:167582 等级:P12 职业:手艺人 城市:杭州

10 条回复

  1. 登录之后跳从服务器端跳不回客户端user页面的,@ AdolphYu $post_login_url这个是在哪里设置的

  2. AdolphYu AdolphYu says:

    '或者在客户端通过访问 CAS 服务端 API 来获取用户信息' 这样的话客户端和服务器端的认证如何做

  3. AdolphYu AdolphYu says:
    @ AdolphYu

    忘记改auth.login 视图中的action 地址了 由action="{{ route('login') }}"改为 action="{{ $post_login_url }}"

  4. miaotaizi miaotaizi says:

    子系统是不是无法直接的管理用户信息? 只负责维护一个 session , 这么理解对吗?

登录后才能进行评论,立即登录?