Django中间件的使用以及开发

Django的中间件为我们提供了全局的请求拦截以及对请求做出某些加工处理, 善用中间件能够为我们的开发提供非常多的便利.

1. Django 默认加载的中间件

在项目全局settings.py文件中,默认加载的中间件为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
MIDDLEWARE_CLASSES = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'corsheaders.middleware.CorsMiddleware', # 跨域解决方案
'django.middleware.common.CommonMiddleware',
# 'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
# 用户的验证
'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'corsheaders.middleware.CorsMiddleware',
# 自定义中间件
'WxTokenLogin.disable_csrf.DisableCSRF',
'WxTokenLogin.django_jwt_session_auth.JwtAuthMiddleware'
]

Django运行后,函数会逐一加载所有的中间件,并运行process_request中的逻辑,所以说,中间件的开发就是去编写process_request以及process_response里面的逻辑。

2. Django结合微信小程序开发token验证中间件

中间件的执行顺序为:请求时由上至下执行,返回时从下至上执行。
由于小程序不支持Cookie的形式来进行验证,所以就只能使用Token的认证方式来进行用户的权限验证。
Token认证流程:

Alt text

在小程序的认证中,用户并不会输入账号和密码,而是前端调用微信接口生成code,将code发送至后端,后端使用code以及AppIDAppScret等信息向微信发起请求,获取该用户在该小程序下的openId。该openId即作为该小程序下的唯一标志进行用户创建。并且根据创建的小程序用户通过各种函数生成Token返回至前端。前端再每次调用接口时,对Request headers进行组装,形式为:

1
2
3
{
"Authorization": "Zero xxx.xxx.xxx"
}

其中的Zero为前缀(prefix),可以在后端代码中进行自定义。
后端收到请求时,首先经过中间件取出请求头部

1
2
headers = request.META.get('HTTP_AUTHORIZATION', b'')
# 注意,这里的headers字段是经过转义了的,并不是 Authorization

使用某些字符操作即可取出Token, 再经过一系列的转换最终得到app_user.
核心代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
from six import text_type
# 这些方法并不需要我们自己造轮子,django-rest-framework-jwt已经为我们提供了成套的解决方案,只不过JWT与`Django.User`耦合较为紧密,需要自己抽离出部分逻辑进行二次加工.
from .utils import jwt_decode_handler, authenticate_credentials, user_to_payload_handler, \
jwt_encode_handler
from api_settings import JWT_PREFIX


class TokenMiddleware(object):
def process_request(self, request):
# 取出Authorization header中的value
jwt_val = self.get_authorization_header(request)
token = self._get_token(jwt_val)
if not token:
request.app_user = None
else:
auth_payload = jwt_decode_handler(token)
app_user = authenticate_credentials(auth_payload)
request.app_user = app_user

def process_response(self, request, response):
return response

@classmethod
def appuser_login(cls, user, request):
"""
根据app_user生成token
"""
payload = user_to_payload_handler(user)
token = jwt_encode_handler(payload)
return token

def get_authorization_header(self, request):
"""
Return request's 'Authorization:' header, as a bytestring.
Hide some test client ickyness where the header can be unicode.
"""
auth = request.META.get('HTTP_AUTHORIZATION', b'')
if isinstance(auth, text_type):
# Work around django test client oddness
auth = auth.encode('utf-8')
return auth

def _get_token(self, jwt_val):
"""get token from jwt_val
"""
try:
prefix, token = jwt_val.split()
except ValueError:
return None
default_prefix = JWT_PREFIX
if prefix != default_prefix:
return None
token = token.decode('utf8')
return token

appuser_login = TokenMiddleware.appuser_login

整体项目Github地址: https://github.com/SmartKeyerror/django-middleware

3. Django中间件原理

Djang在请求进入时顺序加载MIDDLEWARE_CLASSES中的class, 并调用class.process_request, 请求对象被被实例化并传入至process_request函数中,执行处理,处理完毕后进入下一个中间件.同时也可以直接在处理函数中返回HttpResponse对象,一般来讲这样的场景为验证没有通过.
process_request方法仅接受request参数,所有的信息均包含在该对象中,那么request对象在这里就显得至关重要了.
比较重要的属性:

  • 请求方法, 请求路径
1
2
request.method
request.path
  • 请求头以及其他
1
request.META # 该dict中包含了非常多的请求头信息以及其他信息

除了Token的验证以外, 中间件还能够为请求提供ip黑名单过滤, 缓存等较为便利的应用.