[陈同学i前端] CAS实践 | WEB

345 阅读5分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 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

接入流程

第一步|申请

在接入系统之前,学校网络管理处要求每个使用方都需要提交申请信息

  • 组织名称
  • 组织性质
  • 网站用途
  • 网站管理者
  • 回调地址(这个很重要)

学校网络管理处审核通过后,会给使用方分配一个appidappsecret

后续使用方便可通过这两个凭证来验证用户身份

第二步|编写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的体系结构如下

  1. 认证接口(login/index.php)
  • 有token则换票,获取用户信息并持久化,构建用户登录态,最后重定向/login/bridge.php
  • 无token则重定向中央认证登录页
  1. 中间辅助接口(login/bridge.php)
  • 有app参数(来源于系统应用跳转),设置refererApp的session,并重定向/login/index.php
  • 无app参数,取session中的refererApp,若无则抛出错误,否则根据refererApp取出目标应用URL进行重定向
  1. 用户信息获取接口(login/token.php)
  • 系统中其它应用获取用户信息使用

20221003231843

第三部|业务应用服务接入

搭建了以上三个接口的逻辑后,我们便可以进行业务应用的接入

$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侧的完整认证流程,读者可以根据文章内容与思路自行实现一下以加深印象

谢谢大家,我们下节再见!!!

感谢各位看到这里,如果你觉得本节内容还不错的话,欢迎各位的点赞、收藏、评论,大家的支持是我做内容的最大动力

本文为作者原创,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接,否则保留追究法律责任的权利

补充

用户认证 | 抽象的SSO具象的CAS