ubuntu16.04安装django,mongoengine,微信小程序登录认证
一、django + mongo
1.安装当前最新的django版本django2.2.3
我们使用django2.2版本,对应的python版本不能使用系统默认的python2.7
$ sudo apt install python3-pip $ sudo pip3 install django==2.2.3
可选安装svn客户端:
安装svn,并从svn服务器下载代码
$ sudo apt install subversion
$ mkdir projects && cd projects
$ svn co svn://192.168.2.249/repos/apps/wechat_test
2.安装mongoengine
$ sudo pip3 install mongoengine
为了使用mongoengine,在settings.py中添加以下代码
import mongoengine conn = mongoengine.connect("parking_test")#本地mongo数据库
3.安装pymysql
由于django默认使用的sqlite3存在高并发情况下被锁住导致部分请求失败的情况,即使我们的业务内容完全不需要数据库的情况,django的认证等还是要用到数据库,将数据库置为空是不行的。而django支持的数据库只有4个sqlite、mysql、postgresql、oracle,使用其次较多的应该就是mysql了,mysql对并发支持相比之下就好很多。
$ sudo pip3 install pymysql
修改settings.py中的DATABASES项
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'djangodb', 'USER': 'root', 'PASSWORD': '123', 'HOST':'',#本地mysql 'PORT':'',#默认端口 }, }
然后不出意外的话运行django项目会抛出异常
django.core.exceptions.ImproperlyConfigured: mysqlclient 1.3.13 or newer is required; you have 0.9.3.
异常提示mysqlclient需要1.3.13或更高版本,而实际上github上最高的版本目前就0.9.3,没有1.3.13。搜索以上问题修改2个文件能解决问题,分别是:(注意自己的python版本,根据抛出的异常堆栈找到相应的文件)
/usr/local/lib/python3.5/dist-packages/django/db/backends/mysql/base.py
/usr/local/lib/python3.5/dist-packages/django/db/backends/mysql/operations.py
打开文件/usr/local/lib/python3.5/dist-packages/django/db/backends/mysql/base.py 并搜索version = Database.version_info
,将这行下面的if version < (1, 3, 13):抛出异常注释掉
version = Database.version_info #if version < (1, 3, 13): # raise ImproperlyConfigured('mysqlclient 1.3.13 or newer is required; you have %s.' % Database.__version__)
再次运行django项目,还有一个异常
AttributeError: 'str' object has no attribute 'decode'
从异常堆栈看到是/usr/local/lib/python3.5/dist-packages/django/db/backends/mysql/operations.py中抛出的,打开文件并搜索query = query.decode(errors='replace'),修改这行代码.将decode改为encode
#query = query.decode(errors='replace') query = query.encode(errors='replace')
4.时区
settings.py关于时区的设定默认是UTC,在linux下它对应的时区是美国/芝加哥,在中国当然要用中国的时区,修改配置很简单
TIME_ZONE = 'Asia/Shanghai' USE_TZ = False
可选安装:
silk性能监测工具
$ sudo pip3 install django-silk
settings.py中添加配置silk
INSTALLED_APPS = ( ... 'silk' ) MIDDLEWARE = [ ... 'silk.middleware.SilkyMiddleware', ... ]
STATIC_ROOT = os.path.join(BASE_DIR,'static')#BASE_DIR是项目文件夹路径
在主urls.py中添加silk
from django.urls import include
import silk urlpatterns = [ ... path('silk/', include('silk.urls', namespace='silk')), ]
然后执行
$ python3 manage.py migrate
$ python3 manage.py collectstatic
再以默认ip和端口运行项目,在浏览器中输入localhsot:8000/silk就能看到silk的监测情况
最后的settings.py:
其中silk 是django可视化性能监测工具包,留意 ALLOWED_HOSTS = ['*']
""" Django settings for wechat_test project. Generated by 'django-admin startproject' using Django 2.2.3. For more information on this file, see https://docs.djangoproject.com/en/2.2/topics/settings/ For the full list of settings and their values, see https://docs.djangoproject.com/en/2.2/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.2/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! SECRET_KEY = '+-i(^67u0d2_f0=!@8s)a*x4=ao4&k1@hc$qi3lrc=xh5g+r5!' # 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', #'park_space', #'coupon', #'feedback', #'order', 'silk', ] 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', 'silk.middleware.SilkyMiddleware', ] ROOT_URLCONF = 'wechat_test.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 = 'wechat_test.wsgi.application' # Database # https://docs.djangoproject.com/en/2.2/ref/settings/#databases # DATABASES = { # 'default': { # 'ENGINE': 'django.db.backends.sqlite3', # 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), # 'OPTIONS':{ # 'timeout':20, # } # } # } import mongoengine conn = mongoengine.connect("parking_test") DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'djangodb', 'USER': 'root', 'PASSWORD': '123', 'HOST':'', 'PORT':'', }, } # Password validation # https://docs.djangoproject.com/en/2.2/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.2/topics/i18n/ LANGUAGE_CODE = 'en-us' #TIME_ZONE = 'UTC' TIME_ZONE = 'Asia/Shanghai' USE_I18N = True USE_L10N = True USE_TZ = False # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/2.2/howto/static-files/ STATIC_URL = '/static/' STATIC_ROOT = os.path.join(BASE_DIR,'static')
二、nginx + uwsgi +django
django的配置沿用前面的django+mongo就行,假设我们新建的django项目wechat_test(绝对路径/home/dyan/projects/wechat_test)而不是从svn下载的
1.安装uwsgi
$ sudo pip3 install uwsgi
进入项目目录执行,以下命令,浏览器访问http://localhost:8000/silk(因为还没有定义其他http请求,或者http://localhost:8000/admin)
$ uwsgi --http :8000 --chdir . --module wechat_test.wsgi
使用脚步启动,在uwsgi_scripts下新建一个文件uwsgi.ini
$ cd uwsgi_scripts $ touch uwsgi.ini
打开uwsgi.ini填写配置
[uwsgi] # 项目目录绝对连接 chdir=/home/hlfpark/projects/wechat_test # 指定项目的application module=wechat_test.wsgi:application # 指定sock的文件路径 socket=uwsgi_script/uwsgi.sock #这个后面nginx代理需要用到,放到运行nginx的用户目录下,以免namespace无权访问。 # 进程个数 workers=4 pidfile=uwsgi_script/uwsgi.pid # 指定IP端口 http=192.168.2.244:8080 #这里可以用:8080代替,但是不能用localhost:8080,这样只能本机访问。后面用nginx代理的话就无所谓 # 指定静态文件 static-map=/static=/home/hlfpark/projects/wechat_test/static #我们在settings.py中设置的STATIC_ROOT路径 # 启动uwsgi的用户名和用户组 uid=hlfpark gid=root # 启用主进程 master=true # 自动移除unix Socket和pid文件当服务停止的时候 vacuum=true # 序列化接受的内容,如果可能的话 thunder-lock=true # 启用线程(python GIL线程对于高并发应该没什么用) #enable-threads=true # 设置自中断时间 harakiri=30 # 设置缓冲 post-buffering=4096 # 设置日志路径 daemonize=uwsgi_script/uwsgi.log #日志大小配置500M log-maxsize = 500000000
然后就可以用uwsgi启动django项目了
$ uwsgi -ini uwsgi.ini
2.使用supervisor管理uwsgi
虽然我们使用uwsgi启动了几个工作进程,但是不好管理,使用supervisor让我们的django项目进程便于管理。
3.配置nginx
location / { # 导入一个Nginx模块他是用来和uWSGI进行通讯的 include uwsgi_params;
#这个.sock文件就是之前在uwsgi.ini中配置的路径 uwsgi_pass unix:/home/hlfpark/projects/wechat_test/uwsgi_script/uwsgi.sock; uwsgi_connect_timeout 30; } # 指定静态文件路径 location /static/{ alias /home/hlfpark/projects/wechat_test/static; index index.html index.htm; }
三、django+ rest_framework_jwt
微信小程序登录,后台从微信服务器拿到openid和session_key,然后用rest_framework_jwt生成用户登录态发送给用户,用户保存登录态,之后与后台的通信都带上用户登录态做用户认证。
rest_framework_jwt是一种类似数字签名的东西。数字签名是明文和密钥用加密算法算出一段定长的字符串,将得出的字符串附加到明文后面,以此做用户认证。rest_framework_jwt为了适用url传递在明文部分增加了加密方式,对数据还进行的base64url编码。默认的密钥是settings.py的SECRET_KEY
首先安装rest_framework_jwt
$ pip install djangorestframework-jwt
$ pip install djangorestframework
然后配置settings.py,参考:http://jpadilla.github.io/django-rest-framework-jwt/
JWT_AUTH配置项如果没有全部使用默认配置
#INSTALLED_APPS配置项增加一项rest_framework
INSTALLED_APPS = [
......
'rest_framework',
]
REST_FRAMEWORK = { 'DEFAULT_PERMISSION_CLASSES': ( 'rest_framework.permissions.IsAuthenticated', ), 'DEFAULT_AUTHENTICATION_CLASSES': ( 'rest_framework_jwt.authentication.JSONWebTokenAuthentication', # <------- 'rest_framework.authentication.SessionAuthentication', 'rest_framework.authentication.BasicAuthentication', ), } JWT_AUTH = { 'JWT_ENCODE_HANDLER': 'rest_framework_jwt.utils.jwt_encode_handler', 'JWT_DECODE_HANDLER': 'rest_framework_jwt.utils.jwt_decode_handler', #指定自定义函数以生成令牌有效内容 'JWT_PAYLOAD_HANDLER': 'rest_framework_jwt.utils.jwt_payload_handler', #如果以不同于默认有效负载处理程序的方式存储user_id,请实现此函数以从有效负载中获取user_id。注意:将弃用JWT_PAYLOAD_GET_USERNAME_HANDLER。 'JWT_PAYLOAD_GET_USER_ID_HANDLER': 'rest_framework_jwt.utils.jwt_get_user_id_from_payload_handler', #负责控制登录或刷新后返回的响应数据。覆盖以返回自定义响应,例如包括用户的序列化表示。默认返回JWT令牌。 'JWT_RESPONSE_PAYLOAD_HANDLER': 'rest_framework_jwt.utils.jwt_response_payload_handler', 'JWT_SECRET_KEY': SECRET_KEY,#这是用于签署JWT的密钥。确保这是安全的,不共享或公开。默认是项目的settings.py中的SECRET_KEY。 'JWT_GET_USER_SECRET_KEY': None,#这是JWT_SECRET_KEY的更强大版本。它是根据用户定义的,因此如果令牌被泄露,可以由所有者轻松更改。更改此值将使给定用户的所有令牌都无法使用。值应该是一个函数,接受用户作为唯一参数并返回它的密钥。默认值为“None”。 'JWT_PUBLIC_KEY': None, #这是cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey类型的对象。它将用于验证传入JWT的签名。设置时将覆盖JWT_SECRET_KEY。阅读文档以获取更多详细信息。请注意,JWT_ALGORITHM必须设置为RS256,RS384或RS512之一。默认值为“None”。 'JWT_PRIVATE_KEY': None,#这是cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey类型的对象。它将用于签署JWT的签名组件。设置时将覆盖JWT_SECRET_KEY。阅读文档以获取更多详细信息。请注意,JWT_ALGORITHM必须设置为RS256,RS384或RS512之一。默认值为“None”。 'JWT_ALGORITHM': 'HS256',#可能的值是PyJWT中用于加密签名的任何支持算法.默认值为“HS256”。 'JWT_VERIFY': True,#如果密钥是错误的,它会引发一个jwt.DecodeError。您仍然可以通过将JWT_VERIFY设置为False来获取有效负载 'JWT_VERIFY_EXPIRATION': True,#您可以通过将JWT_VERIFY_EXPIRATION设置为False来关闭到期时间验证。如果没有过期验证,JWT将永远存在,意味着攻击者可以无限期地使用泄露的令牌。默认为True。 'JWT_LEEWAY': 0,#这允许您验证过去但不是很远的过期时间。例如,如果您有一个JWT有效负载,其过期时间设置为创建后30秒,但您知道有时您将在30秒后处理它,您可以设置10秒的余地以获得一些余量。 'JWT_EXPIRATION_DELTA': datetime.timedelta(days=30),#设置过期时间。默认值为datetime.timedelta(seconds= 300)(5分钟)。 'JWT_AUDIENCE': None,#这是一个字符串,将根据令牌的aud字段进行检查(如果存在)。默认值None(如果JWT上有aud,则会失败) 'JWT_ISSUER': None,#这是一个字符串,将根据令牌的iss字段进行检查。默认值为None(不检查JWT上的iss)。 'JWT_ALLOW_REFRESH': False,#启用令牌刷新功能。从rest_framework_jwt.views.obtain_jwt_token发出的令牌将具有orig_iat字段。默认值为False 'JWT_REFRESH_EXPIRATION_DELTA': datetime.timedelta(days=365),#限制令牌刷新,是一个datetime.timedelta实例。这是在原始令牌之后可以刷新未来令牌的一段时间。默认值为datetime.timedelta(days = 7)(7天)。 'JWT_AUTH_HEADER_PREFIX': 'JWT',#您可以修改需要与令牌一起发送的Authorization标头值前缀。默认值为JWT。此决定是在PR#4中引入的,以允许在DRF中同时使用此程序包和OAuth2。用于令牌和授权标头的另一个常见值是Bearer。默认是JWT。 'JWT_AUTH_COOKIE': None,#如果除了Authorization标头之外还要使用http cookie作为令牌的有效传输,则可以将其设置为字符串。您在此处设置的字符串将用作cookie名称,该名称将在请求令牌时在响应标头中设置。如果设置,令牌验证过程也将查看此cookie。如果请求中存在标头和cookie,则“授权”标头优先。默认值为“None”,在创建令牌时未设置cookie,验证时也不接受。 }
之后手动签发jwt,上面的链接仍然有示例。由于我们使用mongodb,需要注意的一点是在用jwt_payload_handler(user)将数据库对象转为python字典对象后user_id字段的值是一个ObjectID对象,不能直接调用
jwt_encode_handler
(),需要再将ObjectID转成python的原生数据类型。
看一下jwt_payload_handler(user)的源码,从数据库出来的数据基本上只用到了pk(可能是id或user_id,根据数据库),username,email这三个字段(也可以没有email字段),而其他的exp,orig_iat,aud,iss都是jwt配置文件相关。所以这里不准备使用jwt_payload_handler函数,而是自己弄一个python字典类型,因为openid唯一,我们只需要openid外加一个过期时间戳exp就够了,session_key貌似用不上而且密码也不能随便发出去吧。当然如果需要也可以附带其他数据。下划线内容替换删除线内容:
import datetime
# 判断用户是否第一次登录 try: user = User.objects.get(openid=openid) except Exception: # 微信用户第一次登陆,新建用户 user = User.objects.create(openid=openid, sex=1,username ='username') # 手动签发jwt jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER payload = jwt_payload_handler(user) payload={} payload['openid']=openid
#虽然解码token出来是时间戳,但是这里不能用时间戳,要用datetime.datetime.utcnow()加设定的过期时间。这里需要注意time和datetime库的默认时区都不是中国时区+8小时才是北京时间。
#但只是用来认证用户登录过期,不用存入数据库倒是没有关系。如果确实需要可以加上datetime.timedelta(hours=8),这样后面使用time.time()获得时间戳也要加8小时
payload['exp']=datetime.datetime.utcnow()+api_settings.JWT_EXPIRATION_DELTA #+ datetime.timedelta(hours=8) token = jwt_encode_handler(payload)
生成的token传给用户,用户向后台发起请求时附带这个token就行了,后台解码token就能进行用户认证了。解出的结果中exp字段是token过期时间的时间戳,openid字段即我们需要的认证的字段,这里需要检测异常,密钥不对的话会抛出异常(除非配置的JWT_VERIFY字段为False,但是这样谁都可以伪造用户发起请求,签名认证就没有意义了)
import jwt
jwt_decode_handler = api_settings.JWT_DECODE_HANDLER try:
user_dict = jwt_decode_handler(token)
except(jwt.DecodeError):
#do something
pass
else:
pass