持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第5天,点击查看活动详情
前言
大家好,我是陈同学,一枚野生前端开发者,感谢各位的点赞、收藏、评论
CAS(Central Authentication Service):中央认证服务,一种独立开放指令协议,目标是为Web应用系统提供一种可靠的单点登录方法
本节文章我们就来聊聊在WEB网页应用中CAS实践中的那些事
本文阅读成本与收益如下:
阅读耗时:7mins
全文字数:5k+
预期效益
- 学习接入CAS的流程
- 学习基于现有的CAS服务搭建自己的认证服务
背景
假设我们现在有一台服务器A,因为业务的需要我们要在上面跑若干WEB项目,每一个WEB项目都需要认证用户的学生身份,而学校方提供了一个中央认证服务系统,故我们可以通过接入这套系统来验证学生用户
服务器A
- IP:192.168.1.1
- 域名:xx.test.edu.cn
中央认证服务
- 域名:cas.test.edu.cn
接入流程
第一步|申请
在接入系统之前,学校网络管理处要求每个使用方都需要提交申请信息
- 组织名称
- 组织性质
- 网站用途
- 网站管理者
- 回调地址(这个很重要)
学校网络管理处审核通过后,会给使用方分配一个appid与appsecret
后续使用方便可通过这两个凭证来验证用户身份
第二步|编写CAS-Client逻辑
阅读上一篇文章后,我们了解到接入了CAS认证的应用需要在应用本身多加一层CAS-Client的逻辑
这里我们用PHP代码举例(CAS-Client),总共包括三个文件
优先判断当前用户在当前业务应用系统已登录,如已经登录,无须进行再次中央认证系统授权登录
// https://xx.test.edu.cn/login/index.php
// 用于处理未登陆认证用户的重定向请求
// 请求来源:
// 1. 从中央认证服务登录页重定向过来,通过参数token去中央认证服务拉取用户信息并设置缓存,二次重定向到/login/bridge.php
// 2. 从/login/bridge.php重定向过来,无token参数,则二次重定向至中央认证服务登录页
session_start();
function curl_post($url, $data)
{
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $httpService);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_POST, 1);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); // 对认证证书来源的检查
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false); // 从证书中检查SSL加密算法是否存在
curl_setopt($curl, CURLOPT_POSTFIELDS, $post_data);
$resp = curl_exec($curl);
curl_close($curl);
$resp = json_decode($resp, true);
return $resp;
}
ini_set("display_errors", "On"); // 错误提醒等级
error_reporting(E_ALL | E_STRICT);
$CAS_Server = "https://cas.test.edu.cn/"; // 中央认证登录页URL
$httpService = "https://cas.test.edu.cn/ssoapi/v2/checkToken"; // 中央认证兑票URL
$userInfoService = "https://cas.test.edu.cn/oauth/getUserInfo"; // 获取用户信息URL
$bridgeUrl = "https://xx.test.edu.cn/login/bridge.php";
$MyAppid = "APPID"; // 本应用系统appid
$MyAppSecret = 'APPSECRET'; // 本应用系统appsecret
$state = 'ABC'; // 声明state变量,非必须,最大长度128
// 检查token票据
$token = $_GET['token']; // 从query中取token
if ($token == '') {
/**
* 情况一:URL中没有带票据(query中的token),转到中样认证登录页
*/
$url = $CAS_Server . "?appid=" . $MyAppid . '&state=' . $state; // 中中央认证登陆页面
header("Location: " . $url); // 重定向用户页面
return;
} else {
/**
* 情况二:URL中有带票据(query中的token),开始兑票(请求中央认证服务,拉用户信息)
*/
// 用户验证登录成功后,系统会自动回调到预设的回调地址并携带token,如果有state声明参数则会一起返回
$userIp = $_SERVER['REMOTE_ADDR'];
$post_data = [
'appid' => $MyAppid,
'appsecret' => $MyAppSecret,
'userip' => $userIp
'token' => $token,
];
$resp = curl_post($httpService, $post_data);
// 校验成功:
// array(5) {
// ["error"]=>int(0),
// ["message"]=>string(7)"success",
// ["openid"]=>string(32)"XXX",
// ["access_token"]=>string(32)"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
// }
// 校验失败:
// array(2) {
// ["error"]=>int(1),
// ["message"]=>string(26)"token不存在或已过期"
// }
var_dump($resp);
// 验证token获取用户openid和access-token
if ($resp['error'] == 0) {
// 获取用户信息
$post_data = [
'access_token' => $resp['access_token'],
'openid' => $resp['openid']
];
$resp = curl_post($userInfoService, $post_data);
// ACT有效:
// array(4) {
// ["username"]=> string(7) "XXX",
// ["name"]=> string(9) "XXX",
// ["group"]=> string(7) "Student",
// ["openid"]=> string(32) "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
// }
// ACT无效:
// array(2) {
// ["error"]=> int(1),
// ["message"]=> string(26) "access-token不存在或已过期"
// }
var_dump($resp);
saveUserInfo($resp); // 持久化用户信息到数据库
$xx_token = hash("sha1", bin2hex(random_bytes(64))); // 颁发一个TOKEN字符串
Cache::set($xx_token, json_encode($resp), 3600); // 缓存用户信息数据
header("Location: ".$bridgeUrl."xx_token=".$xx_token);
return;
} else {
echo $resp['message'];
}
}
// https://xx.test.edu.cn/login/bridge.php
// https://xx.test.edu.cn/login/bridge.php?app=TESTAPP1
// 用于处理当前服务器系统上的应用映射关系
// 请求来源:
// 1. 应用A发现用户登录态失效则带app参数重定向到此处设置session,并二次重定向至/login/index.php
// 2. 用户从/login/index.php中拿到xx_token重定向到此处进行地址转化,并二次重定向至登陆的目标应用系统URL
// 保存系统中需要认证用户信息的应用回调地址映射关系
$defaultUrl = 'https://xx.test.edu.cn';
$apps = [
'TESTAPP1'=>'https://xx.test.edu.cn/app1/login',
'TESTAPP2'=>'https://xx.test.edu.cn/app2/login',
];
$app = $_GET['app']; // 取出key为app的GET请求参数,如:TESTAPP1
$xx_token = $_GET['xx_token']; // 取出key为xx_token的GET请求参数,如:XXX
if ($app != '') {
if (array_key_exists($app, $apps)) {
// 当前服务器上存在目标应用的APPID,则设置session并重定向
$_SESSION['refererApp'] = $app;
header("Location: https://xx.test.edu.cn/login/index.php");
return;
} else {
// 不存在app参数对应的appid
echo '不存在的appid';
header("Location: ".$defaultUrl);
return;
}
} else {
$refererApp = $_SESSION['refererApp'];
$url = $refererApp ? $apps[$refererApp] : 'https://xx.test.edu.cn';
header("Location: ".$url."?xx_token".$xx_token); // 重定向到业务应用,业务应用可以通过xx_token参数到/login/token.php拉取用户信息
return;
}
// https://xx.test.edu.cn/login/token.php?xx_token=XXXXXXXXX
// 请求来源:
// 1. 服务器上的其他应用
$token = $_GET['xx_token'];
$resp = Cache::get($token);
if($resp === false) {
abort('404', 'token不存在或已过期.');
}
echo $resp;
以上代码示例请结合注释食用
CAS-Client的体系结构如下
- 认证接口(login/index.php)
- 有token则换票,获取用户信息并持久化,构建用户登录态,最后重定向/login/bridge.php
- 无token则重定向中央认证登录页
- 中间辅助接口(login/bridge.php)
- 有app参数(来源于系统应用跳转),设置refererApp的session,并重定向/login/index.php
- 无app参数,取session中的refererApp,若无则抛出错误,否则根据refererApp取出目标应用URL进行重定向
- 用户信息获取接口(login/token.php)
- 系统中其它应用获取用户信息使用
第三部|业务应用服务接入
搭建了以上三个接口的逻辑后,我们便可以进行业务应用的接入
$login_url = "https://xx.test.edu.cn/login/";
$appid = 'TESTAPP1';
$token = input('get.xx_token');
if (null == $token) {
return redirect('https://xx.test.edu.cn/login/bridge.php?app=' . $appid, 302);
}
$token_url = 'https://xx.test.edu.cn/login/token.php?xx_token='.$token;
$arrContextOptions = [
"ssl" => [
"verify_peer"=>false,
"verify_peer_name"=>false,
],
];
$response = file_get_contents($token_url, false, stream_context_create($arrContextOptions));
$resp = json_decode($response, true); // 应用系统拿到用户信息
// 后续可以定义业务系统登录态
if ($resp['Result'] == '0') {
session('username',$resp['LoginName']);
}
redirect('https://xx.test.edu.cn/app1')
return;
讲到最后
本篇文章学习了CAS中央认证登陆体系WEB侧的完整认证流程,读者可以根据文章内容与思路自行实现一下以加深印象
谢谢大家,我们下节再见!!!
感谢各位看到这里,如果你觉得本节内容还不错的话,欢迎各位的点赞、收藏、评论,大家的支持是我做内容的最大动力
本文为作者原创,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接,否则保留追究法律责任的权利