美文网首页
项目笔记/配置/jwt/isdangerous

项目笔记/配置/jwt/isdangerous

作者: LiWei_9e4b | 来源:发表于2018-11-07 10:18 被阅读0次

    前端文件开发预览

    可以使用前端node.js 提供的服务器live-server作为前端开发服务器使用。安装node.js的版本控制工具nvm,在终端中执行
    curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.11/install.sh | bash
    重新进入终端,使用nvm安装最新版本的node.js
    nvm install node
    安装live-server
    npm install -g live-server
    使用:在静态文件目录front_end_pc下执行
    live-server
    live-server运行在8080端口下,可以通过127.0.0.1:8080来访问静态页面。

    修改manage.py文件,在开发和生产环境中指定使用不同的配置:
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "meiduo_mall.settings.dev")

    为本项目创建数据库用户(不再使用root账户)
    create user meiduo identified by 'meiduo';
    grant all on meiduo_mall.* to 'meiduo'@'%';
    flush privileges;
    说明:
    第一句:创建用户账号 meiduo, 密码 meiduo (由identified by 指明)
    第二句:授权meiduo_mall数据库下的所有表(meiduo_mall.*)的所有权限(all)给用户meiduo在以任何ip访问数据库的时候('meiduo'@'%')
    第三句:刷新生效用户权限

    根据配置文件路径,来设置项目的工程路径,便于导包
    BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(file)))

    添加导包路径,将apps路径添加进去

    import sys
    sys.path.insert(0, os.path.join(BASE_DIR, 'apps'))

    在INSTALLED_APPS中添加rest_framework框架
    INSTALLED_APPS = [
    'rest_framework',
    ]

    3. 数据库

    DATABASES = {
        'default': {
            'ENGINE': 'django.db.backends.mysql',
            'HOST': '127.0.0.1',  # 数据库主机
            'PORT': 3306,  # 数据库端口
            'USER': 'name',  # 数据库用户名
            'PASSWORD': 'mima',  # 数据库用户密码
            'NAME': 'database_name'  # 数据库名字
        }
    }
    

    注意:在项目包init中添加mysql转换操作:

    import pymysql
    pymysql.install_as_MySQLdb()
    

    4. Redis

    安装django-redis,并配置

    CACHES = {
        "default": {
            "BACKEND": "django_redis.cache.RedisCache",
            "LOCATION": "redis://10.211.55.5:6379/0",
            "OPTIONS": {
                "CLIENT_CLASS": "django_redis.client.DefaultClient",
            }
        },
        "session": {
            "BACKEND": "django_redis.cache.RedisCache",
            "LOCATION": "redis://10.211.55.5:6379/1",
            "OPTIONS": {
                "CLIENT_CLASS": "django_redis.client.DefaultClient",
            }
        }
    }
    SESSION_ENGINE = "django.contrib.sessions.backends.cache"
    SESSION_CACHE_ALIAS = "session"
    

    除了名为default的redis配置外,还补充了名为session的redis配置,分别使用两个不同的redis库。同时修改了Django的Session机制使用redis保存,且使用名为'session'的redis配置。

    关于django-redis 的使用,说明文档可见http://django-redis-chs.readthedocs.io/zh_CN/latest/

    5. 本地化语言与时区

    LANGUAGE_CODE = 'zh-hans'
    TIME_ZONE = 'Asia/Shanghai'
    

    6. 日志

    LOGGING = {
        'version': 1,
        'disable_existing_loggers': False,  # 是否禁用已经存在的日志器
        'formatters': {  # 日志信息显示的格式
            'verbose': {
                'format': '%(levelname)s %(asctime)s %(module)s %(lineno)d %(message)s'
            },
            'simple': {
                'format': '%(levelname)s %(module)s %(lineno)d %(message)s'
            },
        },
        'filters': {  # 对日志进行过滤
            'require_debug_true': {  # django在debug模式下才输出日志
                '()': 'django.utils.log.RequireDebugTrue',
            },
        },
        'handlers': {  # 日志处理方法
            'console': {  # 向终端中输出日志
                'level': 'INFO',
                'filters': ['require_debug_true'],
                'class': 'logging.StreamHandler',
                'formatter': 'simple'
            },
            'file': {  # 向文件中输出日志
                'level': 'INFO',
                'class': 'logging.handlers.RotatingFileHandler',
                'filename': os.path.join(os.path.dirname(BASE_DIR), "logs/meiduo.log"),  # 日志文件的位置
                'maxBytes': 300 * 1024 * 1024,
                'backupCount': 10,
                'formatter': 'verbose'
            },
        },
        'loggers': {  # 日志器
            'django': {  # 定义了一个名为django的日志器
                'handlers': ['console', 'file'],  # 可以同时向终端与文件中输出日志
                'propagate': True,  # 是否继续传递日志信息
                'level': 'INFO',  # 日志器接收的最低日志级别
            },
        }
    }
    

    7. 异常处理

    修改Django REST framework的默认异常处理方法,补充处理数据库异常和Redis异常。会抓取一般的链接数据库的(查询过程中断开的)异常.但无法抓取具体的数据库(查询:不存在,插入:已存在)的异常新建utils/exceptions.py

    from rest_framework.views import exception_handler as drf_exception_handler
    import logging
    from django.db import DatabaseError
    from redis.exceptions import RedisError
    from rest_framework.response import Response
    from rest_framework import status
    # 获取在配置文件中定义的logger,用来记录日志
    logger = logging.getLogger('django')
    def exception_handler(exc, context):
        """
        自定义异常处理
        :param exc: 异常
        :param context: 抛出异常的上下文
        :return: Response响应对象
        """
        # 调用drf框架原生的异常处理方法
        response = drf_exception_handler(exc, context)
        if response is None:
            view = context['view']
            if isinstance(exc, DatabaseError) or isinstance(exc, RedisError):
                # 数据库异常
                logger.error('[%s] %s' % (view, exc))
                response = Response({'message': '服务器内部错误'}, status=status.HTTP_507_INSUFFICIENT_STORAGE)
        return response
    

    配置文件中添加

    REST_FRAMEWORK = {
        # 异常处理
        'EXCEPTION_HANDLER': 'meiduo_mall.utils.exceptions.exception_handler',
    }
    

    用户模型类

    Django提供了认证系统,文档资料可参考此链接https://yiyibooks.cn/xx/Django_1.11.6/topics/auth/index.html

    Django认证系统同时处理认证和授权。简单地讲,认证验证一个用户是否它们声称的那个人,授权决定一个通过了认证的用户被允许做什么。 这里的词语“认证”同时指代这两项任务,即Django的认证系统同时提供了认证机制和权限机制。

    Django的认证系统包含:

    • 用户
    • 权限:二元(是/否)标志指示一个用户是否可以做一个特定的任务。
    • 组:对多个用户运用标签和权限的一种通用的方式。
    • 一个可配置的密码哈希系统
    • 用户登录或内容显示的表单和视图
    • 一个可插拔的后台系统

    Django用户模型类

    Django认证系统中提供了用户模型类User保存用户的数据,默认的User包含以下常见的基本字段:

    • username
      必选。 150个字符以内。 用户名可能包含字母数字,_@+ .-个字符。在Django更改1.10:max_length从30个字符增加到150个字符。
    • first_name
      可选(blank=True)。 少于等于30个字符。
    • last_name
      可选(blank=True)。 少于等于30个字符。
    • email
      可选(blank=True)。 邮箱地址。
    • password
      必选。 密码的哈希及元数据。 (Django 不保存原始密码)。 原始密码可以无限长而且可以包含任意字符。
    • groups
      Group 之间的多对多关系。
    • user_permissions
      Permission 之间的多对多关系。
    • is_staff
      布尔值。 指示用户是否可以访问Admin 站点。
    • is_active
      布尔值。 指示用户的账号是否激活。 我们建议您将此标志设置为False而不是删除帐户;这样,如果您的应用程序对用户有任何外键,则外键不会中断。它不是用来控制用户是否能够登录。 在Django更改1.10:在旧版本中,默认is_active为False不能进行登录。
    • is_superuser
      布尔值。 指定这个用户拥有所有的权限而不需要给他们分配明确的权限。
    • last_login
      用户最后一次登录的时间。
    • date_joined
      账户创建的时间。 当账号创建时,默认设置为当前的date/time。
    常用方法:
    • set_password(raw_password)
      设置用户的密码为给定的原始字符串,并负责密码的。 不会保存User 对象。当Noneraw_password 时,密码将设置为一个不可用的密码。
    • check_password(raw_password)
      如果给定的raw_password是用户的真实密码,则返回True,可以在校验用户密码时使用。
    管理器方法:

    管理器方法即可以通过User.objects. 进行调用的方法。

    • create_user(username, email=None, password=None, **extra_fields)
      创建、保存并返回一个User对象。
    • create_superuser(username, email, password, **extra_fields)
      create_user() 相同,但是设置is_staffis_superuserTrue

    创建自定义的用户模型类

    Django认证系统中提供的用户模型类及方法很方便,我们可以使用这个模型类,但是字段有些无法满足项目需求,如本项目中需要保存用户的手机号,需要给模型类添加额外的字段。
    Django提供了django.contrib.auth.models.AbstractUser用户抽象模型类允许我们继承,扩展字段来使用Django认证系统的用户模型类。

    我们现在在meiduo/meiduo_mall/apps中创建Django应用users,并在配置文件中注册users应用。
    在创建好的应用models.py中定义用户的用户模型类。

    class User(AbstractUser):
        """用户模型类"""
        mobile = models.CharField(max_length=11, unique=True, verbose_name='手机号')
        class Meta:
            db_table = 'tb_users'
            verbose_name = '用户'
            verbose_name_plural = verbose_name
    

    我们自定义的用户模型类还不能直接被Django的认证系统所识别,需要在配置文件中告知Django认证系统使用我们自定义的模型类。
    在配置文件中进行设置

    AUTH_USER_MODEL = 'users.User'
    

    AUTH_USER_MODEL 参数的设置以点.来分隔,表示应用名.模型类名
    注意:Django建议我们对于AUTH_USER_MODEL参数的设置一定要在第一次数据库迁移之前就设置好,否则后续使用可能出现未知错误。

    执行数据库迁移

    python manage.py makemigrations
    python manage.py migrate
    

    跨域CORS

    我们为前端和后端分别设置了两个不同的域名
    现在,前端与后端分处不同的域名,我们需要为后端添加跨域访问的支持。
    我们使用CORS来解决后端对跨域访问的支持。
    使用django-cors-headers扩展
    参考文档https://github.com/ottoyiu/django-cors-headers/

    安装

    pip install django-cors-headers
    

    添加应用

    INSTALLED_APPS = (
        ...
        'corsheaders',
        ...
    )
    

    中间层设置

    MIDDLEWARE = [
        'corsheaders.middleware.CorsMiddleware',
        ...
    ]
    

    添加白名单

    # CORS
    CORS_ORIGIN_WHITELIST = (
        '127.0.0.1:8080',
        'localhost:8080',
     ........
    )
    CORS_ALLOW_CREDENTIALS = True  # 允许携带cookie
    
    • 凡是出现在白名单中的域名,都可以访问后端接口
    • CORS_ALLOW_CREDENTIALS 指明在跨域访问中,后端是否支持对cookie的操作。

    Celery使用

    项目目录下创建celery_tasks用于保存celery异步任务。
    在celery_tasks目录下创建config.py文件,用于保存celery的配置信息
    broker_url = "redis://127.0.0.1/14"
    在celery_tasks目录下创建main.py文件,用于作为celery的启动文件
    from celery import Celery

    为celery使用django配置文件进行设置

    import os
    if not os.getenv('DJANGO_SETTINGS_MODULE'):
    os.environ['DJANGO_SETTINGS_MODULE'] = 'meiduo_mall.settings.dev'

    创建celery应用

    app = Celery('异步任务名')

    导入celery配置

    app.config_from_object('celery_tasks.config')

    自动注册celery任务

    app.autodiscover_tasks(['celery_tasks.sms'])
    在celery_tasks目录下创建sms目录,用于放置发送短信的异步任务相关代码。
    将提供的发送短信的云通讯SDK放到celery_tasks/sms/目录下。
    在celery_tasks/sms/目录下创建tasks.py文件,用于保存发送短信的异步任务
    import logging
    from celery_tasks.main import app
    from .yuntongxun.sms import CCP
    logger = logging.getLogger("django")

    验证码短信模板

    SMS_CODE_TEMP_ID = 1
    @app.task(name='send_sms_code')
    def send_sms_code(mobile, code, expires):
    """
    发送短信验证码
    :param mobile: 手机号
    :param code: 验证码
    :param expires: 有效期
    :return: None
    """
    try:
    ccp = CCP()
    result = ccp.send_template_sms(mobile, [code, expires], SMS_CODE_TEMP_ID)
    except Exception as e:
    logger.error("发送验证码短信[异常][ mobile: %s, message: %s ]" % (mobile, e))
    else:
    if result == 0:
    logger.info("发送验证码短信[正常][ mobile: %s ]" % mobile)
    else:
    logger.warning("发送验证码短信[失败][ mobile: %s ]" % mobile)
    在需要发送短信的视图中,使用celery异步任务发送短信
    from celery_tasks.sms import tasks as sms_tasks
    class SMSCodeView(GenericAPIView):
    # 发送短信验证码,使用delay将任务加入到异步队列
    sms_tasks.send_sms_code.delay(mobile, sms_code, sms_code_expires)

    JWT

    JWT的构成
    第一部分我们称它为头部(header),第二部分我们称其为载荷(payload, 类似于飞机上承载的物品),第三部分是签证(signature).

    注意:secret是保存在服务器端的,jwt的签发生成也是在服务器端的,secret就是用来进行jwt的签发和jwt的验证,所以,它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。

    Django REST framework JWT

    我们在验证完用户的身份后(检验用户名和密码),需要向用户签发JWT,在需要用到用户身份信息的时候,还需核验用户的JWT。
    关于签发和核验JWT,我们可以使用Django REST framework JWT扩展来完成。
    文档网站http://getblimp.github.io/django-rest-framework-jwt/

    安装配置

    安装

    pip install djangorestframework-jwt
    

    配置

    REST_FRAMEWORK = {
        'DEFAULT_AUTHENTICATION_CLASSES': (        'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
            'rest_framework.authentication.SessionAuthentication',
            'rest_framework.authentication.BasicAuthentication',
        ),
    }
    JWT_AUTH = {
        'JWT_EXPIRATION_DELTA': datetime.timedelta(days=1),
    }
    
    • JWT_EXPIRATION_DELTA 指明token的有效期

    使用

    Django REST framework JWT 扩展的说明文档中提供了手动签发JWT的方法

    from rest_framework_jwt.settings import api_settings
    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
    我们可以将JWT保存在cookie中,也可以保存在浏览器的本地存储里,我们保存在浏览器本地存储中
    浏览器的本地存储提供了sessionStorage 和 localStorage 两种:
    sessionStorage 浏览器关闭即失效
    localStorage 长期有效

    1. 后端实现
      Django REST framework JWT提供了登录签发JWT的视图,可以直接使用
      from rest_framework_jwt.views import obtain_jwt_token
      urlpatterns = [
      url(r'^authorizations/$', obtain_jwt_token),
      ]
      但是默认的返回值仅有token,我们还需在返回值中增加username和user_id。
      通过修改该视图的返回值可以完成我们的需求。
      在users/utils.py 中,创建
      def jwt_response_payload_handler(token, user=None, request=None):
      """
      自定义jwt认证成功返回数据
      """
      return {
      'token': token,
      'user_id': user.id,
      'username': user.username
      }
      修改配置文件

    JWT

    JWT_AUTH = {
    'JWT_EXPIRATION_DELTA': datetime.timedelta(days=1),
    'JWT_RESPONSE_PAYLOAD_HANDLER': 'users.utils.jwt_response_payload_handler',
    }

    1. 增加支持用户名与手机号均可作为登录账号
      JWT扩展的登录视图,在收到用户名与密码时,也是调用Django的认证系统中提供的authenticate()来检查用户名与密码是否正确。
      我们可以通过修改Django认证系统的认证后端(主要是authenticate方法)来支持登录账号既可以是用户名也可以是手机号。修改Django认证系统的认证后端需要继承django.contrib.auth.backends.ModelBackend,并重写authenticate方法。
      authenticate(self, request, username=None, password=None, **kwargs)方法的参数说明:
      request 本次认证的请求对象
      username 本次认证提供的用户账号
      password 本次认证提供的密码
      我们想要让用户既可以以用户名登录,也可以以手机号登录,那么对于authenticate方法而言,username参数即表示用户名或者手机号。

    重写authenticate方法的思路:
    根据username参数查找用户User对象,username参数可能是用户名,也可能是手机号
    若查找到User对象,调用User对象的check_password方法检查密码是否正确
    在users/utils.py中编写:
    def get_user_by_account(account):
    """
    根据帐号获取user对象
    :param account: 账号,可以是用户名,也可以是手机号
    :return: User对象 或者 None
    """
    try:
    if re.match('^1[3-9]\d{9}$', account):
    # 帐号为手机号
    user = User.objects.get(mobile=account)
    else:
    # 帐号为用户名
    user = User.objects.get(username=account)
    except User.DoesNotExist:
    return None
    else:
    return user

    class UsernameMobileAuthBackend(ModelBackend):
    """
    自定义用户名或手机号认证
    """
    def authenticate(self, request, username=None, password=None, **kwargs):
    user = get_user_by_account(username)
    if user is not None and user.check_password(password):
    return user

    在配置文件中告知Django使用我们自定义的认证后端
    AUTHENTICATION_BACKENDS = [
    'users.utils.UsernameMobileAuthBackend',
    ]

    使用itsdangerous生成凭据access_token

    itsdangerous模块的参考资料连接http://itsdangerous.readthedocs.io/en/latest/

    安装
    pip install itsdangerous
    
    TimedJSONWebSignatureSerializer的使用

    使用TimedJSONWebSignatureSerializer可以生成带有有效期的token

    from itsdangerous import TimedJSONWebSignatureSerializer as Serializer
    from django.conf import settings
    # serializer = Serializer(秘钥, 有效期秒)
    serializer = Serializer(settings.SECRET_KEY, 300)
    # serializer.dumps(数据), 返回bytes类型
    token = serializer.dumps({'mobile': '18512345678'})
    token = token.decode()
    
    # 检验token
    # 验证失败,会抛出itsdangerous.BadData异常
    serializer = Serializer(settings.SECRET_KEY, 300)
    try:
        data = serializer.loads(token)
    except BadData:
        return None
    

    相关文章

      网友评论

          本文标题:项目笔记/配置/jwt/isdangerous

          本文链接:https://www.haomeiwen.com/subject/einptqtx.html