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)