实现目标说明

用户登录最少需要做到如下两点:
1.合法性,即 是他自己登录的账号( 做法 :token + https ,token验证可以确定操作源是该用户本人,https可以在很大程度上保证token在传输的过程中不被截获篡改,是目前较为安全的一种做法)
2.唯一性,即 不能对同一个账号重复登录( 做法 :服务器保存生成的token后再将其发送给用户,每次用户请求数据时, 先验证token是否相同,然后再验证token是否有效 。相同,则说明用户唯一,有效则说明用户合法。 注意 :就算传入token和数据库中的token相同,也不能说明用户合法,因为数据库中的token可能已经过期无效了,如果token过期就让用户重新登录,然后再次签发和保存token。 ps :token必须具有时效性,否则用户数据被破解只是时间问题)
做法总结:
1.djangorestframework + djangorestframework-jwt 实现用户的token登录。全局验证token有效性(注册、登录和网站通用视图除外),并且只通过登录视图发放token给用户。
2.通过mysql保存用户token,确保单个账号只有一个用户在线。用户的每次操作都需要对比请求头中的token和数据库中的token是否相同,全局验证token唯一性,(注册、登录和网站通用视图除外)

首先创建自定义用户模块

  1. 要登陆就需要有 用户模块 ,django本身自带auth用户模块,但不能满足现在五花八门的用户字段需求。好在django还提供了 自定义用户模块 的功能,创建方法如下:

输入命令创建user

# 输入命令
python manage.py startapp user

编写自定义用户

from django.db import models
from django.contrib.auth.models import AbstractUser
# 继承 django 的 AbstractUser
class UserInfo(AbstractUser):
    自定义的用户模块
    nick_name = models.CharField(max_length=15, default="NoOne", verbose_name="昵称")
    sign = models.TextField(max_length=100, default="要不写点什么,反正你挺闲的、、", verbose_name="签名")
    token = models.CharField(max_length=300, null=True, blank=True, verbose_name="用户认证token")
    class Meta:
        verbose_name = "用户信息"
        verbose_name_plural = verbose_name
    def __str__(self):
        return self.username

配置settings.py文件

# 在 settings.py 文件中添加如下内容,其中 UserInfo 是自己定义的用户模块名称
AUTH_USER_MODEL = 'user.UserInfo'   # 采用自己定制的用户model
# 在settings中注册自定义的用户模块
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework',
    'crispy_forms',
    'corsheaders',
    'apps.user',

注册用户到django admin

# 别忘记注册到 django admin 后台管理系统
# 如下两种方式选一种就行
# 1 显示所有字段
admin.site.register(UserModel)
# 2 显示自定义的字段
# @admin.register(UserInfo)
# class CategoryAdmin(admin.ModelAdmin):
    # list_display = ('nick_name', 'sign', 'username', 'email')
    # fields = ('nick_name', 'sign', 'username', 'password', 'email')

最后 makemigrations 和 migrate 同步数据库,然后打开数据库管理工具就可以看到自定义的用户表了。到此,自定义用户模块完成。

安装配置djangorestframework-jwt模块

2.安装并配置token验证模块djangorestframework-jwt,如下:

输入命令安装

pip install djangorestframework-jwt

配置settings.py文件

# DRF中间件 REST_FRAMEWORK = { 'DEFAULT_PERMISSION_CLASSES': ( # 提供的权限↓ # AllowAny 允许所有用户 # IsAuthenticated 仅通过认证的用户 # IsAdminUser 仅管理员用户 # IsAuthenticatedOrReadOnly 认证的用户可以完全操作,否则只能get读取 'rest_framework.permissions.IsAuthenticated', 'DEFAULT_AUTHENTICATION_CLASSES': ( # 认证方式 # 此方法是自定义方法,用于检查用户是否重复登录,后面会讲 'utils.my_authentication.LoginRepeatAuth', # 此方法是 djangorestframework-jwt 的方法,用于检查用户token是否合法 'rest_framework_jwt.authentication.JSONWebTokenAuthentication', JWT_AUTH = { # 指明Token的有效期 'JWT_EXPIRATION_DELTA': datetime.timedelta(days=1), 'JWT_AUTH_HEADER_PREFIX': 'JWT',

编写 检查重复登录 的方法

3.编写检查用户重复登录的方法:

from rest_framework import exceptions
from rest_framework.authentication import BaseAuthentication
from apps.user.models import UserInfo
class LoginRepeatAuth(BaseAuthentication):
    # authenticate必须在DRF认证内被重写,request参数必须传入
    def authenticate(self, request):
        将获取的token去token表内进行比对,存在信息即验证通过
            - 获取token表内token更新时间,若超时则验证失败重新登陆(用于数据的清理)
            - 验证通过:返回空 - 表示后续仍能继续其他验证
            = 验证通过:返回认证用户和当前数据记录对象 - 后续不再进行验证
                - 对应DRF内Request对象User类内_authenticate方法执行
                - from rest_framework.request import Request
        :param request:
        :return:
        # 数据放在header内传输,request.META获取
        # meta查询key值格式:HTTP_大写字段名 例如:token - HTTP_TOKEN
        token = request.META.get('HTTP_AUTHORIZATION')
        # token = request.query_params.get('token')
        # 查找是否存在token值和请求头中的token值相同的用户
        user = UserInfo.objects.filter(token=token).first()
        if not user:
            # 如果没有,就认为是跳过了登录阶段的非法操作.
            # 因为只有登录方法会保存生成的token并返回给用户,如果保存的token和用户携带的token不同,说明用户token被篡改,或有人尝试破解用户的token.
            # 此时应终止方法,并要求用户重新登录,以便保存并返回给用户新的token
            raise exceptions.APIException('token比对失败,非法操作,请勿跳过登录方法!')
        # 查询到对应用户信息,认证通过
        # 此时如果接下来还有验证方法,就返回 None
        # 如果接下来没有验证方法了,那么返回 当前认证用户 和 当前token记录对象
        # 返回的数据可通过 request.user, request.auth进行获取
        return None

编写用户 注册 登录 和 修改密码 的view

  1. 编写用户 注册 登录修改密码 方法, 注册和登录方法 用于用户上线,修改密码方法用于测试用户的 合法性 和 唯一性 如下:
from rest_framework.views import APIView
from rest_framework.renderers import JSONRenderer
from rest_framework.response import Response
from rest_framework import status
from rest_framework_jwt.settings import api_settings
from .models import UserInfo
class RegisterView(APIView):
    parm = [ username, password ]
    renderer_classes = [JSONRenderer]   # json渲染器
    authentication_classes = []         # 此方法不验证JWT
    permission_classes = []             # 此方法不设权限
    def post(self, request):
        username = request.data.get("username", 0)
        password = request.data.get("password", 0)
        if username and password:
            # 校验注册,名字不可重复
            user = UserInfo.objects.filter(username=username).first()
            if user:
                content = {'msg': '用户已存在'}
                return Response(content, status=status.HTTP_400_BAD_REQUEST)
            else:
                # 注册成功,创建用户
                UserInfo.objects.create_user(
                    username=username,
                    password=password
                content = {'msg': '注册成功'}
                return Response(content, status=status.HTTP_201_CREATED)
        content = {'msg': '账号或密码不能为空'}
        return Response(content, status=status.HTTP_403_FORBIDDEN)
class LoginView(APIView):
    parm = [ username, password]
    renderer_classes = [JSONRenderer]   # json渲染器
    authentication_classes = []         # 此方法不验证JWT
    permission_classes = []             # 此方法不设权限
    def post(self, request):
        # 登录的业务逻辑 start
        username = request.data.get("username", 0)
        password = request.data.get("password", 0)
        if not username or not password:
            content = {'msg': '输入的账号或密码有误'}
            return Response(content, status=status.HTTP_400_BAD_REQUEST)
        else:
            user = UserInfo.objects.filter(username=username).first()
            if not user or not user.check_password(password):
                content = {'msg': '输入的账号或密码有误'}
                return Response(content, status=status.HTTP_400_BAD_REQUEST)
            else:
                # 生成token的业务逻辑 start
                jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
                jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
                payload = jwt_payload_handler(user)
                token = jwt_encode_handler(payload)
                token_with_JWT = "JWT " + token     # 为token添加JWT头后保存到数据库
                user.token = token_with_JWT
                user.save()
                # 生成token的业务逻辑 end
                content = {'Authorization': token_with_JWT}
                return Response(content, status=status.HTTP_200_OK)
        # 登录的业务逻辑 end
class ChangePassWord(APIView):
    parm = [ username, password, new_password ]
    renderer_classes = [JSONRenderer]  # json渲染器
    def post(self, request):
        username = request.data.get("username", 0)
        old_password = request.data.get("old_password", 0)
        new_password = request.data.get("new_password", 0)
        if username and old_password and new_password:
            # 校验用户名和密码
            user = UserInfo.objects.filter(username=username).first()
            if user and user.check_password(old_password):
                # 校验成功,保存新密码
                user.set_password(new_password)
                user.save()
                content = {'msg': '密码修改成功'}
                return Response(content, status=status.HTTP_205_RESET_CONTENT)
            else:
                content = {'msg': '原密码输入有误'}
                return Response(content, status=status.HTTP_400_BAD_REQUEST)
        content = {'msg': '账号或密码不能为空'}
        return Response(content, status=status.HTTP_400_BAD_REQUEST)

配置三个视图的url

# 别忘记将视图的url配置好,此处的 DEV_NAME 是我定义的应用名,可以去掉
# 用户登录(JWT唯一获取接口)
path(DEV_NAME + 'login/', LoginView.as_view()),
# 用户注册
path(DEV_NAME + 'register/', RegisterView.as_view()),
# 用户修改密码
path(DEV_NAME + 'change_pw/', ChangePassWord.as_view()),

通过postman测试用户的 合法性 和 唯一性

  1. 完成上述步骤后,就可以通过postman进行测试了.

首先测试用户合法性:
注册账号,
登录并复制返回的token
粘贴token到请求头,然后携带请求头进行密码修改,
提示密码修改成功.

然后测试用户唯一性:
再次登录刚才的账号获得一个新的token,但是我们不用这个token,
仍然使用刚才的那个token进行密码修改,这时会提示 ‘token比对失败,非法操作,请勿跳过登录方法!’,
这样就确保了最新一次的登录会覆盖掉之前的登录,保证了用户的唯一性.

DRF + jwt + mysql 防止用户重复登录实现目标说明创建自定义用户模块安装配置djangorestframework-jwt模块编写 检查重复登录 的方法编写用户 注册 登录 和 修改密码 的方法通过postman测试用户的 合法性 和 唯一性实现目标说明1.djangorestframework + djangorestframework-jwt 实现用户的token登录2.通...
问题描述: 在使用jwt生成token并且使用redis作为tokenStore的时候,需要防止单个用户单客户端多个浏览器同时登录的情况下通过重写 DefaultTokenServices token生成工具来满足需求 只需要注释掉一个判断语句即可 代码如下: @Transactional public OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) throws AuthenticationEx
我们后台项目是用Spring Security+JWT+OAuth2做的安全框架,现在有个需求就是同一账号重复登录时第一次登陆成功的强制下线。尝试了很多方法无果,当时真的是被弄得焦头烂额了。最后功夫不负有心人,最后找到了解决方案,下面跟大家分享下。 具体实现方案就是重写DefaultTokenServices的createAccessToken方法(下发token的方法) 虽说是重写但是我们新建了一个SingleTokenServices类,将DefaultTokenServices中的方法复
最近在项目上遇到了一个同一账号多终端(或者说多用户)同时登录导致的token重复问题。可以在浏览器相应地做一些防止表单重复提交的操作,比如登录按钮点击一次后变成不可点击的状态,等待服务器的响应之后再恢复成点击状态。不过这也并不能解决同一账号多终端登录的问题。 https://github.com/bluesnail9... 项目中的伪代码...
jwt是一种无状态的身份验证方式,简单来说就是服务器不保存用户的信息状态。用户权限登录信息保存在token里,这样服务器只需要验证jwt token就能获取到用户权限相关信息 但是jwt验证过期只有生成的时候会设置过期时间,要想时之前的token失效可以借助于过滤器和缓存来实现 处理方式有很多种,这里拿spring security举例
导航项目场景:问题描述:解决思路:后端解决方案代码:JWT实现鉴权中心,写成服务注入IOC容器中Redis写成服务注入IOC容器中使用signalR进行websocket通信登录控制器引用上述服务前端解决方案代码最终效果总结 项目场景: web\小程序。限制账户同时在线、登录。或者限制账户并发数 项目环境:asp.net core 3.1 API+redis+jwt +微信小程序 问题描述: 我们经常可以看到一些软件无法同时登录同一个账号,也就是同时只能有一个用户使用这个账号。或者另一种情形,限制用户
问题描述: 首先后端生成的token是有时限的,在一段时间后不管前端用户是否进行了访问后端的操作,后端的token都会过期,在拦截器阶段就会返回错误的请求:token过期,从而拿不到想要的请求数据. 解决思路: 每隔一段时间的后端请求中都将token传送过去获取新的token并返回前端放入cookies中并记录cookie的存储失控,达到更新cookie中token的效果;而长时间不做操作的话我...
jwt的规范目前只检测jwt的发布者,过期时间,签名等信息.大部分现成的库都是按照标准写的.但是标准没有要求jwt带入登录时间等信息,因此用户连续登录多次,后台返回的token在有效期内都能访问后台api.也就是用户可以在多个设备同时登录. 有人会采用在登录时将jwt用redis等储存起来,在api的中间件检查时去查数据库中是否储存了这个token,设置过期时间.如果用户再次登录,那么将该tok...
Django REST framework (DRF) 可以通过使用 authentication 和 permission classes 实现用户登录和注册功能。对于注册功能,可以在视图中使用 serializers 从请求体中获取并验证用户输入的数据。导入 DRF 自带的 UserSerializer 类,并根据需求定制,然后在视图中使用该 serializer 进行表单验证和创建新用户。对于登录功能,可以使用 TokenAuthentication 来为用户提供 token,以便他们可以使用其他受保护的资源。可以在视图中检查用户凭据的有效性,并使用 TokenAuthentication 为认证用户提供存储在数据库中的 token
if math.isnan(sth): # 这个是判断数值的,如果没有这个判断,那么数值0也会直接返回True(空) return True if isinstance(sth, float): if np.isnan(sth): return True return False [/code]
Mysql group by 查询报错 1055 this is incompatible with sql_mode=only_full_group_by 盼小辉丶: 优质好文,收藏起来慢慢学习~ pytorch泰坦尼克号幸存者预测(二分类) 盼小辉丶: 优质好文,收藏起来慢慢学习~ pytorch房价预测(线性回归) 盼小辉丶: 优质好文,收藏起来慢慢学习~ pytorch房价预测(线性回归) 虚心求知的熊: 非常实用,博主介绍的非常详细,学到了许多新知识,希望大佬也能对我的文章指点下!