1. 功能列表
1) 支持多种登录模式,如账号密码登录、微信登录、支付宝登录等。
2) 支持单设备在线,因为在每次登录后会重新刷新token ,该用户存在redis里的token也会被刷新掉,更换设备后,先check_token,token不相等就意味着失效,然后前端返回登录页面。
2. 多种模式的token方案登录流程
3. token方案解析
- 将 时间戳+ 用户id 用md5的方式加密得到token作为key 存放到 redis服务器里。value为 用户id或者其他信息, 后续会使用该信息,比如check_token的时候,根据token获取用户信息。
2) 用户每次登录后会产生新的token,并设置失效时间存放到redis里。访问之前需要进行Checktoken,如果前端包含了此token,那么就返回True,不需要再次登录, 如果不包含此token,那么就返回False,前端就需要重新登录以获取新的token,然后前端就可以用新的token来访问系统,只需要在请求头里携带token就可以访问到系统,对于需要验证的请求那么就可以加上auth,通过比对token,相等就通过,不同就拦截。另外django获取请求头的方式前面需要添加:
HTTP_ ,比如我传的请求头是 user-token, 那么在获取的时候我们应该用HTTP_USER_TOKEN。 django会自动将请求头用"_" 进行拼接。
def auth(self, request):
user_token = request.META.get("HTTP_USER_TOKEN")
if user_token is None:
raise AuthException
conn = redis_util.get_redis_connection()
if not conn.exists(user_token):
raise AuthException
- 在登录前,前端一定要Check_token,如果通过此token能够获取到用户信息,那么就直接登录成功,如果不能返回验证错误AuthException, 那么表示账号在其他地方登录被挤下线或者是登录已经失效, 这是无状态的登录, 另外如果根据返回的用户信息中包含了没有验证身份,那么提示用户进行手机号绑定身份验证。如果是账号密码登录,那么就不需要再进行身份验证,因为账号就是手机号。
- check_token验证无效的token后,如果使用三方微信或支付宝登录,我们需要判断是否是首次登录,如果是首次登录,那么返回首次登录标志,前端跳转至用户完善信息页面,如果不是首次登录,那么页面就直接进入到系统首页。
需要注意的是:
- 三方登录用户(微信、支付宝或其他),首次登录需要绑定手机号才能进行后续操作。
- 如果没有绑定手机号退出了,下次进来得提示该用户绑定手机号才能进行后续操作。
- 三方登录用户绑定手机号,如果未绑定手机号就退出,那么就需要提醒再次绑定,需要验证验证码是否正确。解决方法: 可以在填写手机验证后,在登录表里将validation 值设置为1, 那么再获取用户信息(check_token)的时候将值进行返回,这样前端就不会再去跳转到验证页面,如果返回的为0,表示没有验证手机号,前端跳转至验证页面。
- 如果绑定的用户手机号已经存在于user表,那么就先检查是否绑定其他账号,如果绑定了,那么就 返回该账号已经绑定。
5. 如果没有绑定其他账号就将该手机号对应的user表的id更新到login表。
6. 如果该手机号没注册,那么就相当于先注册,然后再绑定。
7. 生成token和刷新token的方法如下
import time
import base64
import hmac
from school_edu_app.redis import redis_util
import hashlib
from HttpResult import AuthException
from school_edu_app.models import SysUser, SysLogin
from Constant import token_exp_time
def md5(key):
md5_key = hashlib.md5()
md5_key.update(key.encode())
return md5_key.hexdigest()
# 根据手机号和角色清除token
def del_token(key, role):
conn = redis_util.get_redis_connection()
user = SysUser.objects.filter(phone=key, role=role).values("id").first()
login = SysLogin.objects.filter(user__id=user["id"])
for i in login:
access_token = i.access_token
if conn.exists(access_token):
conn.delete(access_token)
conn.close()
i.access_token = ""
i.save()
# 每次登录会生成新的token, 根据用户手机号或user_id生成token,同时设置失效时间存放到redis里
def generate_token(key):
conn = redis_util.get_redis_connection()
ts_str = str(time.time()) + "-" + str(key)
token = md5(ts_str)
conn.setex(token, token_exp_time, key)
conn.close()
return token
# 获取用户时,刷新token时效,延长token使用时间
def get_user_by_token(token):
conn = redis_util.get_redis_connection()
user_id = conn.get(token)
if user_id is not None:
user_id = user_id.decode("utf-8")
else:
raise AuthException("登录失效!")
# 刷新token时效
conn.setex(token, token_exp_time, user_id)
conn.close()
return user_id
测试接口: http://localhost:8006/app/check_token/10459db2ab26c0e975f9a461efbfae65
返回结果:
1. 在登录前前端一定要check_token。
2.如果check_token有返回用户信息,表示token有效,如果返回的data为null,那么表示该token无效。
3. 如果验证了手机号,那么validation为True,没有验证手机号就返回False,前端根据此标志是否进行三方新用户手机号验证绑定。
{
"code": 0,
"message": "OK",
"data": {
"id": 2,
"description": null,
"enabled": true,
"name": "张三",
"mail": null,
"gender": null,
"age": null,
"role": 2,
"phone": "15394737178",
"validation": true
},
"elapsed": 324
}
4. db设计
后台只需要两个表,一个sys_user表,一个sys_login表, login表通过user_id与user表进行关联。
sys_user:
/*
Navicat Premium Data Transfer
Source Server Type : MySQL
Source Server Version : 50733
Source Schema : my_school
Target Server Type : MySQL
Target Server Version : 50733
File Encoding : 65001
Date: 01/03/2021 17:55:07
*/
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for sys_user
-- ----------------------------
DROP TABLE IF EXISTS `sys_user`;
CREATE TABLE `sys_user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`description` varchar(5000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`enabled` tinyint(1) NOT NULL,
`create_datetime` datetime(6) NULL,
`update_datetime` datetime(6) NULL,
`delete_datetime` datetime(6) NULL DEFAULT NULL,
`name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`mail` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`gender` int(11) NULL DEFAULT NULL,
`age` int(11) NULL DEFAULT NULL,
`role` int(11) NOT NULL,
`phone` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
`pwd` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
`birthday` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`last_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `phone`(`phone`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of sys_user
-- ----------------------------
INSERT INTO `sys_user`(`id`, `description`, `enabled`, `create_datetime`, `update_datetime`, `delete_datetime`, `name`, `mail`, `gender`, `age`, `role`, `phone`, `pwd`, `birthday`, `last_name`) VALUES (1, NULL, 1, '2022-04-07 09:07:49.000000', NULL, NULL, 'zhangsan', '123456146@qq.com', 1, 23, 1, '', 'ioaseydf12312lsajfda', NULL, NULL);
sys_login:
identifier: 为三方应用的唯一序列串,授权后生成,后台只需要接收即可。
access_token: 为后续访问系统的token, 在redis里也有一个备份。
/*
Navicat Premium Data Transfer
Source Server Type : MySQL
Source Server Version : 50733
Target Server Type : MySQL
Target Server Version : 50733
File Encoding : 65001
Date: 01/03/2021 17:55:16
*/
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for sys_login
-- ----------------------------
DROP TABLE IF EXISTS `sys_login`;
CREATE TABLE `sys_login` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`description` varchar(5000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`enabled` tinyint(1) NOT NULL,
`create_datetime` datetime(6) NULL,
`update_datetime` datetime(6) NULL,
`delete_datetime` datetime(6) NULL DEFAULT NULL,
`login_type` int(11) NOT NULL,
`identifier` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
`validation` tinyint(1) NOT NULL,
`user_id` int(11) NULL DEFAULT NULL,
`access_token` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
PRIMARY KEY (`id`) USING BTREE,
INDEX `sys_login_user_id_9bda9a31_fk_sys_user_id`(`user_id`) USING BTREE,
CONSTRAINT `sys_login_user_id_9bda9a31_fk_sys_user_id` FOREIGN KEY (`user_id`) REFERENCES `sys_user` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE = InnoDB AUTO_INCREMENT = 7 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of sys_login
-- ----------------------------
INSERT INTO `sys_login` VALUES (3, '', 1, '2021-02-26 16:21:57.491866', '2021-03-01 17:08:04.934626', NULL, 0, '', 1, 2, 'd52c2018b21fd83658b777d5d08479a5');
INSERT INTO `sys_login` VALUES (4, '', 1, '2021-02-26 16:23:29.131366', '2021-02-26 17:16:45.380937', NULL, 1, 'wx123456', 1, 1, '6f7f63eb6bdb01a5030abf8fed5f74fb');
INSERT INTO `sys_login` VALUES (5, '', 1, '2021-02-26 17:32:17.306361', '2021-03-01 17:06:15.010139', NULL, 0, '', 1, 1, 'b6470404c2813672c9ae037d89d44526');
INSERT INTO `sys_login` VALUES (6, '', 1, '2021-03-01 09:01:56.651269', '2021-03-01 10:24:07.342097', NULL, 3, 'zdb123456', 1, 2, '');
SET FOREIGN_KEY_CHECKS = 1;