传统登录方案

session+cookie的登录方案

示意图

传统的方式的用户掌控力非常强,可以随时踢出用户下线,在单机用户量小的时候问题不大,如果是分布式,多产品问题就大了


为什么呢?

多系统(多项目)一般不会多用户管理系统,比方说(淘宝、天猫、支付宝)用户相通。用户管理会抽离出来,登陆信息操作在同一点、(单点登录)


这种方式缺点?

如示意图所示,a和b系统的访问会到认证中心、进行认证。这种方式多余的请求开销比较大,认证中心会随着a和b系统的访问量或用户量增大而增大,比较烧钱,认证中心要做高可用。

单机系统不在这种情况里面

token模式

示意图token模式


为了减少服务器压力,减少像认证中心发送请求,演变了token模式。一般用JWT技术,子系统自我验证,是属于分布式设计。
不受子系统扩容影响。

缺点

认证中心失去了对用户的掌控,比方说,让这个用户禁止登录了,但是因为这个用户的手里还持有token,在token的生命周期里访问还是有效的,做不到及时。如果子系统多控制会更麻烦

token + RfreshToken 模式

示意图


用户登录成功后、服务器发送2个token。一个时间有效时间长(refreshToekn)可以设置1个月、一周等,一个有效时间短30分种或者最多不超过1小时。

用短的token进行访问受保护的资源。等过期了用长refreshToekn去换短token

token无感刷新代码

前端使用axios库来做请求

<!DOCTYPE html>
<html lang="zh-cmn-Hans">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
  <script src="/axios.min.js"></script>
</head>
<body>

<script>


  function getToken() {
    return localStorage.getItem('token');
  }

  function setToken(newToken) {
    localStorage.setItem('token', newToken);
  }

  function setRefreshToken(newRefreshToken) {
    localStorage.setItem('refreshToken', newRefreshToken);
  }

  function getRefreshToken() {
    return localStorage.getItem('refreshToken');
  }

  function login() {
    return ins.post('token.php?login');
  }

  function reqProtected() {
    return ins.post('token.php?protected');
  }

  let promise;

  async function refreshToken() {
    if (promise) {
      return promise;
    }
    console.log("刷新token");
    promise = new Promise(async (resolve) => {
      const resp = await ins.get('token.php?refresh_token', {
        headers: {
          Authorization: `Bearer ${getRefreshToken()}`
        },
        __isRefreshToken: true,
      });
      //根据后台定义的code值
      resolve(resp.code === 200);
    });
    promise.finally(() => {
      promise = null;
    })
    return promise;
  }

  function isRefreshRequest(config) {
    return !!config.__isRefreshToken;

  }

  const ins = axios.create({
    baseUrl: 'http://localhost',
    headers: {
      Authorization: `Bearer ${getToken()}`
    }
  });
  ins.interceptors.response.use(async (res) => {
    if (res.headers.authorization) {
      const token = res.headers.authorization.replace('Bearer ', '');
      setToken(token);
      ins.defaults.headers.Authorization = `Bearer ${token}`;
    }
    if (res.headers.refreshtoken) {
      const refreshtoken = res.headers.refreshtoken.replace('Bearer ', '');
      setRefreshToken(refreshtoken);
    }
    console.log(!isRefreshRequest(res.config));
    //业务逻辑定义无权限的
    if (res.data.code === 401 && !isRefreshRequest(res.config)) {
      //刷新token
      const isSuccess = await refreshToken();
      if (isSuccess) {
        //重新请求
        console.log('重新请求');
        res.config.headers.Authorization = `Bearer ${getToken()}`
        const resp = await ins.request(res.config)
        return resp;
      } else {
        // 无权限
        console.log('到登陆页面');
      }
    }
    return res.data;
  })

</script>
<button onclick="login()">登陆</button>
<button onclick="reqProtected()">访问受保护的接口</button>
<button onclick="refreshToken()">刷新token</button>
</body>
</html>

后端PHP简单的代码

<?php
require_once "vendor/autoload.php";

use Firebase\JWT\JWT;


$redis = new \Redis();
$code = 200;
$msg = "";
$data = "";
$key = "qidong";
$headers = getallheaders();
$token="";
if (key_exists('Authorization', $headers)) {
    $token = str_replace('Bearer ', '', $headers['Authorization']);
}
function checkToken($token,$code=401): void {
    if (empty($token)) {
        echo json_encode(['code' => $code, 'data' => "", "msg" => "错误"]);
        die;
    }
}

if (key_exists('login', $_GET)) {
    
    $tokenArr = [
     "iss" => "https://echo.solo90.com",
     "iat" => mt_rand(1, 99),
     "exp" => time() + 5,
     "user_id" => 1
    ];
    $tokenJWT = JWT::encode($tokenArr, $key, 'HS256');
    $redis->setex($tokenJWT, 5, 1);
    $tokenArr['exp'] = time() + 7200;
    $refreshTokenJWT = JWT::encode($tokenArr, $key, 'HS256');;
    $redis->setex($refreshTokenJWT, 3600, 1);
    header("Authorization:Bearer " . $tokenJWT);
    header("refreshtoken:Bearer " . $refreshTokenJWT);
    echo json_encode(['code' => 200, "data" => "", "msg" => "登陆成功"]);
}

if (key_exists('refresh_token', $_GET)) {
    checkToken($token);
    
    $bool = $redis->get($token);
    if ($bool) {
        $tokenArr = [
         "iss" => "https://echo.solo90.com",
         "iat" => mt_rand(1, 99),
         "exp" => time() + 5,
         "user_id" => 1
        ];
        $tokenJWT = JWT::encode($tokenArr, $key, 'HS256');
        $redis->setex($tokenJWT, 10, 1);
        header("Authorization:Bearer " . $tokenJWT);
        $msg = "刷新成功";
    } else {
        $code =500;
        $msg = "token过期";
    }
    
    echo json_encode(['code' => $code, "data" => "", "msg" => $msg]);
    
}

if (key_exists('protected', $_GET)) {
    checkToken($token);
    $bool = $redis->get($token);
    if ($bool) {
        $msg = "受保护的资源";
    } else {
        $code = 401;
        $msg = "token过期";
    }
    
    echo json_encode(['code' => $code, 'data' => "", "msg" => $msg]);
}
Last modification:April 14, 2024
如果觉得我的文章对你有用,请随意赞赏