Multiplayer blog development project - back end

Keywords: Python Django JSON Session Database

Basic analysis

1 Analysis

For the blog system used by many people, the BS architecture is adopted here.
Blog system needs user management and blog management

User management: user registration, adding, deleting, modifying and querying users

Blog management: add, delete, change and check blog

The database is required. This time, Mysql5.7.17 is used. innodb storage engine is used. The front end uses react framework, and the back end uses django framework.

It needs to support multi-user login. Each user can manage his own blog (add, delete, modify and query). Management is not public, but blog can be viewed publicly without login.

2 environmental preparation

1 mysql 5.6.42

Create a library and specify character sets and related users and permissions

create database if not exists  blog CHARACTER set  utf8mb4 COLLATE utf8mb4_general_ci;

grant all on  blog.* to  blog@localhost identified  by 'blog@123Admin';

flush privileges;

Because the backend and database of this project are on the same device, you can use localhost to access them. If it is not a device, you need to change the localhost in the second article to% 'and

grant all on  blog.* to  blog@'%' identified  by 'blog';

See below

2 django backend project preparation

This section is no longer cumbersome for basic application creation. Please refer to the previous section for details.

The directory after project creation is as follows

settings.py

"""
Django settings for blog project.

Generated by 'django-admin startproject' using Django 2.0.

For more information on this file, see
https://docs.djangoproject.com/en/2.0/topics/settings/

For the full list of settings and their values, see
https://docs.djangoproject.com/en/2.0/ref/settings/
"""

import os

# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/2.0/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = '-5n#!qq=8=49k@iikd@c46r%=iq=nu97-5#f@4d4&^x+0=s^9f'

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True

ALLOWED_HOSTS = ['*']

# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'user',
    'post',
]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
#    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

ROOT_URLCONF = 'blog.urls'

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

WSGI_APPLICATION = 'blog.wsgi.application'

# Database
# https://docs.djangoproject.com/en/2.0/ref/settings/#databases

# DATABASES = {
#     'default': {
#         'ENGINE': 'django.db.backends.sqlite3',
#         'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
#     }
# }

DATABASES = {
    'default':{
        'ENGINE' :'django.db.backends.mysql',
        'NAME':'blog',
        'USER':'blog',
        'PASSWORD':'blog@123Admin',
        'HOST':'localhost',
        'PORT':'3306',
    }
}

# Password validation
# https://docs.djangoproject.com/en/2.0/ref/settings/#auth-password-validators

AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]

# Internationalization
# https://docs.djangoproject.com/en/2.0/topics/i18n/

LANGUAGE_CODE = 'zh-Hans'

TIME_ZONE = 'Asia/Shanghai'

USE_I18N = True

USE_L10N = True

USE_TZ = False

# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/2.0/howto/static-files/

STATIC_URL = '/static/'

Start to see

2. Implementation of registration interface

1 user function design and Implementation

Provide user registration and establishment
Provide user login processing
Provide routing configuration

Implementation of user registration interface

Accept the registration information submitted by the user through the POST method, and submit the data in JSON format.

Check whether the email already exists in the database. If it exists, an error status code will be returned, such as 4xx. If it does not exist, the data submitted by the user will be stored in the table.

The whole process adopts AJAX asynchronous process. Users hand over JSON data, and the server gets the data and returns it to JSON for processing.

URL:/user/reg
METHOD: POST

2 request and analysis

The time of the front end can only be completed through CSS,JS and HTML, but the implementation of the back end can be completed in multiple languages.

Request flow

Browser ------ nginx / LVS (handling static and dynamic separation and reverse proxy requests) --- python solves dynamic problems (java,php, etc.) -- react handles project static page problems.

The communication between two python projects can be realized by exposing the URL through simple HTTP protocol.

Nginx processing static pages is the only way for nginx backend agents.

The user requests to go to the front-end and back-end of nginx. One is the nginx static page and the other is python. Through static nginx to access the API for processing, it can use internal IP address plus port number for access, without using external access processing;

django accesses DB backward, sorts the data and returns it to nginx static state, forms relevant JS through react framework, renders in DOM tree through AJAX callback, and displays it.

3 basic URL routing configuration

1. Set url mapping in blog/urls to configure multi-level routing

from django.contrib import admin
from django.conf.urls import  url,include # The include module is introduced here for communication processing with the lower module.

urlpatterns = [
    url(r'admin/', admin.site.urls),
    url(r'^user/',include('user.urls'))  # Here, the user.urls represents the URLs file reference under the user application.
]

The parameter of the include function writes to the application. Routing module. The function will dynamically import the module of the specified package, read the urlpatterns from the module, and return triples.

If the second parameter of the url function is not a callable object, if it is a primitive or a list, the matching part will be found out from the path, and the remaining part will be matched with the urlpatterns of the routing module in the application.

Create urls.py file in user application

as follows

#!/usr/bin/poython3.6
#conding:utf-8
from  django.conf.urls import  url
from  django.http import  HttpResponse,HttpRequest,JsonResponse

def reg(request:HttpRequest):  #This temporary configuration is used to test whether it can display normally
    return HttpResponse(b'user.reg')

urlpatterns = [
    url(r'reg$',reg)  # Here reg represents the reg function. It can be functions, objects, and classes.
]

test
Here is form data submission

Data submitted in JSON mode is as follows

The logs are as follows

[20/Oct/2019 10:40:22] "POST /user/reg HTTP/1.1" 200 8
[20/Oct/2019 10:42:01] "POST /user/reg HTTP/1.1" 200 8

4 database user table class creation

1 create class

Create the following code in user/models.py, where the mailbox must be unique

from django.db import models

class User(models.Model):
    class Meta:
        db_table='user'
    id=models.AutoField(primary_key=True)
    name=models.CharField(max_length=48,null=False)
    email=models.CharField(max_length=64,unique=True,null=False)
    password=models.CharField(max_length=128,null=False)
    createdate=models.DateTimeField(auto_now=True)  # Update time only when created

    def __repr__(self):
        return  '<user  name:{} id:{}>'.format(self.name,self.id)
    __str__=__repr__

2 Migration

python  manage.py  makemigrations

3 perform migration to generate tables of database

python  manage.py  migrate

give the result as follows

5 view function configuration

1 Write view function reg in user/views.py

#!/usr/bin/poython3.6
#conding:utf-8
from  django.conf.urls import  url  
from   user.views  import  reg  #Here the functions in views are exported here by importing

urlpatterns = [
    url(r'reg$',reg)  # Here reg represents the reg function. It can be functions, objects, and classes.
]

user/views.py

from  django.http import  HttpResponse,HttpRequest,JsonResponse

def reg(request:HttpRequest):  #This temporary configuration is used to test whether it can display normally
    print ('request','------------------')
    print (type(request))
    print (request.POST)
    print (request.GET)
    print(request.body)
    return HttpResponse(b'user.reg')

The JSON request results are as follows

Quit the server with CONTROL-C.
request ------------------
<class 'django.core.handlers.wsgi.WSGIRequest'>
<QueryDict: {}>
<QueryDict: {}>
b'{\n\t"name":"mysql"\n}'
[20/Oct/2019 10:47:02] "POST /user/reg HTTP/1.1" 200 8

A binary json data is returned here

The result of submitting from data is as follows. The request.body must be removed in this way.

request ------------------
<class 'django.core.handlers.wsgi.WSGIRequest'>
<QueryDict: {'hello': ['word']}>
<QueryDict: {}>
[20/Oct/2019 10:52:12] "POST /user/reg HTTP/1.1" 200 8

2 JSON data processing

Because the above return is binary data, you need to use JSON to process it.

Modify the code as follows

from  django.http import  HttpResponse,HttpRequest,JsonResponse
import   json

def reg(request:HttpRequest):  #This temporary configuration is used to test whether it can display normally
    print (json.loads(request.body.decode()))  # This must be JSON submission mode 
    return HttpResponse(b'user.reg')

The request is as follows

The request results are as follows

{'name': 'mysql'}
[20/Oct/2019 10:55:19] "POST /user/reg HTTP/1.1" 200 8

3. Handling conversion process exception

from  django.http import  HttpResponse,HttpRequest,JsonResponse
import   json

def reg(request:HttpRequest):  #This temporary configuration is used to test whether it can display normally
    try:
        payloads=json.loads(request.body.decode())  # This must be JSON submission mode
        print(payloads)
        return HttpResponse('user.reg')
    except  Exception  as   e:
        print (e)
        return  HttpResponse() #Create an instance with nothing in it

give the result as follows

{'name': 'mysql'}
[20/Oct/2019 11:04:58] "POST /user/reg HTTP/1.1" 200 8

4 simplejson processing data

simplejson is more convenient and powerful than standard library

pip install  simplejson

The data submitted by the browser end is placed in the body of the request object and needs to be parsed by simplejson. The parsing method is the same as json, but simplejson is more convenient.

from  django.http import  HttpResponse,HttpRequest,JsonResponse
import   simplejson

def reg(request:HttpRequest):  #This temporary configuration is used to test whether it can display normally
    try:
        payloads=simplejson.loads(request.body)  # This must be JSON submission mode
        print(payloads['name'])  # Get the data
        return HttpResponse('user.reg')
    except  Exception  as   e:
        print (e)
        return  HttpResponse() #Create an instance with nothing in it

The request is as follows

The response data is as follows

mysql
[20/Oct/2019 11:20:52] "POST /user/reg HTTP/1.1" 200 8

5 project registration user configuration

1 create project registration directory log

mkdir /var/log/blog/

2 the analysis is as follows

Mailbox detection

Mailbox detection needs to query the user table and use the filter method of user class.

email=email, with field name in front and variable name in back. After query, the result will be returned. If there is a result in the query, it means that the email already exists and returns 400 to the front end.

User storage information

Create User class instance, attribute store data, and finally call save method. Django is committed to submit transaction data when save(), delete(), and if it throws any exception, it needs to catch its exception.

exception handling

If there is an exception in obtaining the information submitted by the input box, an exception will be returned.
Query mailbox exists, return exception
save method saves data. If there is an exception, it will be thrown out to catch the exception.
Note that the exception class of django inherits from the HttpEResponse class, so it cannot raise, only return.
The front end judges whether it is successful through the status verification code

3 write the relevant code as follows

from  django.http import  HttpResponse,HttpRequest,JsonResponse,HttpResponseBadRequest
import   simplejson
import logging
from  .models import  User
FORMAT="%(asctime)s  %(threadName)s %(thread)d %(message)s"
logging.basicConfig(format=FORMAT,level=logging.INFO,filename='/var/log/blog/reg.log')

def reg(request:HttpRequest):  #This temporary configuration is used to test whether it can display normally
    print (request.POST)
    print (request.body)
    payloads=simplejson.loads(request.body)
    try:
        email=payloads['email']
        query=User.objects.filter(email=email)  # Here is to verify whether the mailbox exists. If it exists, return directly
        if query:
            return  HttpResponseBadRequest('email:{} exits'.format(email))  # An instance is returned here. After return, the following will not be executed
        name=payloads['name']
        password=payloads['password']
        logging.info('Registered users{}'.format(name)) # Basic information of registered user is written here
        # Instantiate write to database
        user=User()  # Instanced object
        user.email=email
        user.password=password
        user.name=name
        try:
            user.save()  # commit submit data
            return  JsonResponse({'userid':user.id})  # If the submission is normal. Then return to this situation
        except:
            raise # If abnormal, return directly

    except  Exception  as   e:
        logging.infe(e)
        return  HttpResponse() #Create an instance with nothing in it

The request data is as follows

The returned data and database data in the log are as follows

The result of the second request is as follows

6. Perfect registration interface

1 certification

HTTP is a stateless protocol. In order to solve this problem, cookie and session technology are produced.

Traditional session cookie mechanism

When the browser initiates the first request to the server and the server finds that the browser does not provide a session id, it is considered as the first request. A new session id will be returned to the browser side. As long as the browser is not closed, the session id will be sent to the server side again with each request. The server checks and finds the session id. if it is found, it is regarded as the same session. If it is not found, it is regarded as a new request.

Session is session level. You can create a lot of data in this session, link or disconnect session cleanup, including session id.

This session mechanism also needs to have an expiration mechanism. If no request is made within a period of time, and the user is considered disconnected, the session will be cleared, and the browser will also clear the corresponding cookie information.

In this case, a large amount of session information is saved on the server side, which consumes the memory field of the server. If the server is deployed in multiple servers, the problem of session sharing needs to be considered, such as using redis and memchached solutions.

2 no session solution JWT

1 Overview

Since the server needs an ID to represent the identity, the inapplicable session can also create an ID to return to the client. However, it is necessary to ensure that the client cannot be tampered with.

The server generates an identity and signs it with some algorithm

The server receives the identity from the client and needs to check the signature

The disadvantage of this scheme is that encryption and decryption need to consume CPU computer resources, and browser can not actively check the expired data to clear it. This technology becomes JWT (JSON web token)

2 JWT

JWT(json web token) is a way to install and transfer information in json mode

PYJWT is python's implementation of JW.

File

https://pyjwt.readthedocs.io/en/latest/

package

https://pypi.org/project/PyJWT/

install

pip  install  pyjwt

On the left is the encrypted thing, which cannot be recognized. It uses base64 encoding, the equal sign is removed, and it is divided into three parts, disconnected by point number.

First part HEADER: what type is it and what encryption algorithm is it

Part II PAYLOAD: data part

The third part, VERIFY SIGNATURE: the signature is obtained by encryption. The signature is irreversible, which also contains a password. In Pycharm, there is such a password, as follows

3 test JWT

#!/usr/bin/poython3.6
#conding:utf-8
import  jwt
import  datetime
import base64
key='test'
payload={'name':'demo','email':'188@123.com','password':'demo','ts':int(datetime.datetime.now().timestamp())}
pwd=jwt.encode(payload,key,'HS256')

HEADER,PAYLOAD,VERIFY=pwd.split(b'.')

def fix(src):
    rem=len(src)%4  # Remainder
    return  src+b'='*rem   # Fill with equal sign

print (base64.urlsafe_b64decode(fix(HEADER)))

print (base64.urlsafe_b64decode(fix(PAYLOAD)))

print (base64.urlsafe_b64decode(fix(VERIFY)))

give the result as follows

4 encode source code in JWT

    def encode(self, payload, key, algorithm='HS256', headers=None,
               json_encoder=None):
        # Check that we get a mapping
        if not isinstance(payload, Mapping):
            raise TypeError('Expecting a mapping object, as JWT only supports '
                            'JSON objects as payloads.')

        # Payload
        for time_claim in ['exp', 'iat', 'nbf']:
            # Convert datetime to a intDate value in known time-format claims
            if isinstance(payload.get(time_claim), datetime):
                payload[time_claim] = timegm(payload[time_claim].utctimetuple())

        json_payload = json.dumps(
            payload,
            separators=(',', ':'),
            cls=json_encoder
        ).encode('utf-8')

        return super(PyJWT, self).encode(
            json_payload, key, algorithm, headers, json_encoder
        )

The payload will be serialized with json.dumps and encoded with utf8.

Related methods in the parent class

    def encode(self, payload, key, algorithm='HS256', headers=None,
               json_encoder=None):
        segments = []

        if algorithm is None:
            algorithm = 'none'

        if algorithm not in self._valid_algs:
            pass

        # Header
        header = {'typ': self.header_typ, 'alg': algorithm}

        if headers:
            header.update(headers)

        json_header = json.dumps(
            header,
            separators=(',', ':'),
            cls=json_encoder
        ).encode('utf-8')

        segments.append(base64url_encode(json_header))
        segments.append(base64url_encode(payload))

        # Segments
        signing_input = b'.'.join(segments)
        try:
            alg_obj = self._algorithms[algorithm]
            key = alg_obj.prepare_key(key)
            signature = alg_obj.sign(signing_input, key)

        except KeyError:
            raise NotImplementedError('Algorithm not supported')

        segments.append(base64url_encode(signature))

        return b'.'.join(segments)

Supported algorithms

def get_default_algorithms():
    """
    Returns the algorithms that are implemented by the library.
    """
    default_algorithms = {
        'none': NoneAlgorithm(),
        'HS256': HMACAlgorithm(HMACAlgorithm.SHA256),
        'HS384': HMACAlgorithm(HMACAlgorithm.SHA384),
        'HS512': HMACAlgorithm(HMACAlgorithm.SHA512)
    }

    if has_crypto:
        default_algorithms.update({
            'RS256': RSAAlgorithm(RSAAlgorithm.SHA256),
            'RS384': RSAAlgorithm(RSAAlgorithm.SHA384),
            'RS512': RSAAlgorithm(RSAAlgorithm.SHA512),
            'ES256': ECAlgorithm(ECAlgorithm.SHA256),
            'ES384': ECAlgorithm(ECAlgorithm.SHA384),
            'ES512': ECAlgorithm(ECAlgorithm.SHA512),
            'PS256': RSAPSSAlgorithm(RSAPSSAlgorithm.SHA256),
            'PS384': RSAPSSAlgorithm(RSAPSSAlgorithm.SHA384),
            'PS512': RSAPSSAlgorithm(RSAPSSAlgorithm.SHA512)
        })

    return default_algorithms

Headers are also cast to binary form.

Among them, the header and payload are added to the segments list, wrapped by binary b'.'.join, and then the signature obtained after processing them together with the key through alg obj. Sign (signing input, key) method is added to the previous segments and returned by b'.'.join(segments)

5. Regenerate the signature according to the corresponding JWT algorithm

#!/usr/bin/poython3.6
#conding:utf-8
import  jwt
import  datetime

from  jwt.algorithms import  get_default_algorithms
import base64
key='test'
payload={'name':'demo','email':'188@123.com','password':'demo','ts':int(datetime.datetime.now().timestamp())}
pwd=jwt.encode(payload,key,'HS256')
header,payload,sig=pwd.split(b'.')

al_obj=get_default_algorithms()['HS256']  # Get the corresponding algorithm, because the above is a function
newkey=al_obj.prepare_key(key)  # Get the encrypted key
print(newkey)

# Get algorithm information and corresponding payload information
sig_input,_,_=pwd.rpartition(b'.')  # Obtain the corresponding algorithm information and payload information.

#The overall output here is as follows
#(b'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiZGVtbyIsInRzIjoxNTcxNTYwNjI2LCJwYXNzd29yZCI6ImRlbW8iLCJlbWFpbCI6IjE4OEAxMjMuY29tIn0', b'.', b'XtY5v8wB0YCsX6ZDwKAMzaPwpbYbPPhTt-vgx4StB74')

print(sig_input)

crypat=al_obj.sign(sig_input,newkey)  # Get a new signature

print (base64.urlsafe_b64encode(crypat))  # Use base64 to do the following
print (sig) # Original encrypted sig signature content 

give the result as follows

Signature acquisition process
1. Get the relevant instances of the corresponding algorithm through get default algorithms
2. Generate a new key, newkey, and binary processing through the prepare_key(key) of the instance.
3. A new signature can be generated by processing the new key and the corresponding algorithm with sign.

Therefore, the token s generated by JWT are divided into three parts

1 header, data type, encryption algorithm
2 payload, which is responsible for data transmission, is usually put into python objects, and will be serialized by JSON.
3 signature, the signature part, is the result that the encryption algorithm uses key to calculate a good one, and then bse64 encodes it to get the signature.

All data is transmitted in plaintext, but base64 is used. If it is sensitive information, please do not use jwt.
The purpose of data signature is not to hide the data, but to ensure that the data will not be tampered with. If the data is tampered with, it will be sent back to the server, and the server uses its own key to calculate even again, and then compared with the signature, it will not be signed.

3 password problem

Log in by email + password

Only mailbox is required, but how to store the password

In the early days, passwords were stored by the name

Later, MD5 storage was used, but it is not safe at present.

MD5 is irreversible and asymmetric
But MD5 can be found back. The time of exhaustion is not very long. MD5 is very fast.
Add the same prefix and suffix, then give two passwords if necessary. You can also infer that all passwords are processed.

Add salt, and store the hash(password+salt) results into the database. Even if you get it to handle the password reverse query, it's useless. But if you add salt in a fixed way, it's still easy to find out the rules, or leak it from the source code, add salt randomly, and the salt changes every time, which increases the difficulty of solution.

Brute force cracking, no password can be guaranteed not to be brute force cracked, such as exhaustive, so to use slow hash algorithm, such as bcrypt, will make every calculation very slow, all at the second level, which will lead to too long exhaustive time. In password cracking, CPU cannot be replaced, and distributed password cracking cannot be realized.

4 bcrypt

1 installation

pip install  bcrypt

2 test code

#!/usr/bin/poython3.6
#conding:utf-8
import  bcrypt
import  datetime

password=b'123456'

# Different salt returns different results
print (1, bcrypt.gensalt())
print (2,bcrypt.gensalt())

# If the same salt is obtained, the calculation result is the same.
salt=bcrypt.gensalt()

print ('same  salt')
x=bcrypt.hashpw(password,salt)
print (3,x)
x=bcrypt.hashpw(password,salt)
print (4,x)

# Different salt results are different
print('----------  different salt -----------')
x=bcrypt.hashpw(password,bcrypt.gensalt())
print (5,x)

x=bcrypt.hashpw(password,bcrypt.gensalt())
print (6,x)

# check
print(7,bcrypt.checkpw(password,x),len(x)) # Check results are returned here
print(8,bcrypt.checkpw(password+b' ',x),len(x))  # If a space is added here, the verification fails

# Computation time
start=datetime.datetime.now()
y=bcrypt.hashpw(password,bcrypt.gensalt())
delta=(datetime.datetime.now()-start).total_seconds()
print (9,delta)

# Check duration

start=datetime.datetime.now()
z=bcrypt.checkpw(password,x)
delta=(datetime.datetime.now()-start).total_seconds()
print (10,delta,z)

give the result as follows

It can be seen from the time-consuming that bcrypt encryption and verification are very time-consuming, so if it is used exhaustively, it will be very time-consuming, and it is necessary to break one password, because the salt is not the same, it has to exhaust the other one.

salt 
b'$2b$12$F18k/9ChWWu8BUYjC2iIMO'

Encrypted result b''b$F18k/9ChWWu8BUYjC2iIMOj0Ny0GdwC.X/.2bFAAy25GgRzcpmqsy'

 Where $is the separator 

 $2b $encryption algorithm

 12 for 2 ^ 12 key expansion groups 

 This is salt b'F18k/9ChWWu8BUYjC2iIMO ', 22 characters, Base64 encoding 

 The ciphertext here is b'F18k/9ChWWu8BUYjC2iIMOj0Ny0GdwC.X/.2bFAAy25GgRzcpmqs',31 characters, Base64 

5. Perfect registration interface

from  django.http import  HttpResponse,HttpRequest,JsonResponse,HttpResponseBadRequest
import   simplejson
import logging
from  .models import  User
import  jwt
import bcrypt
from  blog.settings import SECRET_KEY  # Get the password in django
import  datetime
FORMAT="%(asctime)s  %(threadName)s %(thread)d %(message)s"
logging.basicConfig(format=FORMAT,level=logging.INFO,filename='/var/log/blog/reg.log')

def  get_token(user_id): # The token here is the name composed of userid and time, and the encryption is realized by the default key of django.
    return   (jwt.encode({'user_id':user_id,  # Here is the token obtained. Tell the user
                        'timestamp':int(datetime.datetime.now().timestamp()),  # Add time stamp
                        },SECRET_KEY,'HS256')).decode()

def reg(request:HttpRequest):  #This temporary configuration is used to test whether it can display normally
    print (request.POST)
    print (request.body)
    payloads=simplejson.loads(request.body)
    try:
        email=payloads['email']
        query=User.objects.filter(email=email)  # Here is to verify whether the mailbox exists. If it exists, return directly
        if query:
            return  HttpResponseBadRequest('email:{} exits'.format(email))  # An instance is returned here. After return, the following will not be executed
        name=payloads['name']
        password=payloads['password']
        logging.info('Registered users{}'.format(name)) # Basic information of registered user is written here
        # Instantiate write to database
        user=User()  # Instanced object
        user.email=email
        user.password=bcrypt.hashpw(password.encode(),bcrypt.gensalt()).decode()  # The password is in string format by default, while bcrypt needs to be processed by default.
        #Then return
        user.name=name
        try:
            user.save()  # commit submit data
            return  JsonResponse({'token':get_token(user.id)})  # If the submission is normal. Then return to this situation
        except:
            raise # If abnormal, return directly

    except  Exception  as   e:
        logging.info(e)
        return  HttpResponse() #Create an instance with nothing in it

The results are as follows

Three login interface implementation

1 user function design and Implementation

1 functional design

Provide user registration processing
Provide user login processing
Provide user routing configuration

2 user login interface design

Accept the login information submitted by the user through POST, and submit the data in JSON format.

{
    "email":"122@123",
 "password":"demo"
    }

Find a record matching email from the user table, and verify whether the password is correct.

Verify that the user login is legal and the welcome interface is displayed.
Error code returned after verification failure, such as 4xx

The whole process adopts AJAX asynchronous process. The user submits JSON data, the server obtains the data and processes it, and returns the JSON object.

API address

URL : /user/login

METHOD: POST

2 routing configuration

1 add secondary route

The configuration in user/urls.py is as follows

#!/usr/bin/poython3.6
#conding:utf-8
from  django.conf.urls import  url
from   user.views  import  reg,login

urlpatterns = [
    url(r'reg$',reg),  # Here reg represents the reg function. It can be functions, objects, and classes.
    url(r'login$',login)
]

3 basic login code

def login(request:HttpRequest):
    payload=simplejson.loads(request.body)
    try:
        email=payload['email']
        query=User.objects.filter(email=email).get()
        print(query.id)
        if not query:
            return   HttpResponseBadRequest(b'email  not  exist')
        if bcrypt.checkpw(payload['password'].encode(),query.password.encode()):  #Judge password validity
            # Verifying and passing
            token=get_token(query.id)
            print('token',token)
            res=JsonResponse({
                'user':{
                    'user_id':query.id,
                    'name':query.name,
                    'email':query.email,
                },'token':token
            })
            return   res
        else:
            return  HttpResponseBadRequest(b'password  is not correct')
    except  Exception as e:
        logging.info(e)
        return   HttpResponseBadRequest(b'The request parameter is not valid')

give the result as follows

4 deal with the problem of whether to log in or not

How to get the token information submitted by the browser?
1 use Authorization in header
Add token information through this header
Send data through header. All methods can be post and get.

2 custom header
JWT to send token

We choose the second way to authenticate

Basically all businesses need to authenticate user information

Compare the timestamps here. If the timestamps expire, the system will throw 401 authentication directly. After the client receives the timestamps, it will jump to the login page directly.

If the user id is not submitted, log in again directly. If the user finds it, fill in the user.

Request - > timestamp comparison - > User ID comparison, backward execution

5 authentication mode of Django

There are many authentication methods in django.contrib.auth, which are mainly introduced here.

1 authenticate(**credentials)
Provide user authentication and verify whether the user name and password are correct

user=authentical(username='1234',password='1234')

2 login(HttpRequest,user,backend=None)
This function takes an HttpRequest object and a validated User object
This function uses django's session framework to attach session id and other information to an authenticated user.

3 logout(request)
Logout user
This function accepts an HttpRequest object with no return value
When this function is called, the session information of the current request will be cleared.
Even if the user is not logged in, there will be no error when using this function

A decorator is also provided to determine whether to log in to django.contrib.auth.decorators.login_required.

In this project, no session mechanism is implemented, and the user information's own table is used for relevant management. Therefore, authentication is implemented in its own way.

6 middle key technology

1 Overview

Officially defined, in django's request and response processing, the hook provided by the framework
Intermediate key technology changed after 1.10

Official references

https://docs.djangoproject.com/en/2.2/topics/http/middleware/

It is equivalent to a global interceptor, which can intercept incoming and outgoing data.

2 decorator

Enhance the function on the view function to be authenticated, write a decorator, and whoever needs authentication, apply the decorator on the view function.

from  django.http import  HttpResponse,HttpRequest,JsonResponse,HttpResponseBadRequest
import   simplejson
import logging
from  .models import  User
import  jwt
import bcrypt
from  blog.settings import SECRET_KEY  # Get the password in django
import  datetime
FORMAT="%(asctime)s  %(threadName)s %(thread)d %(message)s"
logging.basicConfig(format=FORMAT,level=logging.INFO,filename='/var/log/blog/reg.log')

def  get_token(user_id): # The token here is the name composed of userid and time, and the encryption is realized by the default key of django.
    return   (jwt.encode({'user_id':user_id,  # Here is the token obtained. Tell the user
                        'timestamp':int(datetime.datetime.now().timestamp()),  # Add time stamp
                        },SECRET_KEY,'HS256')).decode()

AUTH_EXPIRE=8*60*60  #This is to define the timeout
def authenticate(view):
    def __wapper(request:HttpRequest):
        print (request.META)
        payload=request.META.get('HTTP_JWT')  # HTTP prefix will be added here, and capitalization will be done automatically.
        print('request',request.body)
        if not payload:  # If it is None here, it means that it has not been obtained, and the authentication fails.
            return  HttpResponseBadRequest(b'authenticate  failed')
        try:
            payload=jwt.decode(payload,SECRET_KEY,algorithms=['HS256'])
            print('Return data',payload)
        except:
            return HttpResponse(status=401)
        current=datetime.datetime.now().timestamp()
        print(current,payload.get('timestamp',0))
        if (current-payload.get('timestamp',0))  > AUTH_EXPIRE:
            return  HttpResponse(status=401)
        try:
            user_id=payload.get('user_id',-1)  # Get user ID
            user=User.objects.filter(pk=user_id).get()
            print ('user',user_id)
        except  Exception as e:
            print(e)
            return  HttpResponse(status=401)
        ret=view(request)
        return  ret
    return  __wapper

def reg(request:HttpRequest):  #This temporary configuration is used to test whether it can display normally
    print (request.POST)
    print (request.body)
    payloads = simplejson.loads(request.body)

    try:
        email=payloads['email']
        query=User.objects.filter(email=email)  # Here is to verify whether the mailbox exists. If it exists, return directly
        if query:
            return  HttpResponseBadRequest('email:{} exits'.format(email))  # An instance is returned here. After return, the following will not be executed
        name=payloads['name']
        password=payloads['password']
        logging.info('Registered users{}'.format(name)) # Basic information of registered user is written here
        # Instantiate write to database
        user=User()  # Instanced object
        user.email=email
        user.password=bcrypt.hashpw(password.encode(),bcrypt.gensalt()).decode()  # The password is in string format by default, while bcrypt needs to be processed by default.
        #Then return
        user.name=name
        try:
            user.save()  # commit submit data
            return  JsonResponse({'token':get_token(user.id)})  # If the submission is normal. Then return to this situation
        except:
            raise # If abnormal, return directly

    except  Exception  as   e:
        logging.info(e)
        return  HttpResponseBadRequest(b'email  not exits') #Create an instance with nothing in it

@authenticate
def login(request:HttpRequest):
    payload=simplejson.loads(request.body)
    try:
        print('login------------',payload)
        email=payload['email']
        query=User.objects.filter(email=email).get()
        print(query.id)
        if not query:
            return   HttpResponseBadRequest(b'email  not  exist')
        if bcrypt.checkpw(payload['password'].encode(),query.password.encode()):  #Judge password validity
            # Verifying and passing
            token=get_token(query.id)
            print('token',token)
            res=JsonResponse({
                'user':{
                    'user_id':query.id,
                    'name':query.name,
                    'email':query.email,
                },'token':token
            })
            return   res
        else:
            return  HttpResponseBadRequest(b'password  is not correct')
    except  Exception as e:
        logging.info(e)
        return   HttpResponseBadRequest(b'The request parameter is not valid')

The request parameters are as follows

7 JWT expiration issues

1 Overview

pyjwt supports setting expiration. If it expires during decode, an exception will be thrown directly. You need to add clamin exp to payload. Exp requires an int timestamp.

2 the relevant codes are as follows

from django.shortcuts import render
from django.http import HttpRequest, HttpResponse, HttpResponseBadRequest, JsonResponse
import simplejson
from .models import User
from testdj.settings import SECRET_KEY
import jwt
import datetime
import bcrypt

# Defining time
EXP_TIMNE = 10 * 3600 * 8

def get_token(user_id):
    return jwt.encode(payload={'user_id': user_id, 'exp': int(datetime.datetime.now().timestamp())+EXP_TIMNE}
                      , key=SECRET_KEY, algorithm='HS256').decode()

def authontoken(view):
    def __wapper(request: HttpRequest):
        token = request.META.get('HTTP_JWT')
        if token:
            try:
                payload = jwt.decode(token, SECRET_KEY, algorithm='HS256')  # Here's a mechanism to handle expiration
                user = User.objects.filter(pk=payload['user_id']).get()  # Obtain the user u ID. if it exists, it indicates that the token is the token of the current user.
                request.user_id = user.id# Get user? ID here for later direct processing
                print('token Legal verification passed')
            except  Exception as e:
                print(e)
                return HttpResponseBadRequest(b'token  auth  failed')
        else:
            print('Not logged in, please log in')
        return view(request)

    return __wapper

def reg(request: HttpRequest):
    try:
        payload = simplejson.loads(request.body)
        email = payload['email']
        print(email)
        query = User.objects.filter(email=email)  # Get mailbox information
        if query:  # If mailbox exists
            return HttpResponseBadRequest(b'email  exist')
        user = User()
        name = payload['name']
        passowrd = payload['password'].encode()
        print(email, name, passowrd)
        user.name = name
        user.password = bcrypt.hashpw(passowrd, bcrypt.gensalt()).decode()  # Get encrypted password information
        user.email = email
        try:
            user.save()
            return JsonResponse({'userinfo': {
                'USER_ID': user.id,
                'name': user.name,
                'email': user.email,
            }, 'token': get_token(user.id)})
        except  Exception as e:
            print(e)
            return HttpResponseBadRequest(b'data  insert  failed')
    except  Exception  as e:
        print(e)
        return HttpResponseBadRequest(b'paraments  type  not  legal')

@authontoken
def login(request: HttpRequest):
    try:
        payload = simplejson.loads(request.body)  # Mailbox and password, and token can be obtained. It is necessary to determine whether the mailbox exists first. If not, an error will be reported directly.
        email = payload['email']
        print(email, '-------------------------------')
        user = User.objects.filter(email=email).get()
        if not user.id:
            return HttpResponseBadRequest("email :{}  not exist".format(email).encode())
        password = payload['password']
        if bcrypt.checkpw(password.encode(), user.password.encode()):
            return JsonResponse({
                "userinfo": {
                    "user_id": user.id,
                    "user_name": user.name,
                    "user_email": user.email,
                },
                "token": get_token(user.id)
            })
        else:
            return HttpResponseBadRequest(b'password  failed')
    except  Exception  as e:
        print(e)
        return HttpResponseBadRequest(b'email  failed')

Four blog interface implementation

1 functional analysis

function Function name Request method Route
Publish (add) pub post /pub
Read the article (check) get get /(\d+)
List (pagination) getall get /

2 routing configuration

1 add primary route

blog/urls.py configuration

from django.contrib import admin
from django.conf.urls import  url,include # The include module is introduced here for communication processing with the lower module.

urlpatterns = [
    url(r'admin/', admin.site.urls),
    url(r'^user/',include('user.urls')),  # Here, the user.urls represents the URLs file reference under the user application.
    url(r'^post/',include('post.urls'))
]

2 add secondary route

post/urls.py

#!/usr/bin/poython3.6
#conding:utf-8
from  django.conf.urls import  url
from  post.views import  get,getall,pub

urlpatterns=[
    url(r'pub',pub),
    url(r'^$',getall),
    url(r'(\d+)',get)
]

2. Add blog function

1 create database class

Create the following configuration in / blog/post/models.py

from django.db import models
from testapp.models import User

class Post(models.Model):
    class Meta:
        db_table = 'post'

    id = models.AutoField(primary_key=True)  # Primary key self increment
    title = models.CharField(max_length=256, null=False)  # Article title definition
    pubdata = models.DateTimeField(auto_now=True) # Automatic processing of time updates
    author = models.ForeignKey(User, on_delete=False)  # Defining foreign keys

    def __repr__(self):
        return "<Post  id:{}  title:{}>".format(self.id, self.title)

    __str_ = __repr__

class Content(models.Model):  # If you do not add an id here, the system will automatically add an auto add id for related operations
    class Meta:
        db_table = 'content'

    post = models.OneToOneField(Post, to_field='id', on_delete=False) # One to one, where there will be a foreign key reference post Ou ID
    content = models.TextField(null=False)

    def __repr__(self):
        return "<Content  {}  {}>".format(self.id, self.post)

    __str__ = __repr__

2 migration configuration

 python  manage.py makemigrations

3 effective configuration

python  manage.py migrate

View results

4 add to the interface as follows

/Add the following configuration to blog/post/admin.py

from django.contrib import admin
from .models import Content, Post

admin.site.register(Content)
admin.site.register(Post)

See below

5 add test data as follows

1 post data is as follows

2. Add content data as follows

3 implementation of upload interface

The user submits json data from the browser, including title and content.
Submit the user to be authenticated and verify jwt from the requested header

from django.http import HttpResponseBadRequest, HttpRequest, HttpResponse, JsonResponse
from .models import Post, Content
import math
from   user.views import authontoken
import simplejson

@authontoken  # Certification is required here. Relevant operations can only be carried out after the authentication is passed. It will get a user ID and process it by whether there is a user ID.
def pub(request: HttpRequest):
    try:
        payload = simplejson.loads(request.body)
        title = payload['title']
        author = request.user_id
        post = Post()
        post.title = title
        post.author_id = author
        try:
            post.save()
            cont = Content()
            content = payload['content']
            cont.content = content
            cont.post_id = post.id
            try:
                cont.save()
                return JsonResponse({"user_id": post.id})
            except Exception  as e:
                print(e)
                return HttpResponseBadRequest(b'con insert into failed')
        except Exception as e:
            print(e)
            HttpResponseBadRequest(b'post  data  insert  failed')

    except  Exception as e:
        print(e)
        return HttpResponseBadRequest(b'request  param  not  auth')

give the result as follows

Result without token added

Result of token added

4. get interface implementation of blog operation

View the blog post according to the post UU ID and return
Here is the view, no authentication is required, and the relevant codes are as follows

def get(request: HttpRequest, id):  # Use here to get the content of the previously configured group match
    print('Article ID', id)
    try:
        query = Post.objects.filter(pk=id).get()
        if not query: 
            return HttpResponseBadRequest(b'article  not exist')
        return JsonResponse({
            "post": {
                "post_title": query.title,
                "author_id": query.author.id,
                "post_conent": query.content.content,  # In this way, the data of the associated database can be obtained.
                "post_user": query.author.email,
                'date': query.pubdata,
                'post_name': query.author.name,
            }
        })
    except Exception as  e:
        print(e)
        return HttpResponseBadRequest(b'article 00 not exist')

give the result as follows

5. Implementation of getall interface

Start the get request, query through the query string http: / / URL / post /? Page = 1 & size = 10, and obtain the relevant paging data and basic data.

The code is as follows

def getall(request: HttpRequest):
    try:
        page = int(request.GET.get('page', 1))  # Here you can get the values of relevant data, page and size
        page = page if page > 0 else 1
    except:
        page = 1
    try:
        size = int(request.GET.get('size', 20))
        size = size if size > 0 and size < 11 else 10
    except:
        size = 10
    start = (page - 1) * size  # Start data list value
    postsall = Post.objects.all()
        posts = Post.objects.all()[::-1][start:page * size]

    # Total data, current page, total pages
    count = postsall.count()
    # PageCount
    pages = math.ceil(count / size)
    # Current page
    page = page
    # Number of current pages
    return JsonResponse({
        "posts": [
            {
                "post_id": post.id,
                "post_title": post.title,
                "post_name": post.author.name,
            } for post in posts
        ],
        "pattern": {
            "count": count,
            "pages": pages,
            "page": page,
            "size": size,
        }
    })

Optimize the code by using the same function for page and size as follows

def getall(request: HttpRequest):
    size=validate(request.GET,'size',int,20,lambda   x,y :  x  if  x>0 and  x<20  else  y)
    page=validate(request.GET,'page',int,1,lambda   x,y :  x  if  x>0   else  y)
    start = (page - 1) * size  # Start data list value
    print(size, page)
   postsall = Post.objects.all()
        posts = Post.objects.all()[::-1][start:page * size]

    # Total data, current page, total pages
    count = postsall.count()
    # PageCount
    pages = math.ceil(count / size)
    # Current page
    page = page
    # Number of current pages
    return JsonResponse({
        "posts": [
            {
                "post_id": post.id,
                "post_title": post.title,
                "post_name": post.author.name,
            } for post in posts
        ],
        "pattern": {
            "count": count,
            "pages": pages,
            "page": page,
            "size": size,
        }
    })

give the result as follows

So far, the basic development of back-end functions has been completed.

Posted by gbow on Mon, 28 Oct 2019 04:13:45 -0700