单点登录思路

2018-06-18 16:39:52 PHP 708 0

后文涉及的跳转逻辑所需,示例站点说明

https://ssl.hlzblog.top/login // 单点登录站点,登录页
https://ssl.hlzblog.top/submit // 单点登录站点,登录表单页
https://ssl.hlzblog.top/redirect // 单点登录站点,重定向页
https://zone.yuntianhe.com/star/articles // 某个应用站点 - 正打算访问的某个地址
https://zone.yuntianhe.com/setcookie // 某个应用站点 - 设置 cookie

单点登录思路

单点登录站点的凭证 与 应用站的凭证 保持一致即可
后文我们都以cookie中的 Auth-Token 作为凭证

  • A网站,访问了需要登录权限的地址
    • 未登录
      • 浏览器没有存储用户有效凭证
      • @动作A:跳向单点登录站点
        • 跳转前,记录所访问的 url 地址、设置 cookie 的回调地址
          • 示例-当前url
            • https://zone.yuntianhe.com/star/articles
          • 示例-跳转前
            • 封装 GET 参数,包含以下数据
              • callback 对应应用站点设置cookie的地址:
              • redirect 正打算访问的某个地址
              • 上面两个地址参数,都得用 urlencode 函数加密
            • 封装结果示例
              • https://ssl.hlzblog.top/login?callback=https%3A%2F%2Fzone.yuntianhe.com%2Fsetcookie&redirect=https%3A%2F%2Fzone.yuntianhe.com%2Fstar%2Farticles
        • 重定向到刚刚封装的结果地址(注:第三方登录与普通帐号的注册、登录、注销都在这完成)
          • 判断 https://ssl.hlzblog.top 站点的cookie中是否为有效的 Auth-Token
            • 存在
              • @动作B:跳回源站
              • 带上GET参数,重定向到 https://ssl.hlzblog.top/redirect 页
                • 示例
                  • https://ssl.hlzblog.top/redirect?callback=https%3A%2F%2Fzone.yuntianhe.com%2Fsetcookie&redirect=https%3A%2F%2Fzone.yuntianhe.com%2Fstar%2Farticles
                • 从本站cookie中取出 Auth-Token,进入应用站点
                  • https://zone.yuntianhe.com/setcookie?redirect=https%3A%2F%2Fzone.yuntianhe.com%2Fstar%2Farticles&Auth-Token=c2f84c60e515f3f1eee3882e958617d9
            • 不存在
              • @动作C:登录请求
              • 正常显示登录页
                • 提交也登录信息,携带GET参数提交到 https://ssl.hlzblog.top/submit
                • 无论提交表单结果如何,都跳转到 https://ssl.hlzblog.top/login?callback=https%3A%2F%2Fzone.yuntianhe.com%2Fsetcookie&redirect=https%3A%2F%2Fzone.yuntianhe.com%2Fstar%2Farticles
                • 如果提交的登录信息正确,则写入 Auth-Token 执行本次跳转
        • 跳回源站 @动作D:登录状态写入
          • 写入cookie,并跳转到 redirect参数对应的页面去
  • A网站,登录成功。通过 Redis,获取到用户的 Auth-Token 存储的身份信息,进行操作

单点登录站

一般通过这个 Auth-Token 凭证,去请求开发的RPC接口
以实现用户相关数据拉取,以此分离不同服务

写入身份-代码示例

<?php
use App\Helpers\Token;
use App\Services\User\LogService;

... // 根据正确的用户登录表单数据 搜索到用户信息
// 删除上一次的登录信息
Token::delete($user->remember_token);

// 写入用户 mobile、token、user_id 到缓存信息
$token      = Token::rand_token();
$token_info = [
    'email'    => $user->email,
    'token'    => $user,
    'user_id' => $user->id,
];
Token::set($token, $token_info);
// 写入登录日志
LogService::user_log($user->id);
// 返回 token 给客户端
return [
    'token' => $token,
];

后记

如果是后台系统的单点登录,还应该把权限控制在这部分完成,如Auth节点级控制
本次单点登录传输 Auth-Token 回源站的安全性问题

附录

<?php
namespace App\Helpers;

// -----------------------
// Link: www.hlzblog.top
// -----------------------

use Illuminate\Support\Facades\Redis;

class Token
{

    /**
     * 随机生成固定长度的随机数
     * @param String  len  截取长度,默认八位
     * @param Int     type 返回类型
     * @return String
     */
    public static function rand_str($len = 8, $type = 'mix')
    {
        switch ($type) {
            case 'mix':
                $str = "0123456789qwertyuiopasdfghjklzxcvbnm~!@#$%^&*()_+";
                break;
            case 'number':
                $str = "0123456789";
                break;
            case 'alpha':
                $str = "qwertyuiopasdfghjklzxcvbnm";
                break;
            default:
                $str = "0123456789qwertyuiopasdfghjklzxcvbnm";
        }
        return substr(str_shuffle($str), 0, $len);
    }

    /**
     * 依据微秒来生成不同字符串
     * @return String 生成的 token
     */
    public static function rand_token()
    {
        return md5(microtime(true) . self::rand_str());
    }

    const TOKEN_PRE    = 'tk:'; // TOKEN 名称 前缀
    const TOKEN_EXPIRE = 2592000; // Token 有效时长,默认,3个月

    public static $token = null; // http请求携带的token

    /**
     * CURD token中的数据
     * 为空时,返回token中的数据,否则更新数据到 token
     * @param Array data
     */
    public static function token_info($data = [])
    {
        if ([] == $data) {
            return self::get(self::$token);
        } else {
            return self::set(self::$token, $data);
        }
    }

    /**
     * 检测AuthToken是否正确
     * @param String token 具体值
     * @return int 用户id
     */
    public static function token_check($token)
    {
        self::$token = $token;
        $arr         = self::get($token);
        if (count($arr)) {
            return $arr['user_id'];
        } else {
            return 0;
        }
    }

    // ------------------------------------
    //      内部逻辑
    // ------------------------------------

    /**
     * 获取 token内容
     * @param String token 具体值
     * @return Array
     */
    public static function get($token)
    {
        $token_name = self::TOKEN_PRE . $token;
        $token_val  = Redis::get($token_name);
        if (!$token_val) {
            return []; // 查询不到时
        }
        return json_decode($token_val, true);
    }

    /**
     * 合并内容到 token
     * @param String token 具体值
     * @param Array data 数组数据
     * @return Void
     */
    public static function set($token, $data)
    {
        $token_name = self::TOKEN_PRE . $token;
        $before     = self::get($token_name);
        $token_life = time() + self::TOKEN_EXPIRE;
        if ($before == null) {
            $before = [];
        }
        $arr = array_merge($before, $data);
        Redis::set($token_name, json_encode($arr));
        Redis::expireAt($token_name, $token_life);
    }

    /**
     * 删除 过期 token
     * @param String token 具体值
     * @return Void
     */
    public static function delete($token)
    {
        $token_name = self::TOKEN_PRE . $token;
        Redis::del($token_name);
    }

}
注:若无特殊说明,文章均为云天河原创,请尊重作者劳动成果,转载前请一定要注明出处