场景说明
web 网站登录认证一般常用的有三种方式:
- session:早期以 web 为主
- token:适用于 web、app
- oauth:微信、QQ登录
目前在 django 中使用 session 认证的方式比较多,因为 django 内置了强大的用户认证系统–auth模块。
下面会结合 session 和 token 两种认证方式做一个登录的示例。
session 登录认证示例
views.py:
def query(request):
if request.method == 'GET':
return render(request, 'login.html')
else:
username = request.POST.get('username')
password = request.POST.get('password')
user_date = {'username': username, 'password': password}
user_obj = authenticate(request, **user_date) # 验证用户名与密码是否正确,可以自定义 backend
if not user_obj:
return HttpResponse('用户名或者密码错误!')
else:
login(request, user_obj) # 登录,将当前用户信息存储在会话中
return redirect('index')
urls.py:
path('login/', views.query, name='login'),
模板:
<h1>登录</h1>
<form action="" method="post">
{% csrf_token %}
<div>
用户名:<input type="text" name="username" id="username">
</div>
<div>
密码:<input type="password" name="password" id="password">
</div>
<div>
<input type="submit" id="btn" value="确认">
</div>
</form>
页面效果:
登录成功之后会在 cookie 中添加一个 session_id:
auth 系统中的 login 方法:
并且将一个已验证的用户附加到当前会话(session)中。
def login(request, user, backend=None):
"""
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: # 如果用户为None则设置为request的user,因为进过了auth的中间间处理所以肯定有user属性
user = request.user
if hasattr(user, 'get_session_auth_hash'): #如果有get_session_auth_hash方法则调用该方法生成hash值
session_auth_hash = user.get_session_auth_hash()
if SESSION_KEY in request.session: # 检查该key是否在会话中存在值
if _get_user_session_key(request) != user.pk or (
session_auth_hash and
not constant_time_compare(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() # 重新加载和生成新的session保持该会话数据
try:
backend = backend or user.backend
except AttributeError:
backends = _get_backends(return_tuples=True)
if len(backends) == 1:
_, backend = backends[0]
else:
raise ValueError(
'You have multiple authentication backends configured and '
'therefore must provide the `backend` argument or set the '
'`backend` attribute on the user.'
)
else:
if not isinstance(backend, str):
raise TypeError('backend must be a dotted import path string (got %r).' % backend)
request.session[SESSION_KEY] = user._meta.pk.value_to_string(user)
request.session[BACKEND_SESSION_KEY] = 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)
在请求中保留用户ID和后端。这样,用户不必在每个请求上都重新进行身份验证。请注意,当用户登录时,匿名会话期间设置的数据将保留。
首先先判断是否有会话冲突,判断该会话是否与其他已经存在的值发生了冲突,如果没有冲突则重新生成一个新的会话并将旧数据保存到新会话中,
然后将 user 的pk转换成字符串放到session中,然后给request设置user属性,此时登录验证的操作就执行完成。
那么上述登录认证做好了,下一次登录时怎么获取用户登录信息呢?
Django是通过django.contrib.auth.middleware.AuthenticationMiddleware中间件来完成的。
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 setting to insert "
"'django.contrib.sessions.middleware.SessionMiddleware' before "
"'django.contrib.auth.middleware.AuthenticationMiddleware'."
)
request.user = SimpleLazyObject(lambda: get_user(request)) # 当前登录用户对象
get_user 核心代码:
def get_user(request):
"""
Return the user model instance associated with the given request session.
If no user is retrieved, return an instance of `AnonymousUser`.
"""
from .models import AnonymousUser
user = None
try:
user_id = _get_user_session_key(request)
backend_path = request.session[BACKEND_SESSION_KEY]
except KeyError:
pass
else:
if backend_path in settings.AUTHENTICATION_BACKENDS:
backend = load_backend(backend_path)
user = backend.get_user(user_id)
# Verify the session
if hasattr(user, 'get_session_auth_hash'):
session_hash = request.session.get(HASH_SESSION_KEY)
session_hash_verified = session_hash and constant_time_compare(
session_hash,
user.get_session_auth_hash()
)
if not session_hash_verified:
if not (
session_hash and
hasattr(user, '_legacy_get_session_auth_hash') and
constant_time_compare(session_hash, user._legacy_get_session_auth_hash())
):
request.session.flush()
user = None
return user or AnonymousUser()
从 session 中获取用户模型实例,如果未检索到用户,则返回AnonymousUser
上述就是 django 的登录认证功能,其中还有很多方法,有兴趣的可以看我关于 auth 章节的讲解。
认证过程大致如下:
1.设置session:
* 在cookie里面加上了一个键值对,session_id:随机字符串
* 将用户的数据进行加密,并保存到Django-session表里面
2.判断session是否合法:
* 从cookie里面拿出session_id:随机字符串
* 去Django-session表里面查询到对应的数据
* 反解加密的用户数据,并获取用户的数据,判断是否合法
基于JWT的Token认证
JSON Web Token(JWT)是一个非常轻巧的规范。这个规范允许我们使用JWT在用户和服务器之间传递安全可靠的信息。
一共分为三部分:
1.header:声明类型,声明加密的算法
2.payload:公共的一些声明:签发者,过期时间,签发时间
3.signature:签证信息,由后端签发,secret是保存在服务器端的,jwt的签发生成也是在服务器端的,secret就是用来进行jwt的签发和jwt的验证
生成 token:
>>> import jwt
>>> encoded = jwt.encode({"some": "payload"}, "secret", algorithm="HS256")
>>> print(encoded)
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzb21lIjoicGF5bG9hZCJ9.Joh1R2dYzkRvDkqv3sygm5YyK8Gi4ShZqbhK2gxcs2U
>>> jwt.decode(encoded, "secret", algorithms=["HS256"])
{'some': 'payload'}
同样的问题,下一次接口登录的时候,怎么获取已经登录的用户信息呢? django 中是有中间件处理,现在使用 token 怎么办,那么也要自定义一个中间件,需要实现的功能也是和 AuthenticationMiddleware 中处理逻辑类似,它是从会话中去拿用户的信息,这里我们需要解码 token,获取用户的信息(生成token的时候可以将用户信息放进去)。
from django.conf import settings
from django.utils.deprecation import MiddlewareMixin
from jose import jwt, ExpiredSignatureError, JWTError
from xx.models import User
class AuthMiddleware(MiddlewareMixin):
def process_request(self, request):
token = request.META.get('HTTP_AUTHORIZATION')
if token:
token = token.replace('Bearer ', '')
try:
jwt_token = jwt.decode(token, settings.SECRET_KEY)
except ExpiredSignatureError:
return False
except JWTError:
return False
user = User.objects.get(id=jwt_token.get('sub'))
request.user = user
return
后续在其他地方也可以使用 request.user
注意点:中间件定义好一定要在 settings.py 中注册
MIDDLEWARE = [
'xx.middleware.auth.AuthMiddleware',
]
参考文献
django 中 auth 模块:https://blog.csdn.net/qq_39253370/article/details/108464419
pyjwt:https://github.com/jpadilla/pyjwt#usage
cookie 和 session:https://blog.csdn.net/qq_39253370/article/details/105462124