备注: 内容基于django1.9版本
auth模块实现功能
通过入口url.py文件中定义的urlpatterns可以看出,auth模块共定义了8个url,分别用于:
- 登录
- 注销
- 修改密码
- 修改密码完成
- 密码重置
- 密码重置完成
- 密码重置验证
- 密码重置结束
- from django.conf.urls import url
- from django.contrib.auth import views
-
- # urlpatterns直接是一个list即可
- urlpatterns = [
- url(r'^login/$', views.login, name='login'),
- url(r'^logout/$', views.logout, name='logout'),
- url(r'^password_change/$', views.password_change, name='password_change'),
- url(r'^password_change/done/$', views.password_change_done, name='password_change_done'),
- url(r'^password_reset/$', views.password_reset, name='password_reset'),
- url(r'^password_reset/done/$', views.password_reset_done, name='password_reset_done'),
- url(r'^reset/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$',
- views.password_reset_confirm, name='password_reset_confirm'),
- url(r'^reset/done/$', views.password_reset_complete, name='password_reset_complete'),
- ]
以下仅对登陆, 注销进行了学习.
login 登录
- def login(request, template_name='registration/login.html',
- redirect_field_name=REDIRECT_FIELD_NAME,
- authentication_form=AuthenticationForm,
- extra_context=None):
- """
- Displays the login form and handles the login action.
- """
- redirect_to = request.POST.get(redirect_field_name,
- request.GET.get(redirect_field_name, ''))
-
- if request.method == "POST":
- form = authentication_form(request, data=request.POST)
- if form.is_valid():
-
- # Ensure the user-originating redirection url is safe.
- if not is_safe_url(url=redirect_to, host=request.get_host()):
- redirect_to = resolve_url(settings.LOGIN_REDIRECT_URL)
-
- # Okay, security check complete. Log the user in.
- auth_login(request, form.get_user())
-
- return HttpResponseRedirect(redirect_to)
- else:
- form = authentication_form(request)
-
- current_site = get_current_site(request)
-
- context = {
- 'form': form,
- redirect_field_name: redirect_to,
- 'site': current_site,
- 'site_name': current_site.name,
- }
- if extra_context is not None:
- context.update(extra_context)
-
- return TemplateResponse(request, template_name, context)
登录处理视图函数依据功能分为:
- 展示登录页面供用户提供认证信息进行登录操作(
request.method == "GET"
) - 对用户提交的认证信息进行认证,并做出相应反映(
request.method == "POST"
)
request.method == "GET"
- 实例化一个
authentication_form
实例(通过传入request将request绑定到form实例上),供用户输入认证信息 - 获取站点信息,供页面显示
- 生成
context
,并使用关键词参数extra_context
进行拓展 - 返回页面(
TemplateResponse
)
此场景处理逻辑较简单
request.method == "POST"
- 通过获取到的用户数据(request.POST)实例化authentication_form
- 判断用户输入数据是否有效合理(检查是否有输入传入;并且调用full_clean方法来进行验证)。默认的AuthenticationForm中的clean方法中会调用
django.contrib.auth.authenticate
方法对用户提供的认证信息进行校验。而authenticate
方法会遍历settings中定义的AUTHENTICATION_BACKENDS
模块,并调用模块的authenticate方法进行认证,直到成功,否则出发user_login_failed singnal
- 调用
django.contrib.auth.login
方法使得用户登录成功,并返回HttpResponseRedirect
响应,重定向到redirect_field_name
指向地址。
- step2 form.is_valid()调用过程
- # django.forms.forms.py
- class BaseForm(object):
- ....
- @property
- def errors(self):
- "Returns an ErrorDict for the data provided for the form"
- if self._errors is None:
- self.full_clean()
- return self._errors
-
- def is_valid(self):
- """
- Returns True if the form has no errors. Otherwise, False. If errors are
- being ignored, returns False.
- """
- return self.is_bound and not self.errors
-
- def full_clean(self):
- """
- Cleans all of self.data and populates self._errors and
- self.cleaned_data.
- """
- self._errors = ErrorDict()
- if not self.is_bound: # Stop further processing.
- return
- self.cleaned_data = {}
- # If the form is permitted to be empty, and none of the form data has
- # changed from the initial data, short circuit any validation.
- if self.empty_permitted and not self.has_changed():
- return
-
- self._clean_fields()
- self._clean_form()
- self._post_clean()
-
- def _clean_fields(self):
- for name, field in self.fields.items():
- # value_from_datadict() gets the data from the data dictionaries.
- # Each widget type knows how to retrieve its own data, because some
- # widgets split data over several HTML fields.
- if field.disabled:
- value = self.initial.get(name, field.initial)
- else:
- value = field.widget.value_from_datadict(self.data, self.files, self.add_prefix(name))
- try:
- if isinstance(field, FileField):
- initial = self.initial.get(name, field.initial)
- value = field.clean(value, initial)
- else:
- value = field.clean(value)
- self.cleaned_data[name] = value
- ## 如果有clean_filedname方法的话,调用之
- if hasattr(self, 'clean_%s' % name):
- value = getattr(self, 'clean_%s' % name)()
- self.cleaned_data[name] = value
- except ValidationError as e:
- self.add_error(name, e)
-
- def _clean_form(self):
- try:
- cleaned_data = self.clean()
- except ValidationError as e:
- self.add_error(None, e)
- else:
- if cleaned_data is not None:
- self.cleaned_data = cleaned_data
-
- def _post_clean(self):
- """
- An internal hook for performing additional cleaning after form cleaning
- is complete. Used for model validation in model forms.
- """
- pass
- step2 django.contrib.auth.authenticate方法
- def load_backend(path):
- return import_string(path)()
-
- def _get_backends(return_tuples=False):
- backends = []
- for backend_path in settings.AUTHENTICATION_BACKENDS:
- backend = load_backend(backend_path)
- backends.append((backend, backend_path) if return_tuples else backend)
- if not backends:
- raise ImproperlyConfigured(
- 'No authentication backends have been defined. Does '
- 'AUTHENTICATION_BACKENDS contain anything?'
- )
- return backends
-
- def authenticate(**credentials):
- """
- If the given credentials are valid, return a User object.
- """
- for backend, backend_path in _get_backends(return_tuples=True):
- try:
- inspect.getcallargs(backend.authenticate, **credentials)
- except TypeError:
- # This backend doesn't accept these credentials as arguments. Try the next one.
- continue
-
- try:
- user = backend.authenticate(**credentials)
- except PermissionDenied:
- # This backend says to stop in our tracks - this user should not be allowed in at all.
- return None
- if user is None:
- continue
- # Annotate the user object with the path of the backend.
- user.backend = backend_path
- return user
-
- # The credentials supplied are invalid to all backends, fire signal
- user_login_failed.send(sender=__name__,
- credentials=_clean_credentials(credentials))
- step3 django.contrib.auth.login方法
-
- 1. request.user = user
- 2. 触发user_logged_in singnal
-
- def login(request, user):
- """
- Persist a user id and a backend in the request. This way a user doesn't
- have to reauthenticate on every request. Note that data set during
- the anonymous session is retained when the user logs in.
- """
- session_auth_hash = ''
- if user is None:
- user = request.user
- if hasattr(user, 'get_session_auth_hash'):
- session_auth_hash = user.get_session_auth_hash()
-
- if SESSION_KEY in request.session:
- if _get_user_session_key(request) != user.pk or (
- session_auth_hash and
- request.session.get(HASH_SESSION_KEY) != session_auth_hash):
- # To avoid reusing another user's session, create a new, empty
- # session if the existing session corresponds to a different
- # authenticated user.
- request.session.flush()
- else:
- request.session.cycle_key()
- request.session[SESSION_KEY] = user._meta.pk.value_to_string(user)
- request.session[BACKEND_SESSION_KEY] = user.backend
- request.session[HASH_SESSION_KEY] = session_auth_hash
- if hasattr(request, 'user'):
- request.user = user
- rotate_token(request)
- user_logged_in.send(sender=user.__class__, request=request, user=user)
- ```
-
- ## logout 注销
-
- ```
- @deprecate_current_app
- def logout(request, next_page=None,
- template_name='registration/logged_out.html',
- redirect_field_name=REDIRECT_FIELD_NAME,
- extra_context=None):
- """
- Logs out the user and displays 'You are logged out' message.
- """
- auth_logout(request)
-
- if next_page is not None:
- next_page = resolve_url(next_page)
-
- if (redirect_field_name in request.POST or
- redirect_field_name in request.GET):
- next_page = request.POST.get(redirect_field_name,
- request.GET.get(redirect_field_name))
- # Security check -- don't allow redirection to a different host.
- if not is_safe_url(url=next_page, host=request.get_host()):
- next_page = request.path
-
- if next_page:
- # Redirect to this page until the session has been cleared.
- return HttpResponseRedirect(next_page)
-
- current_site = get_current_site(request)
- context = {
- 'site': current_site,
- 'site_name': current_site.name,
- 'title': _('Logged out')
- }
- if extra_context is not None:
- context.update(extra_context)
-
- return TemplateResponse(request, template_name, context)
实现了登出用户,并跳转到指定(next_page或者request.GET, request.POST中携带的redicted_field_name)页面功能
- 登出用户 django.contrib.auth.logout
-
- 1. 出发user_logged_out singnal
- 2. request.session.flush()
- 3. request.user = AnonymousUser() if hasattr(request, 'user')
-
- def logout(request):
- """
- Removes the authenticated user's ID from the request and flushes their
- session data.
- """
- # Dispatch the signal before the user is logged out so the receivers have a
- # chance to find out *who* logged out.
- user = getattr(request, 'user', None)
- if hasattr(user, 'is_authenticated') and not user.is_authenticated():
- user = None
- user_logged_out.send(sender=user.__class__, request=request, user=user)
-
- # remember language choice saved to session
- language = request.session.get(LANGUAGE_SESSION_KEY)
-
- request.session.flush()
-
- if language is not None:
- request.session[LANGUAGE_SESSION_KEY] = language
-
- if hasattr(request, 'user'):
- from django.contrib.auth.models import AnonymousUser
- request.user = AnonymousUser()