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 correctuser=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.