django开发JWT登录功能流程梳理

2024年05月22日 编程 暂无评论 阅读55 次





django开发JWT登录功能流程梳理

jwt介绍

JWT登录功能,就是返回给用户JWT验证信息做token。后端验证用户信息的时候对信息解密,验证用户是否正确。

jwt分为三部分,header定义加密算法,payload是用户信息,signature是前两部分的base64字符串后再加密。三部分再base64中间用点号连接组成JWT

jwt可以是对称加密也可以是非对称加密(私钥加密,公钥解密),秘钥要保存在服务器端不能泄露

登录流程

验证码表

用户表

 

JWTtoken生成

安装依赖库:
https://github.com/jpadilla/django-rest-framework-jwt
注意上面项目其实已经不再维护,不过现在还可以用。如果更高版本的django可以考虑使用下面的库
https://github.com/jazzband/djangorestframework-simplejwt
目前我这还是用的第一个drfjwt

pip install djangorestframework-jwt

djangorestframework-jwt==1.11.0
PyJWT==1.7.1
配置身份验证
REST_FRAMEWORK = {  
    'DEFAULT_PERMISSION_CLASSES': (  
        'rest_framework.permissions.IsAuthenticated',  
    ),  
    'DEFAULT_AUTHENTICATION_CLASSES': (  
        'rest_framework_jwt.authentication.JSONWebTokenAuthentication',  
        'rest_framework.authentication.SessionAuthentication',  
        'rest_framework.authentication.BasicAuthentication',  
    ),  
}

# jwt的前缀和有效期配置
import datetime  
JWT_AUTH = {  
    'JWT_EXPIRATION_DELTA': datetime.timedelta(days=7),  
    'JWT_AUTH_HEADER_PREFIX': 'JWT',  
    'JWT_SECRET_KEY': SECRET_KEY,  
}

#urls路由配置
# 这个配置是传入用户密码直接获得jwttoken的,没有验证码不安全,也可以不用
from rest_framework_jwt.views import obtain_jwt_token
path('api-auth/', include('rest_framework.urls')),
# 登录接口
url(r'^login/', RegisterView.ObtainJWTWithCaptchaView.as_view()),

# 下面authenticate做了用户验证获得user之后,通过user生成token

# 导入JWT生成器  
jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER  
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER  
class ObtainJWTWithCaptchaView(APIView):  
    permission_classes = [AllowAny]  
  
    """  
    获取JWT token并验证验证码  
    """    serializer_class = ObtainJWTWithCaptchaSerializer  
  
    def post(self, request, *args, **kwargs):  
        serializer = self.serializer_class(data=request.data)  
        if serializer.is_valid():  
            user = serializer.validated_data['user']  
            payload = jwt_payload_handler(user)  
            token = jwt_encode_handler(payload)  
  
            result={}  
            result['token'] = token  
            result['name'] = user.nick_name  
            return Response(result, status=status.HTTP_200_OK)  
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

class ObtainJWTWithCaptchaSerializer(serializers.Serializer):  
    username = serializers.CharField()  
    password = serializers.CharField()  
    captcha_key = serializers.CharField()  
    captcha_value = serializers.CharField()  
  
    def validate(self, attrs):  
        username = attrs.get('username')  
        password = attrs.get('password')  
        captcha_key = attrs.get('captcha_key')  
        captcha_value = attrs.get('captcha_value')  
  
        # 验证验证码  
        try:  
            captcha = CaptchaStore.objects.get(hashkey=captcha_key)  
            if captcha.expiration < timezone.now():  
                raise serializers.ValidationError("验证码过期")  
            if captcha.response != captcha_value.lower():  
                raise serializers.ValidationError("验证码错误")  
        except CaptchaStore.DoesNotExist:  
            raise serializers.ValidationError("无效的验证码键")  
  
        # 验证用户名和密码  
        user = authenticate(username=username, password=password)  
        if user is None or not user.is_active:  
            raise serializers.ValidationError("用户名或密码错误")  
  
        attrs['user'] = user  
        return attrs

上面的authenticate是将email传入了username,所以需要修改django的默认验证方法

from django.contrib.auth.backends import ModelBackend
from django.contrib.auth import get_user_model  
User = get_user_model()  
import logging  
  
# 设置日志记录器  
logger = logging.getLogger('logindemo')  # 确保这里使用你的应用名称

class CustomBackend(ModelBackend):  
    """  
    自定义用户验证  
    """    def authenticate(self, request, username=None, password=None, **kwargs):  
        try:  
            user = User.objects.get(Q(username=username) | Q(email=username))  
            if user.check_password(password):  
                return user  
        except User.DoesNotExist:  
            logger.warning(f'User does not exist: {username}')  
            return None  
        except Exception as e:  
            logger.error(f'Error during authentication: {e}')  
            return None


setting文件中配置
AUTHENTICATION_BACKENDS = (  
    'users.views.CustomBackend',  
)

vue

vue发起登录请求获得token和user返回后保存cookie和store,并根据身份数据判断是否登录的状态

const submitForm = () => {  
  formRef.value.validate((valid) => {  
    if (valid) {  
      loginUser(  
          form.value.email,  
          form.value.password,  
          captchaKey.value,  
          form.value.captcha  
      )  
          .then(response => {  
            ElMessage.success('登录成功');  
            // 保存到 cookie,有效期 7 天  
            cookie.setCookie('token', response.data.token, 7);  
            cookie.setCookie('name', response.data.name, 7);  
            userStore.syncUserInfoFromCookie()  
            router.push('/userinfo');  
  
          })  
          .catch(error => {  
            console.error('Error logging in:', error);  
            if (error.response && error.response.data) {  
              const errors = error.response.data;  
              for (const key in errors) {  
                if (errors.hasOwnProperty(key)) {  
                  ElMessage.error(errors[key].join(', '));  
                }  
              }  
            } else {  
              ElMessage.error('登录失败,请检查输入项');  
            }  
          });  
    } else {  
      ElMessage.error('请检查输入项');  
    }  
  });  
};

export const useUserStore = defineStore('user', {  
    state: () => ({  
        name: cookie.getCookie('name') || '',  
        token: cookie.getCookie('token') || ''  
    }),  
    actions: {  
        setUserInfo({ name, token }) {  
            this.name = name;  
            this.token = token;  
        },  
        clearUserInfo() {  
            this.name = '';  
            this.token = '';  
            cookie.delCookie('name');  
            cookie.delCookie('token');  
        },  
        syncUserInfoFromCookie() {  
            this.name = cookie.getCookie('name') || '';  
            this.token = cookie.getCookie('token') || '';  
        }  
    }  
});

首页判断用户是否登录
import { useUserStore } from '@/store/store.js';
const userStore = useUserStore()
// 计算是否已登录  
const isLoggedIn = ref(!!userStore.token)
// 监听 userStore.token 的变化  
watch(() => userStore.token, (newToken) => {  
  isLoggedIn.value = !!newToken  
})

<el-menu-item v-if="!isLoggedIn" index="/login">登录</el-menu-item>  
<el-menu-item v-if="!isLoggedIn" index="/register">注册</el-menu-item>  
<el-menu-item v-if="!isLoggedIn" index="/forgotPassword">忘记密码</el-menu-item>  
<el-menu-item v-if="isLoggedIn" index="/userinfo">个人信息</el-menu-item>


app.vue获取用户信息
<script setup>  
import HelloWorld from './components/HelloWorld.vue'  
import { useUserStore } from '@/store/store.js';  
const userStore = useUserStore();  
  
// 在应用初始化时同步用户信息  
userStore.syncUserInfoFromCookie();  
</script>

 

获取用户信息

//前端axios请求接口的时候,增加拦截器,在头部添加jwt参数
const service = axios.create({  
    //url = base url + reqeust url  
    baseURL : API_BASE_URL,  
    //配置请求超时时间  
    timeout: 5000  
})  
  
service.interceptors.request.use(  
    config => {  
        const userStore = useUserStore();  
        if (userStore.token) {  // 判断是否存在token,如果存在的话,则每个http header都加上token  
            config.headers.Authorization = `JWT ${userStore.token}`;  
        }  
        return config;  
    },  
    err => {  
        return Promise.reject(err);  
    }  
);

#get接口加上用户登录相关权限和认证class皆可以获取到用户信息了

class getUserInfo(APIView):  
    """  
    获取用户信息  
    """    authentication_classes = [JSONWebTokenAuthentication, SessionAuthentication]  
    permission_classes = [IsAuthenticated]  
  
    def get(self, request, *args, **kwargs):  
        user = request.user  
        data = {  
            "username": user.username,  
            "date_joined": user.date_joined,  
            "nick_name": user.nick_name,  
            "image": user.image.url if user.image else None,  
            "email": user.email,  
        }  
        return Response(data, status=status.HTTP_200_OK)


给我留言

登录

忘记密码 ?

切换登录

注册