在視圖層中,基於函數的視圖叫FBV(function base views),基於類的視圖叫CBV(class base views)。當須要用到裝飾器時,例如以前的基於Cookie與基於Session的登陸驗證,給FBV加裝飾器很簡單,一個@語法糖就OK了,那麼給CBV加裝飾器呢,難道直接跟單例實現的方法之一同樣,類上面一個@語法糖就好了嗎。其實給CBV加裝飾器有三種方法。css
須要先導入模塊:html
from django.utils.decorators import method_decorator
裝飾類時,語法爲:@method_decorator(裝飾器函數名, name=‘類內須要裝飾的方法名’),jquery
直接裝飾類內的方法時,直接@method_decorator(裝飾器函數名)便可。咱們這裏不用原生的裝飾器是由於擴展性差,由於類方法有一個self參數,若是用原生的裝飾器就須要加一個參數,但是加了以後裝飾器又無法裝飾普通的函數了。git
# @method_decorator(login_auth,name='get') # 第二種 name參數必須指定 class MyHome(View): @method_decorator(login_auth) # 第三種 get和post都會被裝飾 def dispatch(self, request, *args, **kwargs): super().dispatch(request,*args,**kwargs) # @method_decorator(login_auth) # 第一種 def get(self,request): return HttpResponse('get') def post(self,request): return HttpResponse('post')
看中間件以前,先看一下Django生命週期:ajax
中間件是一個用來處理Django的請求和響應的框架級別的鉤子。它是一個輕量、低級別的插件系統,用於在全局範圍內改變Django的輸入和輸出。每一箇中間件組件都負責作一些特定的功能。django
可是因爲其影響的是全局,因此須要謹慎使用,使用不當會影響性能。bootstrap
說的直白一點中間件是幫助咱們在視圖函數執行以前和執行以後均可以作一些額外的操做,它本質上就是一個自定義類,類中定義了幾個方法,Django框架會在請求的特定的時間去執行這些方法。後端
中間件就像是Django的門戶,數據在進入與離開時都要通過中間件。瀏覽器
Django默認有七種中間件,能夠在settings.py中查看,每一個都有各自的功能。cookie
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', ]
有興趣能夠看一下各中間件的源碼(注意:以該種方式看完源碼後要運行Django項目,記得把它們給註釋或者刪除了,否則會報Apps aren't loaded yet.等錯誤):
import re from django.conf import settings from django.http import HttpResponsePermanentRedirect from django.utils.deprecation import MiddlewareMixin class SecurityMiddleware(MiddlewareMixin): def __init__(self, get_response=None): self.sts_seconds = settings.SECURE_HSTS_SECONDS self.sts_include_subdomains = settings.SECURE_HSTS_INCLUDE_SUBDOMAINS self.sts_preload = settings.SECURE_HSTS_PRELOAD self.content_type_nosniff = settings.SECURE_CONTENT_TYPE_NOSNIFF self.xss_filter = settings.SECURE_BROWSER_XSS_FILTER self.redirect = settings.SECURE_SSL_REDIRECT self.redirect_host = settings.SECURE_SSL_HOST self.redirect_exempt = [re.compile(r) for r in settings.SECURE_REDIRECT_EXEMPT] self.get_response = get_response def process_request(self, request): path = request.path.lstrip("/") if (self.redirect and not request.is_secure() and not any(pattern.search(path) for pattern in self.redirect_exempt)): host = self.redirect_host or request.get_host() return HttpResponsePermanentRedirect( "https://%s%s" % (host, request.get_full_path()) ) def process_response(self, request, response): if (self.sts_seconds and request.is_secure() and 'strict-transport-security' not in response): sts_header = "max-age=%s" % self.sts_seconds if self.sts_include_subdomains: sts_header = sts_header + "; includeSubDomains" if self.sts_preload: sts_header = sts_header + "; preload" response["strict-transport-security"] = sts_header if self.content_type_nosniff and 'x-content-type-options' not in response: response["x-content-type-options"] = "nosniff" if self.xss_filter and 'x-xss-protection' not in response: response["x-xss-protection"] = "1; mode=block" return response
import time from importlib import import_module from django.conf import settings from django.contrib.sessions.backends.base import UpdateError from django.core.exceptions import SuspiciousOperation from django.utils.cache import patch_vary_headers from django.utils.deprecation import MiddlewareMixin from django.utils.http import cookie_date class SessionMiddleware(MiddlewareMixin): def __init__(self, get_response=None): self.get_response = get_response engine = import_module(settings.SESSION_ENGINE) self.SessionStore = engine.SessionStore def process_request(self, request): session_key = request.COOKIES.get(settings.SESSION_COOKIE_NAME) request.session = self.SessionStore(session_key) def process_response(self, request, response): """ If request.session was modified, or if the configuration is to save the session every time, save the changes and set a session cookie or delete the session cookie if the session has been emptied. """ try: accessed = request.session.accessed modified = request.session.modified empty = request.session.is_empty() except AttributeError: pass else: # First check if we need to delete this cookie. # The session should be deleted only if the session is entirely empty if settings.SESSION_COOKIE_NAME in request.COOKIES and empty: response.delete_cookie( settings.SESSION_COOKIE_NAME, path=settings.SESSION_COOKIE_PATH, domain=settings.SESSION_COOKIE_DOMAIN, ) else: if accessed: patch_vary_headers(response, ('Cookie',)) if (modified or settings.SESSION_SAVE_EVERY_REQUEST) and not empty: if request.session.get_expire_at_browser_close(): max_age = None expires = None else: max_age = request.session.get_expiry_age() expires_time = time.time() + max_age expires = cookie_date(expires_time) # Save the session data and refresh the client cookie. # Skip session save for 500 responses, refs #3881. if response.status_code != 500: try: request.session.save() except UpdateError: raise SuspiciousOperation( "The request's session was deleted before the " "request completed. The user may have logged " "out in a concurrent request, for example." ) response.set_cookie( settings.SESSION_COOKIE_NAME, request.session.session_key, max_age=max_age, expires=expires, domain=settings.SESSION_COOKIE_DOMAIN, path=settings.SESSION_COOKIE_PATH, secure=settings.SESSION_COOKIE_SECURE or None, httponly=settings.SESSION_COOKIE_HTTPONLY or None, ) return response
import re import warnings from django import http from django.conf import settings from django.core.exceptions import PermissionDenied from django.core.mail import mail_managers from django.urls import is_valid_path from django.utils.cache import ( cc_delim_re, get_conditional_response, set_response_etag, ) from django.utils.deprecation import MiddlewareMixin, RemovedInDjango21Warning from django.utils.encoding import force_text from django.utils.six.moves.urllib.parse import urlparse class CommonMiddleware(MiddlewareMixin): """ "Common" middleware for taking care of some basic operations: - Forbids access to User-Agents in settings.DISALLOWED_USER_AGENTS - URL rewriting: Based on the APPEND_SLASH and PREPEND_WWW settings, this middleware appends missing slashes and/or prepends missing "www."s. - If APPEND_SLASH is set and the initial URL doesn't end with a slash, and it is not found in urlpatterns, a new URL is formed by appending a slash at the end. If this new URL is found in urlpatterns, then an HTTP-redirect is returned to this new URL; otherwise the initial URL is processed as usual. This behavior can be customized by subclassing CommonMiddleware and overriding the response_redirect_class attribute. - ETags: If the USE_ETAGS setting is set, ETags will be calculated from the entire page content and Not Modified responses will be returned appropriately. USE_ETAGS is deprecated in favor of ConditionalGetMiddleware. """ response_redirect_class = http.HttpResponsePermanentRedirect def process_request(self, request): """ Check for denied User-Agents and rewrite the URL based on settings.APPEND_SLASH and settings.PREPEND_WWW """ # Check for denied User-Agents if 'HTTP_USER_AGENT' in request.META: for user_agent_regex in settings.DISALLOWED_USER_AGENTS: if user_agent_regex.search(request.META['HTTP_USER_AGENT']): raise PermissionDenied('Forbidden user agent') # Check for a redirect based on settings.PREPEND_WWW host = request.get_host() must_prepend = settings.PREPEND_WWW and host and not host.startswith('www.') redirect_url = ('%s://www.%s' % (request.scheme, host)) if must_prepend else '' # Check if a slash should be appended if self.should_redirect_with_slash(request): path = self.get_full_path_with_slash(request) else: path = request.get_full_path() # Return a redirect if necessary if redirect_url or path != request.get_full_path(): redirect_url += path return self.response_redirect_class(redirect_url) def should_redirect_with_slash(self, request): """ Return True if settings.APPEND_SLASH is True and appending a slash to the request path turns an invalid path into a valid one. """ if settings.APPEND_SLASH and not request.path_info.endswith('/'): urlconf = getattr(request, 'urlconf', None) return ( not is_valid_path(request.path_info, urlconf) and is_valid_path('%s/' % request.path_info, urlconf) ) return False def get_full_path_with_slash(self, request): """ Return the full path of the request with a trailing slash appended. Raise a RuntimeError if settings.DEBUG is True and request.method is POST, PUT, or PATCH. """ new_path = request.get_full_path(force_append_slash=True) if settings.DEBUG and request.method in ('POST', 'PUT', 'PATCH'): raise RuntimeError( "You called this URL via %(method)s, but the URL doesn't end " "in a slash and you have APPEND_SLASH set. Django can't " "redirect to the slash URL while maintaining %(method)s data. " "Change your form to point to %(url)s (note the trailing " "slash), or set APPEND_SLASH=False in your Django settings." % { 'method': request.method, 'url': request.get_host() + new_path, } ) return new_path def process_response(self, request, response): """ Calculate the ETag, if needed. When the status code of the response is 404, it may redirect to a path with an appended slash if should_redirect_with_slash() returns True. """ # If the given URL is "Not Found", then check if we should redirect to # a path with a slash appended. if response.status_code == 404: if self.should_redirect_with_slash(request): return self.response_redirect_class(self.get_full_path_with_slash(request)) if settings.USE_ETAGS and self.needs_etag(response): warnings.warn( "The USE_ETAGS setting is deprecated in favor of " "ConditionalGetMiddleware which sets the ETag regardless of " "the setting. CommonMiddleware won't do ETag processing in " "Django 2.1.", RemovedInDjango21Warning ) if not response.has_header('ETag'): set_response_etag(response) if response.has_header('ETag'): return get_conditional_response( request, etag=response['ETag'], response=response, ) # Add the Content-Length header to non-streaming responses if not # already set. if not response.streaming and not response.has_header('Content-Length'): response['Content-Length'] = str(len(response.content)) return response def needs_etag(self, response): """ Return True if an ETag header should be added to response. """ cache_control_headers = cc_delim_re.split(response.get('Cache-Control', '')) return all(header.lower() != 'no-store' for header in cache_control_headers) class BrokenLinkEmailsMiddleware(MiddlewareMixin): def process_response(self, request, response): """ Send broken link emails for relevant 404 NOT FOUND responses. """ if response.status_code == 404 and not settings.DEBUG: domain = request.get_host() path = request.get_full_path() referer = force_text(request.META.get('HTTP_REFERER', ''), errors='replace') if not self.is_ignorable_request(request, path, domain, referer): ua = force_text(request.META.get('HTTP_USER_AGENT', '<none>'), errors='replace') ip = request.META.get('REMOTE_ADDR', '<none>') mail_managers( "Broken %slink on %s" % ( ('INTERNAL ' if self.is_internal_request(domain, referer) else ''), domain ), "Referrer: %s\nRequested URL: %s\nUser agent: %s\n" "IP address: %s\n" % (referer, path, ua, ip), fail_silently=True) return response def is_internal_request(self, domain, referer): """ Returns True if the referring URL is the same domain as the current request. """ # Different subdomains are treated as different domains. return bool(re.match("^https?://%s/" % re.escape(domain), referer)) def is_ignorable_request(self, request, uri, domain, referer): """ Return True if the given request *shouldn't* notify the site managers according to project settings or in situations outlined by the inline comments. """ # The referer is empty. if not referer: return True # APPEND_SLASH is enabled and the referer is equal to the current URL # without a trailing slash indicating an internal redirect. if settings.APPEND_SLASH and uri.endswith('/') and referer == uri[:-1]: return True # A '?' in referer is identified as a search engine source. if not self.is_internal_request(domain, referer) and '?' in referer: return True # The referer is equal to the current URL, ignoring the scheme (assumed # to be a poorly implemented bot). parsed_referer = urlparse(referer) if parsed_referer.netloc in ['', domain] and parsed_referer.path == uri: return True return any(pattern.search(uri) for pattern in settings.IGNORABLE_404_URLS)
""" Cross Site Request Forgery Middleware. This module provides a middleware that implements protection against request forgeries from other sites. """ from __future__ import unicode_literals import logging import re import string from django.conf import settings from django.core.exceptions import ImproperlyConfigured from django.urls import get_callable from django.utils.cache import patch_vary_headers from django.utils.crypto import constant_time_compare, get_random_string from django.utils.deprecation import MiddlewareMixin from django.utils.encoding import force_text from django.utils.http import is_same_domain from django.utils.six.moves import zip from django.utils.six.moves.urllib.parse import urlparse logger = logging.getLogger('django.security.csrf') REASON_NO_REFERER = "Referer checking failed - no Referer." REASON_BAD_REFERER = "Referer checking failed - %s does not match any trusted origins." REASON_NO_CSRF_COOKIE = "CSRF cookie not set." REASON_BAD_TOKEN = "CSRF token missing or incorrect." REASON_MALFORMED_REFERER = "Referer checking failed - Referer is malformed." REASON_INSECURE_REFERER = "Referer checking failed - Referer is insecure while host is secure." CSRF_SECRET_LENGTH = 32 CSRF_TOKEN_LENGTH = 2 * CSRF_SECRET_LENGTH CSRF_ALLOWED_CHARS = string.ascii_letters + string.digits CSRF_SESSION_KEY = '_csrftoken' def _get_failure_view(): """ Returns the view to be used for CSRF rejections """ return get_callable(settings.CSRF_FAILURE_VIEW) def _get_new_csrf_string(): return get_random_string(CSRF_SECRET_LENGTH, allowed_chars=CSRF_ALLOWED_CHARS) def _salt_cipher_secret(secret): """ Given a secret (assumed to be a string of CSRF_ALLOWED_CHARS), generate a token by adding a salt and using it to encrypt the secret. """ salt = _get_new_csrf_string() chars = CSRF_ALLOWED_CHARS pairs = zip((chars.index(x) for x in secret), (chars.index(x) for x in salt)) cipher = ''.join(chars[(x + y) % len(chars)] for x, y in pairs) return salt + cipher def _unsalt_cipher_token(token): """ Given a token (assumed to be a string of CSRF_ALLOWED_CHARS, of length CSRF_TOKEN_LENGTH, and that its first half is a salt), use it to decrypt the second half to produce the original secret. """ salt = token[:CSRF_SECRET_LENGTH] token = token[CSRF_SECRET_LENGTH:] chars = CSRF_ALLOWED_CHARS pairs = zip((chars.index(x) for x in token), (chars.index(x) for x in salt)) secret = ''.join(chars[x - y] for x, y in pairs) # Note negative values are ok return secret def _get_new_csrf_token(): return _salt_cipher_secret(_get_new_csrf_string()) def get_token(request): """ Returns the CSRF token required for a POST form. The token is an alphanumeric value. A new token is created if one is not already set. A side effect of calling this function is to make the csrf_protect decorator and the CsrfViewMiddleware add a CSRF cookie and a 'Vary: Cookie' header to the outgoing response. For this reason, you may need to use this function lazily, as is done by the csrf context processor. """ if "CSRF_COOKIE" not in request.META: csrf_secret = _get_new_csrf_string() request.META["CSRF_COOKIE"] = _salt_cipher_secret(csrf_secret) else: csrf_secret = _unsalt_cipher_token(request.META["CSRF_COOKIE"]) request.META["CSRF_COOKIE_USED"] = True return _salt_cipher_secret(csrf_secret) def rotate_token(request): """ Changes the CSRF token in use for a request - should be done on login for security purposes. """ request.META.update({ "CSRF_COOKIE_USED": True, "CSRF_COOKIE": _get_new_csrf_token(), }) request.csrf_cookie_needs_reset = True def _sanitize_token(token): # Allow only ASCII alphanumerics if re.search('[^a-zA-Z0-9]', force_text(token)): return _get_new_csrf_token() elif len(token) == CSRF_TOKEN_LENGTH: return token elif len(token) == CSRF_SECRET_LENGTH: # Older Django versions set cookies to values of CSRF_SECRET_LENGTH # alphanumeric characters. For backwards compatibility, accept # such values as unsalted secrets. # It's easier to salt here and be consistent later, rather than add # different code paths in the checks, although that might be a tad more # efficient. return _salt_cipher_secret(token) return _get_new_csrf_token() def _compare_salted_tokens(request_csrf_token, csrf_token): # Assume both arguments are sanitized -- that is, strings of # length CSRF_TOKEN_LENGTH, all CSRF_ALLOWED_CHARS. return constant_time_compare( _unsalt_cipher_token(request_csrf_token), _unsalt_cipher_token(csrf_token), ) class CsrfViewMiddleware(MiddlewareMixin): """ Middleware that requires a present and correct csrfmiddlewaretoken for POST requests that have a CSRF cookie, and sets an outgoing CSRF cookie. This middleware should be used in conjunction with the csrf_token template tag. """ # The _accept and _reject methods currently only exist for the sake of the # requires_csrf_token decorator. def _accept(self, request): # Avoid checking the request twice by adding a custom attribute to # request. This will be relevant when both decorator and middleware # are used. request.csrf_processing_done = True return None def _reject(self, request, reason): logger.warning( 'Forbidden (%s): %s', reason, request.path, extra={ 'status_code': 403, 'request': request, } ) return _get_failure_view()(request, reason=reason) def _get_token(self, request): if settings.CSRF_USE_SESSIONS: try: return request.session.get(CSRF_SESSION_KEY) except AttributeError: raise ImproperlyConfigured( 'CSRF_USE_SESSIONS is enabled, but request.session is not ' 'set. SessionMiddleware must appear before CsrfViewMiddleware ' 'in MIDDLEWARE%s.' % ('_CLASSES' if settings.MIDDLEWARE is None else '') ) else: try: cookie_token = request.COOKIES[settings.CSRF_COOKIE_NAME] except KeyError: return None csrf_token = _sanitize_token(cookie_token) if csrf_token != cookie_token: # Cookie token needed to be replaced; # the cookie needs to be reset. request.csrf_cookie_needs_reset = True return csrf_token def _set_token(self, request, response): if settings.CSRF_USE_SESSIONS: request.session[CSRF_SESSION_KEY] = request.META['CSRF_COOKIE'] else: response.set_cookie( settings.CSRF_COOKIE_NAME, request.META['CSRF_COOKIE'], max_age=settings.CSRF_COOKIE_AGE, domain=settings.CSRF_COOKIE_DOMAIN, path=settings.CSRF_COOKIE_PATH, secure=settings.CSRF_COOKIE_SECURE, httponly=settings.CSRF_COOKIE_HTTPONLY, ) # Set the Vary header since content varies with the CSRF cookie. patch_vary_headers(response, ('Cookie',)) def process_request(self, request): csrf_token = self._get_token(request) if csrf_token is not None: # Use same token next time. request.META['CSRF_COOKIE'] = csrf_token def process_view(self, request, callback, callback_args, callback_kwargs): if getattr(request, 'csrf_processing_done', False): return None # Wait until request.META["CSRF_COOKIE"] has been manipulated before # bailing out, so that get_token still works if getattr(callback, 'csrf_exempt', False): return None # Assume that anything not defined as 'safe' by RFC7231 needs protection if request.method not in ('GET', 'HEAD', 'OPTIONS', 'TRACE'): if getattr(request, '_dont_enforce_csrf_checks', False): # Mechanism to turn off CSRF checks for test suite. # It comes after the creation of CSRF cookies, so that # everything else continues to work exactly the same # (e.g. cookies are sent, etc.), but before any # branches that call reject(). return self._accept(request) if request.is_secure(): # Suppose user visits http://example.com/ # An active network attacker (man-in-the-middle, MITM) sends a # POST form that targets https://example.com/detonate-bomb/ and # submits it via JavaScript. # # The attacker will need to provide a CSRF cookie and token, but # that's no problem for a MITM and the session-independent # secret we're using. So the MITM can circumvent the CSRF # protection. This is true for any HTTP connection, but anyone # using HTTPS expects better! For this reason, for # https://example.com/ we need additional protection that treats # http://example.com/ as completely untrusted. Under HTTPS, # Barth et al. found that the Referer header is missing for # same-domain requests in only about 0.2% of cases or less, so # we can use strict Referer checking. referer = force_text( request.META.get('HTTP_REFERER'), strings_only=True, errors='replace' ) if referer is None: return self._reject(request, REASON_NO_REFERER) referer = urlparse(referer) # Make sure we have a valid URL for Referer. if '' in (referer.scheme, referer.netloc): return self._reject(request, REASON_MALFORMED_REFERER) # Ensure that our Referer is also secure. if referer.scheme != 'https': return self._reject(request, REASON_INSECURE_REFERER) # If there isn't a CSRF_COOKIE_DOMAIN, require an exact match # match on host:port. If not, obey the cookie rules (or those # for the session cookie, if CSRF_USE_SESSIONS). good_referer = ( settings.SESSION_COOKIE_DOMAIN if settings.CSRF_USE_SESSIONS else settings.CSRF_COOKIE_DOMAIN ) if good_referer is not None: server_port = request.get_port() if server_port not in ('443', '80'): good_referer = '%s:%s' % (good_referer, server_port) else: # request.get_host() includes the port. good_referer = request.get_host() # Here we generate a list of all acceptable HTTP referers, # including the current host since that has been validated # upstream. good_hosts = list(settings.CSRF_TRUSTED_ORIGINS) good_hosts.append(good_referer) if not any(is_same_domain(referer.netloc, host) for host in good_hosts): reason = REASON_BAD_REFERER % referer.geturl() return self._reject(request, reason) csrf_token = request.META.get('CSRF_COOKIE') if csrf_token is None: # No CSRF cookie. For POST requests, we insist on a CSRF cookie, # and in this way we can avoid all CSRF attacks, including login # CSRF. return self._reject(request, REASON_NO_CSRF_COOKIE) # Check non-cookie token for match. request_csrf_token = "" if request.method == "POST": try: request_csrf_token = request.POST.get('csrfmiddlewaretoken', '') except IOError: # Handle a broken connection before we've completed reading # the POST data. process_view shouldn't raise any # exceptions, so we'll ignore and serve the user a 403 # (assuming they're still listening, which they probably # aren't because of the error). pass if request_csrf_token == "": # Fall back to X-CSRFToken, to make things easier for AJAX, # and possible for PUT/DELETE. request_csrf_token = request.META.get(settings.CSRF_HEADER_NAME, '') request_csrf_token = _sanitize_token(request_csrf_token) if not _compare_salted_tokens(request_csrf_token, csrf_token): return self._reject(request, REASON_BAD_TOKEN) return self._accept(request) def process_response(self, request, response): if not getattr(request, 'csrf_cookie_needs_reset', False): if getattr(response, 'csrf_cookie_set', False): return response if not request.META.get("CSRF_COOKIE_USED", False): return response # Set the CSRF cookie even if it's already set, so we renew # the expiry timer. self._set_token(request, response) response.csrf_cookie_set = True return response
from django.conf import settings from django.contrib import auth from django.contrib.auth import load_backend from django.contrib.auth.backends import RemoteUserBackend from django.core.exceptions import ImproperlyConfigured from django.utils.deprecation import MiddlewareMixin from django.utils.functional import SimpleLazyObject def get_user(request): if not hasattr(request, '_cached_user'): request._cached_user = auth.get_user(request) return request._cached_user class AuthenticationMiddleware(MiddlewareMixin): def process_request(self, request): assert hasattr(request, 'session'), ( "The Django authentication middleware requires session middleware " "to be installed. Edit your MIDDLEWARE%s setting to insert " "'django.contrib.sessions.middleware.SessionMiddleware' before " "'django.contrib.auth.middleware.AuthenticationMiddleware'." ) % ("_CLASSES" if settings.MIDDLEWARE is None else "") request.user = SimpleLazyObject(lambda: get_user(request)) class SessionAuthenticationMiddleware(MiddlewareMixin): """ Formerly, a middleware for invalidating a user's sessions that don't correspond to the user's current session authentication hash. However, it caused the "Vary: Cookie" header on all responses. It's now a shim to allow a single settings file to more easily support multiple versions of Django. Will be RemovedInDjango20Warning. """ def process_request(self, request): pass class RemoteUserMiddleware(MiddlewareMixin): """ Middleware for utilizing Web-server-provided authentication. If request.user is not authenticated, then this middleware attempts to authenticate the username passed in the ``REMOTE_USER`` request header. If authentication is successful, the user is automatically logged in to persist the user in the session. The header used is configurable and defaults to ``REMOTE_USER``. Subclass this class and change the ``header`` attribute if you need to use a different header. """ # Name of request header to grab username from. This will be the key as # used in the request.META dictionary, i.e. the normalization of headers to # all uppercase and the addition of "HTTP_" prefix apply. header = "REMOTE_USER" force_logout_if_no_header = True def process_request(self, request): # AuthenticationMiddleware is required so that request.user exists. if not hasattr(request, 'user'): raise ImproperlyConfigured( "The Django remote user auth middleware requires the" " authentication middleware to be installed. Edit your" " MIDDLEWARE setting to insert" " 'django.contrib.auth.middleware.AuthenticationMiddleware'" " before the RemoteUserMiddleware class.") try: username = request.META[self.header] except KeyError: # If specified header doesn't exist then remove any existing # authenticated remote-user, or return (leaving request.user set to # AnonymousUser by the AuthenticationMiddleware). if self.force_logout_if_no_header and request.user.is_authenticated: self._remove_invalid_user(request) return # If the user is already authenticated and that user is the user we are # getting passed in the headers, then the correct user is already # persisted in the session and we don't need to continue. if request.user.is_authenticated: if request.user.get_username() == self.clean_username(username, request): return else: # An authenticated user is associated with the request, but # it does not match the authorized user in the header. self._remove_invalid_user(request) # We are seeing this user for the first time in this session, attempt # to authenticate the user. user = auth.authenticate(request, remote_user=username) if user: # User is valid. Set request.user and persist user in the session # by logging the user in. request.user = user auth.login(request, user) def clean_username(self, username, request): """ Allows the backend to clean the username, if the backend defines a clean_username method. """ backend_str = request.session[auth.BACKEND_SESSION_KEY] backend = auth.load_backend(backend_str) try: username = backend.clean_username(username) except AttributeError: # Backend has no clean_username method. pass return username def _remove_invalid_user(self, request): """ Removes the current authenticated user in the request which is invalid but only if the user is authenticated via the RemoteUserBackend. """ try: stored_backend = load_backend(request.session.get(auth.BACKEND_SESSION_KEY, '')) except ImportError: # backend failed to load auth.logout(request) else: if isinstance(stored_backend, RemoteUserBackend): auth.logout(request) class PersistentRemoteUserMiddleware(RemoteUserMiddleware): """ Middleware for Web-server provided authentication on logon pages. Like RemoteUserMiddleware but keeps the user authenticated even if the header (``REMOTE_USER``) is not found in the request. Useful for setups when the external authentication via ``REMOTE_USER`` is only expected to happen on some "logon" URL and the rest of the application wants to use Django's authentication mechanism. """ force_logout_if_no_header = False
from django.conf import settings from django.contrib.messages.storage import default_storage from django.utils.deprecation import MiddlewareMixin class MessageMiddleware(MiddlewareMixin): """ Middleware that handles temporary messages. """ def process_request(self, request): request._messages = default_storage(request) def process_response(self, request, response): """ Updates the storage backend (i.e., saves the messages). If not all messages could not be stored and ``DEBUG`` is ``True``, a ``ValueError`` is raised. """ # A higher middleware layer may return a request which does not contain # messages storage, so make no assumption that it will be there. if hasattr(request, '_messages'): unstored_messages = request._messages.update(response) if unstored_messages and settings.DEBUG: raise ValueError('Not all temporary messages could be stored.') return response
""" Clickjacking Protection Middleware. This module provides a middleware that implements protection against a malicious site loading resources from your site in a hidden frame. """ from django.conf import settings from django.utils.deprecation import MiddlewareMixin class XFrameOptionsMiddleware(MiddlewareMixin): """ Middleware that sets the X-Frame-Options HTTP header in HTTP responses. Does not set the header if it's already set or if the response contains a xframe_options_exempt value set to True. By default, sets the X-Frame-Options header to 'SAMEORIGIN', meaning the response can only be loaded on a frame within the same site. To prevent the response from being loaded in a frame in any site, set X_FRAME_OPTIONS in your project's Django settings to 'DENY'. Note: older browsers will quietly ignore this header, thus other clickjacking protection techniques should be used if protection in those browsers is required. https://en.wikipedia.org/wiki/Clickjacking#Server_and_client """ def process_response(self, request, response): # Don't set it if it's already in the response if response.get('X-Frame-Options') is not None: return response # Don't set it if they used @xframe_options_exempt if getattr(response, 'xframe_options_exempt', False): return response response['X-Frame-Options'] = self.get_xframe_options_value(request, response) return response def get_xframe_options_value(self, request, response): """ Gets the value to set for the X_FRAME_OPTIONS header. By default this uses the value from the X_FRAME_OPTIONS Django settings. If not found in settings, defaults to 'SAMEORIGIN'. This method can be overridden if needed, allowing it to vary based on the request or response. """ return getattr(settings, 'X_FRAME_OPTIONS', 'SAMEORIGIN').upper()
經過源碼,咱們明確的知道中間件其實就是一個個的類,這就意味着咱們實際上是能夠經過繼承類MiddlewareMixin自定義中間件的。首先咱們來了解一下中間件能夠定義的五個方法:
每一個方法都有對應的觸發條件,接下來咱們自定義兩個中間件來看看(在應用下新建一個文件夾,裏面新建一個py文件,文件夾和文件名字沒有要求,而後裏面寫自定義中間件,以後再settings.py中註冊該中間件便可):
相應視圖函數及執行的結果:
發現process_exception和process_template_response方法沒有觸發,咱們修改一下視圖函數:
接着再修改一下視圖函數:
因而可知(使用多個自定義中間件便可得出結論):
其實中間件能夠爲咱們實現不少事情,好比涉及到全局權限、控制用戶訪問頻率、全局登陸校驗、用戶訪問白名單、黑名單等。
CSRF(Cross-site request forgery)跨站請求僞造,也被稱爲「One Click Attack」或者Session Riding,一般縮寫爲CSRF或者XSRF,是一種對網站的惡意利用。儘管聽起來像跨站腳本(XSS),但它與XSS很是不一樣,XSS利用站點內的信任用戶,而CSRF則經過假裝成受信任用戶的請求來利用受信任的網站。與XSS攻擊相比,CSRF攻擊每每不大流行(所以對其進行防範的資源也至關稀少)和難以防範,因此被認爲比XSS更具危險性。
釣魚網站就是建一個和正常的網站如出一轍的網站,而後用戶在輸入的時候調的也是正常網站的接口去處理,因此用戶的錢會扣掉,可是並無轉給指定的人,其實就是建了一個和正常網站如出一轍的東西,而後偷偷的在轉給目標用戶那裏,偷偷的將input框當前的name去掉,而後用了一個hidden隱藏起來,在隱藏起來的input框中給一個默認的value,具體示例以下:
def transfer(request): if request.method == 'POST': username = request.POST.get('name') money = request.POST.get('money') others = request.POST.get('others') print('%s 給 %s 轉了 %s塊錢'%(username,others,money)) return render(request,'transfer.html')
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script> <link rel="stylesheet" href="/static/bootstrap-3.3.7-dist/css/bootstrap.min.css"> <script src="/static/bootstrap-3.3.7-dist/js/bootstrap.min.js"></script> </head> <body> <h1>正經網站</h1> <form action="" method="post"> <p>name:<input type="text" name="name"></p> <p>money:<input type="text" name="money"></p> <p>others:<input type="text" name="others"></p> <input type="submit"> </form> </body> </html>
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script> <link rel="stylesheet" href="/static/bootstrap-3.3.7-dist/css/bootstrap.min.css"> <script src="/static/bootstrap-3.3.7-dist/js/bootstrap.min.js"></script> </head> <body> <h1>釣魚網站</h1> <form action="http://127.0.0.1:8000/transfer" method="post"> <p>name:<input type="text" name="name"></p> <p>money:<input type="text" name="money"></p> <p>others: <input type="text" > <input type="hidden" name="others" value="mcc" style="display: none" > </p> <input type="submit"> </form> </body> </html>
在此以外,咱們經過form表單和ajax日後端提交數據時都會報大黃頁:
其實csrf中間件就是用來防止跨站請求僞造的,它的原理也是在HTML頁面中隱藏一個Input框,name固定是'csrfmiddlewaretoken',value是動態生成的字符串,每次頁面刷新就會變化,若是csrf拿到的的value跟它想的不同,就會報大黃頁的錯誤,而以前咱們form表單和ajax中都沒有csrf token,因此報錯是必然的。
form表單中增長csrf token的方法:
<form action="" method="post"> {% csrf_token %} <!--就是這一句--> username:<input type="text" name="username"> password:<input type="text" name="password"> <input type="submit"> </form>
隨後就能發現頁面檢查時有csrf token了:
ajax增長csrf token的方法:
<script> $('button').click(function () { $.ajax({ url:'', type:'post', data:{'name': 'hello', 'csrfmiddlewaretoken': $("[name='csrfmiddlewaretoken']").val()}, success:function (data) { console.log(data) } }) }) </script>
ajax增長csrf token的方法二:
<script> $('button').click(function () { $.ajax({ url:'', type:'post', data:{'name': 'hello', 'csrfmiddlewaretoken': '{{ csrf_token }}'}, success:function (data) { console.log(data) } }) }) </script>
第二種方法必定要將{{ csrf_token }}放在 ''裏面,否則會出現後端沒有報403 forbidden,可是你的數據就是沒法提交至後端的狀況。
ajax的該方法其實就是將form表單中的csrf token拿出來放進本身的data。
首先須要導入一共三個裝飾器:
from django.utils.decorators import method_decorator from django.views.decorators.csrf import csrf_exempt,csrf_protect
method_decorator是給類裝飾用的裝飾器,而csrf_exempt是忽略csrf校驗的函數裝飾器,csrf_protect是使用csrf校驗的函數裝飾器。
對於FBV而言:
局部禁用csrf校驗:
@csrf_exempt def index1(request): return HttpResponse('ok')
局部使用csrf校驗:
@csrf_protect def login(request): pass
對於CBV而言:
csrf_protect跟正常的CBV裝飾器同樣,能夠有三種方式(開始CBV裝飾器提到的三種)。
而csrf_exempt只有兩種方式:
@method_decorator(csrf_exempt,name='dispatch') # 第一種 class Index3(View): # @method_decorator(csrf_exempt) # 第二種 def dispatch(self, request, *args, **kwargs): super().dispatch(request,*args,**kwargs) #其實都是給dispatch加
咱們在開發一個網站的時候,無可避免的須要設計實現網站的用戶系統。此時咱們須要實現包括用戶註冊、用戶登陸、用戶認證、註銷、修改密碼等功能,這還真是個麻煩的事情呢。
Django做爲一個完美主義者的終極框架,固然也會想到用戶的這些痛點。它內置了強大的用戶認證系統--auth,它默認使用 auth_user 表來存儲用戶數據。
首先也要先導入auth模塊:
from django.contrib import auth
管理員帳號能夠登陸管理系統,即Django urls.py自帶的第一個路由:
提供了用戶認證功能,即驗證用戶名以及密碼是否正確,至少須要username 、password兩個關鍵字參數。
若是認證成功(用戶名和密碼正確有效),便會返回一個 User 對象。
authenticate()會在該 User 對象上設置一個屬性來標識後端已經認證了該用戶,且該信息在後續的登陸過程當中是須要的。
auth.authenticate(request,username=username,password=password)
該函數接受一個HttpRequest對象,以及一個通過認證的User對象。
該函數實現一個用戶登陸的功能。它本質上會在後端爲該用戶生成相關session數據。
from django.contrib.auth import authenticate, login def auth_login(request): if request.method == 'POST': username = request.POST.get('username') password = request.POST.get('password') # models.User.objects.filter(username=username,password=password).first() user_obj = auth.authenticate(request,username=username,password=password) if user_obj: # 記錄用戶狀態 # request.session['name'] = 'jason' auth.login(request,user_obj) # 一旦記錄了,能夠在任意的地方經過request.user獲取到當前登陸對象 return HttpResponse('ok') return render(request,'auth_login.html')
auth.authenticate()和auth.login()的優勢:
# 只要登錄成功執行了auth.login(request,user) # 以後在其餘任意的視圖函數中都經過request.user獲取當前登錄用戶對象 # 當沒有執行auth.login, request.user打印出來的是匿名用戶。將session表數據刪除便可演示該效果 # 如何判斷request.user用戶是否經過auth.login登錄呢?request.user.is_authenticated() # 爲什麼執行auth.login以後,其餘視圖函數中就能夠經過request.user拿到當前登錄對象呢?
想一想django的中間件中有沒有一個叫Auth啥的中間件,它幹了件什麼事,能不能推導一下?
取出session去表裏面查相應的數據,而後放到request.user中,
點進去看一下這個中間件確實如此
該函數接受一個HttpRequest對象,無返回值。
當調用該函數時,當前請求的session信息會所有清除。該用戶即便沒有登陸,使用該函數也不會報錯。
auth.logout(request) # 等價於刪除session數據request.session.flush()
判斷當前用戶是否已經經過驗證
def auth_index(request): print(request.user.is_authenticated()) # 判斷當前用戶是否已經經過驗證 print(request.user, type(request.user)) # 獲取當前登陸用戶對象 return HttpResponse('ok')
auth 提供的一個檢查密碼是否正確的方法,須要提供當前請求用戶的密碼。密碼正確返回True,不然返回False。
def auth_password(request): print(request.user.password) is_res = request.user.check_password('jason123') # 校驗密碼是否一致 if is_res: request.user.set_password('666') # 設置新密碼 request.user.save() # 修改密碼必須save保存 否則無效 return HttpResponse('ok')
auth 提供的一個建立新用戶的方法,須要提供必要參數(username、password)等
from django.contrib.auth.models import User user = User.objects.create_user(username='用戶名',password='密碼',email='郵箱',...)
若是你繼承AbstractUser擴展了auth_user類,假設擴展後的類叫UserInfo,那麼建立用戶時代碼要修改一下:
from app01 import models user = models.UserInfo.objects.create_user(username='用戶名',password='密碼',email='郵箱',...)
auth 提供的一個建立新的超級用戶的方法,須要提供必要參數(username、password)等。
from django.contrib.auth.models import User user = User.objects.create_superuser(username='用戶名',password='密碼',email='郵箱',...)
auth 給咱們提供的一個裝飾器工具,用來快捷的給某個視圖添加登陸校驗。
局部配置:
from django.contrib.auth.decorators import login_required @login_required(login_url='/auth_login/') # 局部配置 def auth_home(request): return HttpResponse('home必須登陸才能訪問')
全局配置:
在setting.py中寫配置:
LOGIN_URL = '/auth_login/' # 寫未登陸時轉向的路由
# 以後都這麼寫便可 @login_required def auth_xxx(request): return HttpResponse('xxx必須登陸才能訪問')
內置的auth_user表字段有限,有時無法知足咱們需求,這時能夠經過繼承AbstractUser類來擴展該表。
第一種方式:一對一關聯表(不推薦)
from django.contrib.auth.model import User class UserDetail(models.Models): phone = models.CharField(max_length=11) user = models.OnoToOneField(to=User)
第二種:面向對象的繼承
from django.contrib.auth.models import User,AbstractUser class UserInfo(AbstractUser): phone = models.CharField(max_length=32) # 須要在配置文件中,指定我再也不使用默認的auth_user表而是使用我本身建立的Userinfo表 AUTH_USER_MODEL = "app名.models裏面對應的模型表名" """ 自定義認證系統默認使用的數據表以後,咱們就能夠像使用默認的auth_user表那樣使用咱們的UserInfo表了。 庫裏面也沒有auth_user表了,原來auth表的操做方法,如今所有用自定義的表都可實現 """
當使用auth組件進行認證登陸以後,只要是須要同後端發post等請求的ajax,就會遇到403Forbidden,而這個403跟中間件csrf是沒有關係的。這個時候咱們須要在ajax攜帶的數據(data)中加上‘csrfmiddlewaretoken’,若是是先後端不分離,那麼能夠採用上述的兩種加csrfmiddlewaretoken的方式;可是ajax是寫在單獨的js文件中,那麼只能採用'csrfmiddlewaretoken': $("[name='csrfmiddlewaretoken']").val()}的方式。加上該方式以後,雖而後端仍是會提示403Forbidden,可是數據是可能正常交互的。
採用auth組件進行登陸驗證時,是須要傳用戶名和密碼的,這是源碼裏面的需求:
但是咱們有手機號與驗證碼就能夠登陸的需求,那就須要重寫該方法,同時還須要在項目中的settings.py文件中配置咱們重寫的類。
這裏是在user APP下新建authenticate.py文件,裏面重寫ModelBackend的authenticate方法:
from django.contrib.auth.backends import ModelBackend from rest_framework.throttling import SimpleRateThrottle from user import models import re class LoginWithPhoneBackend(ModelBackend): def authenticate(self, request, username=None, password=None, **kwargs): # 這裏經過正則校驗是不是手機號的格式 if not re.match(r'^1[3-9]\d{9}$', username): user = models.User.objects.filter(username=username).first() if user.check_password(password) and self.user_can_authenticate(user): return user else: user = models.User.objects.filter(phone=username).first() # 校驗用戶是否處於激活狀態,以is_active字段的值爲依據 if self.user_can_authenticate(user): return user
而後在設置文件中配置:
# auth認證中返回user對象的方法,默認是ModelBackend AUTHENTICATION_BACKENDS = ['user.authenticates.LoginWithPhoneBackend']
這樣便可實現經過手機號也能經過auth.authenticate認證,從而登陸。
關於發送短信能夠看這篇博客,使用的是容聯(註冊後能夠免費發160條短信),接口使用也不會很複雜。點擊前往發送短信的博客