在前边的文章中,我们已经在templates中开发了一个名为index.html的页面代码,接下来我们修改一下该文件的代码为如下内容:
<head>
<meta charset="UTF-8">
<title>Django Pagetitle>
head>
<body>
<h1>发布会系统h1>
<form>
<label>
<input name="username" type="text" placeholder="username">
label><br>
<label>
<input name="password" type="password" placeholder="password">
label><br>
<button id="btn" type="submit">登陆button>
form>
body>
很显然,变成了一个登陆页面,那么登陆表单(form)中的数据(用户名密码)以什么方法提交到服务端?Django如何验证用户名密码的正确性?验证失败与成功又做如何处理?
客户端通过HTTP协议向服务端提交请求,最常用的方法无非就是GET和POST,而他们最根本的区别便是:GET是从指定的资源请求数据而POST是向指定的资源提交要被处理的数据,看似区别不大,却是天壤之别。
将上一点html代码修改为如下内容:
<head>
<meta charset="UTF-8">
<title>Django Pagetitle>
head>
<body>
<h1>发布会系统h1>
<form method="get">
<label>
<input name="username" type="text" placeholder="username">
label><br>
<label>
<input name="password" type="password" placeholder="password">
label><br>
<button id="btn" type="submit">登陆button>
form>
body>
然后在页面中随便输入用户名和密码,看一下URL和控制台的信息变化:
URL变为
http://127.0.0.1:8000/index/?username=111&password=111
而后台中会打印一条日志信息:
[20/Oct/2019 16:28:44] "GET /index/?username=111&password=111 HTTP/1.1" 200 404
<head>
<meta charset="UTF-8">
<title>Django Pagetitle>
head>
<body>
<h1>发布会系统h1>
<form method="post">
<label>
<input name="username" type="text" placeholder="username">
label><br>
<label>
<input name="password" type="password" placeholder="password">
label><br>
<button id="btn" type="submit">登陆button>
form>
body>
再次输入用户名密码点击登陆按钮,页面会变为,如下图所示,并且URL并未像Get方法一样带有用户名密码等信息。
CSRF即Cross-Site Request Forgery 跨站请求伪造漏洞,Django针对于CSRF的报错是是在生成的每个表单中放置一个自动生成的令牌,通过这个令牌判断POST请求是否来自同一个网站,继续修改HTML代码
<head>
<meta charset="UTF-8">
<title>Django Pagetitle>
head>
<body>
<h1>发布会系统h1>
<form method="post">
<label>
<input name="username" type="text" placeholder="username">
label><br>
<label>
<input name="password" type="password" placeholder="password">
label><br>
<button id="btn" type="submit">登陆button>
{% csrf_token %}
form>
body>
加上令牌后,刷新页面,再次输入用户名密码,点击登陆按钮,就不会再包CSRF错误,在浏览器开发者工具中查看请求,如下图所示
如果想忽略掉这个检查,可以在Django项目的settings.py文件中注释掉该检查
继续修改html代码,如下内容所示:
<head>
<meta charset="UTF-8">
<title>Django Pagetitle>
head>
<body>
<h1>发布会系统h1>
<form method="post" action="/login_action/">
<label>
<input name="username" type="text" placeholder="username">
label><br>
<label>
<input name="password" type="password" placeholder="password">
label><br>
<button id="btn" type="submit">登陆button>
{% csrf_token %}
form>
body>
这表明,当我们提交表单的时候由http://127.0.0.1:8000/login_action/路径来提交登陆请求
打开…/guest/urls.py文件添加login_action/的路由
from django.contrib import admin
from django.urls import path
from sign import views
from django.conf.urls import url
urlpatterns = [
path(r'admin/', admin.site.urls),
path(r'index/', views.index), # 添加index/路径配置
url(r'login_action/', views.login_action),
]
登陆请求有views.py视图文件的login_action函数来处理,在视图文件中添加login_action函数
def login_action(request):
if request.method == 'POST':
username = request.POST.get('username', '')
password = request.POST.get('password', '')
if username == 'admin' and password == 'password':
return HttpResponse('login success~!')
else:
return render(request, 'index.html', {'error': 'username or password error~!'})
客户端的请求信息都包含在request中,如何获取request中包含的信息,可参考Django文档
从login_action函数中我们看到,用户名密码错误的话会提示失败信息,而原有html代码并没有显示错误信息的地方,因此修改html代码如下:
<head>
<meta charset="UTF-8">
<title>Django Pagetitle>
head>
<body>
<h1>发布会系统h1>
<form method="post" action="/login_action/">
<label>
<input name="username" type="text" placeholder="username">
label><br>
<label>
<input name="password" type="password" placeholder="password">
label><br>
{{ error }}<br>
<button id="btn" type="submit">登陆button>
{% csrf_token %}
form>
body>
使用Django的模板语言,添加{{error}},它对应render返回字典中的key,即‘error’,在登陆失败的页面中显示对应的value,即"username or password error~!"
创建…/templates/event_manage.html
<head>
<meta charset="UTF-8">
<title>Event Manage Pagetitle>
head>
<body>
<h1>Login Success!h1>
body>
修改…/sign/views.py文件
"""
Django的视图文件,控制向前端页面展示的内容
"""
from django.shortcuts import render
from django.http import HttpResponse
from django.http import HttpResponseRedirect
# Create your views here.
def index(request):
return render(request, "index.html")
def login_action(request):
if request.method == 'POST':
username = request.POST.get('username', '')
password = request.POST.get('password', '')
if username == 'admin' and password == 'password':
# 页面重定向
return HttpResponseRedirect('/event_manage/')
else:
return render(request, 'index.html', {'error': 'username or password error~!'})
# 创建event_manage函数,用于返回页面event_manage.html
def event_manage(request):
return render(request, 'event_manage.html')
在…/guest/urls.py文件中添加event_manage/的路由
from django.contrib import admin
from django.urls import path
from sign import views
from django.conf.urls import url
urlpatterns = [
path(r'admin/', admin.site.urls),
path(r'index/', views.index), # 添加index/路径配置
url(r'login_action/', views.login_action),
url(r'event_manage/', views.event_manage),
]
修改…/sign/views.py
"""
Django的视图文件,控制向前端页面展示的内容
"""
from django.shortcuts import render
from django.http import HttpResponse
from django.http import HttpResponseRedirect
# Create your views here.
def index(request):
return render(request, "index.html")
def login_action(request):
if request.method == 'POST':
username = request.POST.get('username', '')
password = request.POST.get('password', '')
if username == 'admin' and password == 'password':
# 页面重定向
reponse = HttpResponseRedirect('/event_manage/')
"""
添加浏览器cookie, 给set_cookie方法传递三个参数,第一个参数user用于表示写入浏览器的Cookie名
第二个参数username是由用户在登录页面上输入的用户名
第三个参数3600用于设置cookie信息在浏览器中的保存时间,默认单位为秒
"""
reponse.set_cookie('user', username, 3600)
return reponse
else:
return render(request, 'index.html', {'error': 'username or password error~!'})
# 创建event_manage函数,用于返回页面event_manage.html
def event_manage(request):
"""
通过request.COOKIES来读取Cookie名为user的值,并通过render将它和event_manage.html一起返回
:param request:
:return:
"""
username = request.COOKIES.get('user', '') # 读取浏览器cookie
return render(request, "event_manage.html", {"user": username})
修改…/templates/event_manage.html页面,添加
<head>
<meta charset="UTF-8">
<title>Event Manage Pagetitle>
head>
<body>
<h1>Login Success!h1>
<div style="float:right;">
<a>嘿~!欢迎{{user}}a>
div>
body>
使用Cookie存在一定的安全隐患,因为用户信息都存储在浏览器中,而Session相对安全很多,浏览器中指保留一个SessionID
修改…/sign/views.py
"""
Django的视图文件,控制向前端页面展示的内容
"""
from django.shortcuts import render
from django.http import HttpResponse
from django.http import HttpResponseRedirect
# Create your views here.
def index(request):
return render(request, "index.html")
def login_action(request):
if request.method == 'POST':
username = request.POST.get('username', '')
password = request.POST.get('password', '')
if username == 'admin' and password == 'password':
# 页面重定向
reponse = HttpResponseRedirect('/event_manage/')
"""
添加浏览器cookie, 给set_cookie方法传递三个参数,第一个参数user用于表示写入浏览器的Cookie名
第二个参数username是由用户在登录页面上输入的用户名
第三个参数3600用于设置cookie信息在浏览器中的保存时间,默认单位为秒
"""
# reponse.set_cookie('user', username, 3600)
request.session['user'] = username # 将session信息记录到浏览器
return reponse
else:
return render(request, 'index.html', {'error': 'username or password error~!'})
# 创建event_manage函数,用于返回页面event_manage.html
def event_manage(request):
"""
通过request.COOKIES来读取Cookie名为user的值,并通过render将它和event_manage.html一起返回
:param request:
:return:
"""
# username = request.COOKIES.get('user', '') # 读取浏览器cookie
username = request.session.get('user', '') # 读取浏览器session
return render(request, "event_manage.html", {"user": username})
再次登陆,会报异常
这个异常跟Session的机制有关,既然要从Web服务器端来记录用户的信息,那么一定要有存放用户SessionID的地方,所以需要创建django_session表,而Django已经帮我们准备好这些基础表,只需要将它生成一下即可,在命令行执行如下命令
(venv) D:\Python_Django_Interface>python manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, contenttypes, sessions
Running migrations:
Applying contenttypes.0001_initial... OK
Applying auth.0001_initial... OK
Applying admin.0001_initial... OK
Applying admin.0002_logentry_remove_auto_add... OK
Applying admin.0003_logentry_add_action_flag_choices... OK
Applying contenttypes.0002_remove_content_type_name... OK
Applying auth.0002_alter_permission_name_max_length... OK
Applying auth.0003_alter_user_email_max_length... OK
Applying auth.0004_alter_user_username_opts... OK
Applying auth.0005_alter_user_last_login_null... OK
Applying auth.0006_require_contenttypes_0002... OK
Applying auth.0007_alter_validators_add_error_messages... OK
Applying auth.0008_alter_user_username_max_length... OK
Applying auth.0009_alter_user_last_name_max_length... OK
Applying auth.0010_alter_group_name_max_length... OK
Applying auth.0011_update_proxy_permissions... OK
Applying sessions.0001_initial... OK
Django默认设置SQLite3数据库,可以在…/settings.py文件中查看SQLite3的数据库配置
# 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'),
}
}
同时在工程结构中也能看到相应的数据库文件
重新登陆,并用开发者工具查看SessionID
目前为止用户登录信息的验证还是通过一个函数判断一组用户名密码,而这对于一个系统而言是不现实的,Djangofrom django.contrib import auth
封装好了用户认证和登陆的相关方法,直接使用即可。
在使用’migrate’命令来进行数据迁移时,Django同时也生成了auth_user表,该表中存放的用户信息可以用来登陆Django自带的Admin管理后台,在此之前需要先用命令行创建Admin后台的管理员账号
(venv) D:\Python_Django_Interface>python manage.py createsuperuser
Username (leave blank to use 'administrator'): admin
Email address: [email protected]
Password:
Password (again):
Superuser created successfully.
然后便可以通过网址http://127.0.0.1:8000/admin/来访问后台管理页面
Django已经封装好了用户认证和登陆的相关方法,并且使用auth_user表中的数据进行验证,修改…/sign/views.py文件中的login_action函数
from django.contrib import auth
def login_action(request):
if request.method == 'POST':
username = request.POST.get('username', '')
password = request.POST.get('password', '')
user = auth.authenticate(username=username,password=password)
if user is not None:
auth.login(request, user) # 判断用户名密码是否为None,如果不是None,则执行登陆
request.session['user'] = username # 将session信息记录到浏览器
response = HttpResponseRedirect('/event_manage/')
return response
else:
return render(request, 'index.html', {'error': 'username or password error~!'})
使用了authenticate函数,如果用户名和密码正确则返回user对象,否则返回None
到目前位置还存在一个严重的漏洞,关闭浏览器,重新打开输入URLhttp://127.0.0.1:8000/event_manage/,发现不需要登陆直接跳转到登陆成功页面了,为了解决这个问题Django仍旧提供了非常便利的方法,修改views.py文件
from django.contrib.auth.decorators import login_required
# 创建event_manage函数,用于返回页面event_manage.html
# 加上login_required注解
@login_required
def event_manage(request):
"""
通过request.COOKIES来读取Cookie名为user的值,并通过render将它和event_manage.html一起返回
:param request:
:return:
"""
# username = request.COOKIES.get('user', '') # 读取浏览器cookie
username = request.session.get('user', '') # 读取浏览器session
return render(request, "event_manage.html", {"user": username})
如果想限制某个视图函数必须登陆才能访问,则只需要在这个函数前加上@login.required装饰即可
在直接访问http://127.0.0.1:8000/event_manage/ (清缓存)便会直接跳如下页面
但我们期望的是跳转到登录页面,而不是Page not found,不能直接访问页面,并且url跳转到了http://127.0.0.1:8000/accounts/login/?next=/event_manage/,这并不是期望结果,接下来修改…/urls.py文件,增加路由
url(r'^$', views.index),
url(r'^index/$', views.index),
url(r'^accounts/login/$', views.index),