1. 用户模型创建
通常来说既然用了django了,不会完全舍弃它的用户模型吧,区别就是继承类的深度。
1.1 User
直接用django自带的User模型,如果你能够把需求砍到这种层度,没毛病。
1.2 AbstractUser
这一层帮你创建好了一些字段:
password, last_login, username, first_name, last_name, email, is_staff, is_active, date_joined
其中is_staff是能访问admin的权限。
还有几个基础方法。
其实继承这层一般就可以了,再新建几列诸如手机号,头像之类的,美滋滋。
1.3 AbstractBaseUser
更深一层的继承
只包含
password, last_login
但是封装了password的加密生成,一些状态的判断……
这是能继承的最基础的类了,如果还想更深的话,可能只能重写了
最后别忘了重新指定一下用户模型
AUTH_USER_MODEL = 'account.Account'
2. 用户模型获取
2.1 获取用户
from django.contrib.auth import get_user_model
User = get_user_model()
获取
User.objects.get/filter
创建
User.objects.create(username="xx")
设置密码
user = User.objects.get(xx)
user.set_password("xxxxx")
2.2 分组
from django.contrib.auth.models import Group
Group.objects.create(name="试用用户组")
group = Group.objects.get(name="试用用户组")
组内所有用户:
users = group.user_set.all()
用户加入组:
user = User.objects.get(xx)
user.groups.add(demo_group)
我这里的接口使用了django-restframework,接下来的操作先封装好一些类
from rest_framework.views import APIView
from rest_framework import permissions
from rest_framework_jwt.authentication import JSONWebTokenAuthentication
class BaseAPIView(APIView):
permission_classes = (permissions.AllowAny, )
authentication_classes = (JSONWebTokenAuthentication, )
3. 注册
3.1 表单
目前第一版暂不需要密码,通过手机短信注册。
from rest_framework import serializers
class RegisterSerializer(serializers.Serializer):
mobile_phone = serializers.IntegerField(allow_null=False)
verification_code = serializers.IntegerField()
name = serializers.CharField()
company = serializers.CharField()
3.2 控制层
写一下整体逻辑
class Register(APIView):
serializer_class = RegisterSerializer
def post(self, request, format=None):
serializer = RegisterSerializer(data=request.data)
if not serializer.is_valid():
return render_no_match_v2(serializer.errors)
mobile_phone = serializer.data["mobile_phone"]
...
item = generate_Register_item(mobile_phone, verification_code, name, company)
return render_api_item_v2({"item": item})
3.3 服务层
整体的流程大概如图:
这边我们进行的是途中的api2,api1我没有写在上面,是一个请求短信服务商发送短信通知的接口。
需要注意的地方有一处: 服务器端要保存下来请求的验证码与api2里用户输入的验证码匹配
当然还要考虑 覆盖(比如说用户请求了两次),以及这个存储的 过期时间
综上,感觉用Redis是比较合适的。
不过,Emmmm,人手不足,怕出了问题的时候没人有时间去维护,这里先用django的cache了
cache.set(key, value, timeout=30*60)
注:区别在于cache会随着django的重启而清空
接下来是注册(api2)的service层:
def generate_Register_item(mobile_phone, verification_code, name, company):
vc = cache.get(mobile_phone)
if vc and verification_code != vc:
return "验证码错误"
User = get_user_model()
if User.objects.filter(telephone=mobile_phone):
return "该手机已注册"
user = User.objects.create(telephone=mobile_phone, username=mobile_phone)
user.real_name = name
user.company = company
user.save()
cache.delete(mobile_phone)
return "注册成功"
4. 登录
4.1 表单
同样,没有设置密码,采用短信服务登录
4.2 控制层
基本如上
4.3 服务层
django是定制了login方法的
from django.contrib.auth import login
接受三个参数login(request, user, backend=None)
本质上应该是往session里写入了一个hash值来保存登录状态。
这里需要注意的是登录前是需要验证用户名密码的。
密码在数据库中通常不是明文存储,而是加盐再经过一个不可逆的hash操作存入数据库。
所以这里的验证本质上应该是把用户输入的明文密码再经过这样的加盐加密,然后对比两个字符串是否相同(没深入了解过,个人的理解),不过这里django也有封装的方法
from django.contrib.auth import authenticate
authenticate(username=xx, password=xx)
不过本次项目里因为暂不考虑密码,所以不需要这一步骤,直接短信验证后login
但是会报错,因为正常的步骤是先authenticate,再login,其中authenticate方法会给user加了一个属性backend
随便写个user测试一下得到这个backend值,赋给user
user.backend='django.contrib.auth.backends.ModelBackend'
5. 注销
这个就不写了,跟login一样,django还有一个logout方法
logout(request)
6. JWT
老样子,使用一个工具之前先搞明白它的作用,以及它和其它类似工具的区别
6.1 作用
JWT全称:Json Web Token,一种登录状态的令牌验证机制。
6.2 构成
JWT分为三段,用"."来分隔。
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6Im1hbmJ1ZyIsIm9yaWdfaWF0IjoxNTE2MzQ5ODYwLCJlbWFpbCI6IiIsImV4cCI6MTUxNjQzNjI2MCwidXNlcl9pZCI6N30.FNOGMZ_c6pD7q6ebhjcblPcQYyVelrV5-XJHLcX_ZQU
第一部分:头部(header)
{
'typ': 'JWT',
'alg': 'HS256'
}
第二部分:载荷(payload)
第三部分:签证(signature)
其中JWT要在服务端设置好秘钥,签证时候和base64后的header,payload共同生成signature
6.3 对比,差异
说到验证登录状态一般都会想到Session,我们先来捋一下一般判断登录状态的流程。
- Cookie
理论上来讲,用户信息可以存到Cookie里的,如果无论你还是服务方都觉得这个安全问题无所谓的话 -.-! - Session
应该是主流的方法吧,记得前年刚工作时各种面试都会问到这个。大概就是每次登录时服务端会在数据库里(或者Redis之类的?)存下一条记录,然后把session返回给浏览器,浏览器保存下来,接下来的操作会拿这个session_id向服务器验证。
至于Session可能隐藏的问题一个是搞负载均衡时如果没把Session存到一台服务器上可能会不断重新登录(Emmmmm这个都没考虑到还是别配多服务器了),再一个就是如果流量很大,对存储的服务器压力过大(T.T入行尚浅,还没经历过什么大型的项目,这种情况下可能会把Redis也撘成集群?) - JWT
至于JWT,其实跟Session挺像的,琢磨了好久有什么区别,后来理解大概就是服务端不用存储吧。
6.4 验证流程
- 客户端用用户名和密码请求服务端,成功后返回token
- 客户端存下token,每次请求时放到头部authorization里
- 服务器收到客户端请求时先验证token
6.5 具体实现代码
同样,本次项目采用django-restframework
关于具体的代码,先弄清总共分为三个模块:生成,验证, 刷新
其实这个库可以直接用
from rest_framework_jwt.views import obtain_jwt_token, refresh_jwt_token, verify_jwt_token
如果有什么特殊的需求那么解决办法其实也很简单,比如说一层层依次点开obtain_jwt_token,找一个合适的层次的类继承它。
举个栗子:
现在要为网站设计子域名,比如说产品环境叫dev-data.xxx.xxx,预览版叫dev-preview.xxx.xxx,要为用户设计个权限控制
那么完全可以先一层层往上找
obtain_jwt_token
>>> ObtainJSONWebToken
>>> JSONWebTokenAPIView
停,重写JSONWebTokenAPIView
的post方法,加一层用户组的判断就ok了
嗯,最后注意一点,别忘了以后的每个api里都加上这个token的验证,可以重新定义个基类
from rest_framework.views import APIView
from rest_framework import permissions
from rest_framework_jwt.authentication import JSONWebTokenAuthentication
class BaseAPIView(APIView):
permission_classes = (permissions.AllowAny, )
authentication_classes = (JSONWebTokenAuthentication, )