本文将详细介绍如何使用 djangorestframework-simplejwt
库为自定义用户模型实现 JWT 认证与授权功能。我们将从配置文件开始,逐步讲解如何将该库集成到 Django 项目中,特别是在使用自定义用户模型的情况下,确保安全、灵活的用户认证系统。
1.myproject/settings.py
设置文件中的配置:
- 安装的应用中添加simplejwt
- 设置默认使用自定义的jwt认证方案
- 设置REFRESH_TOKEN的有效期
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| INSTALLED_APPS = [ 'rest_framework_simplejwt', ]
REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': [ 'myapp.authentication.MyJWTAuthentication', ], }
SIMPLE_JWT = { 'REFRESH_TOKEN_LIFETIME': timedelta(days=30), 'ROTATE_REFRESH_TOKENS': True, }
|
2.myapp/models.py
在用户模型中添加 password
和 is_active
字段,及 check_password
方法。
1 2 3 4 5 6 7
| class Author(models.Model): ... password = models.CharField(max_length=20) is_active = models.BooleanField(default=True)
def check_password(self, password): return password == self.password
|
3.myapp/views.py
我们需要新建一个 LoginView
来提供登录视图,根据前端提供的用户名密码,验证通过后生成 access token和 refresh token,并返回。对于不需要JWT鉴权的接口可以使用 authentication_classes
及 permission_classes
来说明。 另外修改 ArticleList
的 get 方法,使用从 token 中解析出的user对象来获取其所有文章。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| from rest_framework.permissions import AllowAny
from .serializers import LoginSerializer
class LoginView(APIView): authentication_classes = [] permission_classes = [AllowAny] def post(self, request, *args, **kwargs): serializer = LoginSerializer(data=request.data, context={'request': request}) serializer.is_valid(raise_exception=True) refresh = serializer.context.get('refresh') access = serializer.context.get('access') return Response({'code': 200, 'msg': '登录成功', 'refresh': refresh, 'access': access})
class ArticleList(APIView):
def get(self, request, format=None): articles = Article.objects.filter(author=request.user.id) serializer = ArticleSerializer(articles, many=True) return Response(serializer.data)
|
4.myapp/serializers.py
主要通过 LoginSerializer
完成验证用户名密码并生成token的过程。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| from rest_framework.exceptions import ValidationError from rest_framework_simplejwt.tokens import RefreshToken
class LoginSerializer(serializers.ModelSerializer): name = serializers.CharField()
class Meta: model = Author fields = ('name', 'password')
def validate(self, attrs): username = attrs.get('name') password = attrs.get('password')
user = Author.objects.get(name=username) if user and user.check_password(password): refresh = RefreshToken.for_user(user) self.context['refresh'] = str(refresh) self.context['access'] = str(refresh.access_token) else: raise ValidationError({'user': 'login error'})
return attrs class AuthorSerializer(serializers.ModelSerializer): ... password = serializers.CharField(max_length=20, required=True)
|
5.myproject/authentication.py
通过继承 JWTAuthentication
来自定义其他需要JWT鉴权的接口的token解析认证过程。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| from rest_framework.exceptions import AuthenticationFailed from rest_framework_simplejwt.authentication import JWTAuthentication
from .models import Author
class MyJWTAuthentication(JWTAuthentication):
def authenticate(self, request): jwt_value = self.get_raw_token(self.get_header(request)) if not jwt_value: raise AuthenticationFailed('token 字段是必须的')
validated_token = self.get_validated_token(jwt_value) user = Author.objects.get(pk=validated_token['user_id']) return user, jwt_value
|
6.myproject/urls.py
提供获取和刷新token的url地址,这两个url分别对应 MyObtainTokenPairView
和 TokenRefreshView
两个视图。
1 2 3 4 5 6 7 8 9
| from rest_framework_simplejwt.views import TokenRefreshView
from myapp.views import LoginView
urlpatterns = [ ... path('api/v1/login/', LoginView.as_view(), name='token_obtain'), path('api/v1/refresh/', TokenRefreshView.as_view(), name='token_refresh'), ]
|
7.测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| docker-compose up -d --build web
docker exec -it django-tutorial-web-1 /bin/sh python manage.py makemigrations myapp python manage.py migrate
curl -H "Content-Type: application/json" -X POST -d '{"name":"author108", "password":"123456"}' 127.0.0.1:5000/api/v1/login/
{"code":200,"msg":"登录成功","refresh":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbl90eXBlIjoicmVmcmVzaCIsImV4cCI6MTczMzExMDg3MiwiaWF0IjoxNzMwNTE4ODcyLCJqdGkiOiI5NjI1NmM0ZmVjNjY0Y2MzOWQ3Mjc3OGM4YmM0MTZiZCIsInVzZXJfaWQiOjF9.IfzLFiGuqChYMBCAFgjWOHb2fuyRIkhoZ5CvNCJ35BA","access":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNzMwNTE5MTcyLCJpYXQiOjE3MzA1MTg4NzIsImp0aSI6IjkyNDg3MzJlYmI0ODRhYTQ5ODJhMzZmMTFlOGJhZTMwIiwidXNlcl9pZCI6MX0.6W6ZhvGERpwTsh6AHPx5By5eAHDZ5lsPuXA1WOyZ8ws","user_id":null}
curl -H "Content-Type: application/json" -X POST -d '{"refresh":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbl90eXBlIjoicmVmcmVzaCIsImV4cCI6MTczMzExMDg3MiwiaWF0IjoxNzMwNTE4ODcyLCJqdGkiOiI5NjI1NmM0ZmVjNjY0Y2MzOWQ3Mjc3OGM4YmM0MTZiZCIsInVzZXJfaWQiOjF9.IfzLFiGuqChYMBCAFgjWOHb2fuyRIkhoZ5CvNCJ35BA"}' 127.0.0.1:5000/api/v1/refresh/
{"access":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNzMwNTE5NTY0LCJpYXQiOjE3MzA1MTg4NzIsImp0aSI6ImE4YmNiNDQzMjMzZDQwNmM4ZGRkODU3N2UxMjg2ZDI5IiwidXNlcl9pZCI6MX0.nQYz0MuMezoPbemz3hoyuewEOkwM32v8Mw2tK7_O2QE","refresh":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbl90eXBlIjoicmVmcmVzaCIsImV4cCI6MTczMzExMTI2NCwiaWF0IjoxNzMwNTE5MjY0LCJqdGkiOiJiOGVlNDE3NzZjZDM0ZWVlOTlkZGM2YTFjZjQ5YzQxYyIsInVzZXJfaWQiOjF9.t_FBq8Ro8ecxw9-QGMnHSKVkNCvC32TelpMmHrUF7j8"}
curl -H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNzMwNTE5NTY0LCJpYXQiOjE3MzA1MTg4NzIsImp0aSI6ImE4YmNiNDQzMjMzZDQwNmM4ZGRkODU3N2UxMjg2ZDI5IiwidXNlcl9pZCI6MX0.nQYz0MuMezoPbemz3hoyuewEOkwM32v8Mw2tK7_O2QE" -X GET 127.0.0.1:5000/api/v1/articles/
[{"id":1,"title":"mytitle","body":"mybody","create_date":"2024-11-02 11:46:00","author":{"id":1,"name":"author108","mobile":"108","age":0,"birth_date":"2001-01-01","mod_date":"2024-10-31 22:54:11","password":"123456","author_articles":[1],"is_active":true},"article_type":"短篇"}]
|