传统登录方案
session+cookie的登录方案
传统的方式的用户掌控力非常强,可以随时踢出用户下线,在
单机用户量小
的时候问题不大,如果是分布式,多产品问题就大了
为什么呢?
多系统(多项目)一般不会多用户管理系统,比方说(淘宝、天猫、支付宝)用户相通。用户管理会抽离出来,登陆信息操作在同一点、(单点登录)
这种方式缺点?
如示意图所示,a和b系统的访问会到认证中心、进行认证。这种方式多余的请求开销比较大,认证中心会随着a和b系统的访问量或用户量增大而增大,比较烧钱,认证中心要做高可用。
单机系统不在这种情况里面
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]);
}