基于 CAS 实现通用的单点登录解决方案(二):CAS 客户端搭建及单点登录测试
上篇教程学院君给大家介绍了 CAS 单点登录原理以及 CAS Server 端搭建,这篇教程我们书接上篇,着手客户端测试应用搭建和完整单点登录流程演示。
客户端配置
首先我们创建两个用于单点登录测试的客户端 Web 应用 testapp
和 otherapp
(如果对应应用之前已经存在,则无需重复创建),下面我们以 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.php
到 config
目录下:
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_SAML
为 false
,因为目前服务端不支持 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.php
的 timezone
默认配置值:
'timezone' => 'Asia/Shanghai',
当然,最好把 testapp
和 otherapp
两个客户端应用的对应配置值也做这样的调整。这样,看起日志来就不会有时区错乱的问题了。
在服务端注册客户端应用
接下来,我们需要在 CAS 服务端中注册客户端应用服务,打开服务端数据库,在 cas_services
数据表中注册两个服务,分别是 otherapp_auth
和 app_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
进行验证,验证通过后将认证用户信息返回给 otherapp
,otherapp
以之前创建的 CASAuth
Cookie 作为 Session ID,根据返回的认证用户信息设置 Session,从而完成 otherapp
的登录认证。
整个过程中,我们只是在第一次 testapp.test
中进行登录认证的时候在 CAS 服务端进行了一次登录操作,后续其它子系统的登录认证只需要通过服务端分配 Ticket 并基于该 Ticket 进行认证验证即可,所以也是一次登录,即可在信任的系统之间访问认证资源。这个信任是通过在 CAS 服务端注册审核客户端应用来完成授权的。而且基于 CAS 实现单点登录的好处是不受主域名必须相同的条件限制,只要是 CAS 服务端信任的客户端,都可以基于 CAS 服务端登录中心完成登录认证。
结合这个实例,回过头再去看上篇教程介绍的 CAS 单点登录原理,是不是可以完全理解了?不再感到云里雾里了?
下篇教程我们将继续围绕 CAS 单点登录展开,演示下如何实现 CAS 单点登录系统的统一退出操作。
23 Comments
学院君,您好,我将服务端login.blade.php中form表单的action参数改为了action="{{ url($post_login_url) }}",这个时候数据库中能够生成ticket,并且可以看到浏览器中回调地址为http://app.test/user?ticket={TicketValue},但是拿不到用户的信息,并且浏览器页面显示错误ErrorException DOMDocument::loadXML(): Opening and ending tag mismatch: meta line 86 and head in Entity, line: 641,并且跟踪到http请求返回Status Code: 500 Internal Server Error错误
需要把 testapp 的数据库设置成和 blog56 的数据库一致
本地项目的话要在hosts加入你的域名,linux的就在/etc/hosts上加入一个域名,不然ping不通是不会得到返回数据的