JWT
在用户注册或登录后,我们想记录用户的登录状态,或者为用户创建身份认证的凭证。
我们不再使用Session认证机制,而使用Json Web Token认证机制。
Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).
该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。
JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,
也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。
安装配置JWT
pip install djangorestframework-jwt
配置
项目settings中配置
# drf框架的配置信息
REST_FRAMEWORK = {
# 异常处理
'EXCEPTION_HANDLER': 'luffy.utils.exceptions.custom_exception_handler',
# 用户登陆认证方式
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.BasicAuthentication',
),
}
# jwt载荷中的有效期设置
JWT_AUTH = {
'JWT_EXPIRATION_DELTA': datetime.timedelta(days=1), # 有效期设置
'JWT_RESPONSE_PAYLOAD_HANDLER': 'users.utils.jwt_response_payload_handler',
}
- JWT_EXPIRATION_DELTA 指明token的有效期
JWt使用
Django REST framework JWT 扩展的说明文档中提供了手动签发JWT的方法.
from rest_framework_jwt.settings import api_settings
jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
payload = jwt_payload_handler(user)
token = jwt_encode_handler(payload)
在用户注册或登录成功后,在序列化器中返回用户信息以后同时返回token即可。
默认情况下,djangorestframework-jwt这个模块已经内置了一个登陆视图接口给我们了。我们直接使用
当前users的urls.py文件代码:
from django.urls import path
# jwt内部实现的登陆视图
from rest_framework_jwt.views import obtain_jwt_token
urlpatterns=[
path(r"login", obtain_jwt_token ),
]
在总路由文件中,urls.py中注册子应用路由:
path('users/',include('users.urls')) ,
可以通过postman来完成测试:
输入账号和密码时:
经过上面的测试,登陆视图已经完成了。
jwt后端认证
Django REST framework JWT提供了登录签发JWT的视图,可以直接使用.
1. users应用中urls.py :
from rest_framework_jwt.views import obtain_jwt_token
urlpatterns = [
path(r'login/$', obtain_jwt_token),
]
2.
但是默认的返回值仅有token,我们还需在返回值中增加username和user_id。
通过修改该视图的返回值可以完成我们的需求。
在users应用中新建一个utils.py 文件,在users/utils.py 中,创建:
def jwt_response_payload_handler(token, user=None, request=None):
"""
自定义jwt认证成功返回数据
:token 返回的jwt
:user 当前登录的用户信息[对象]
:request 当前本次客户端提交过来的数据
"""
return {
'token': token,
'id': user.id,
'username': user.username,
}
3. 项目配置文件中:
# JWT
JWT_AUTH = {
'JWT_EXPIRATION_DELTA': datetime.timedelta(days=1),
'JWT_RESPONSE_PAYLOAD_HANDLER': 'users.utils.jwt_response_payload_handler',
}
测试结果:
前端登录功能的实现
Login.vue登陆视图组件,代码:
<button class="login_btn" @click="loginhander">登录</button>
<script>
export default {
name: "Login",
data() {
return {
remmber:false,//是否记住密码
login_type: 1,//登录方式,短信登录还是密码登录
username: "",// 登录账号
password: "",// 登录密码
mobile: "",//手机号码
sms: "",// 手机短信验证码
}
},
methods:{
loginheader:function () {
//登录函数
this.$axios.post('http://127.0.0.1:8000/users/login',{
'username':this.username,
'password':this.password},{responseType:'json'}
).then(
response=>{
//请求成功,保存登录状态
console.log(response);
}
).catch(error=>{})
}
},
components: {},
}
</script>
点击登录按钮的情况:
当用户名和密码正确时,就需要将服务端返回的数据进行保存。
前端保存jwt
我们可以将JWT保存在cookie中,也可以保存在浏览器的本地存储里,我们保存在浏览器本地存储中
浏览器的本地存储提供了sessionStorage 和 localStorage 两种:
- sessionStorage 会话存储,浏览器关闭即失效
- localStorage 永久存储,长期有效
使用方法:
sessionStorage.变量名 = 变量值 // 保存数据
sessionStorage.变量名 // 读取数据
sessionStorage.clear() // 清除所有sessionStorage保存的数据
localStorage.变量名 = 变量值 // 保存数据
localStorage.变量名 // 读取数据
localStorage.clear() // 清除所有localStorage保存的数据
在端登录后保存用户登录信息和登录成功后的页面跳转:
在Login.vue:
<div class="rember">
<p>
<input type="checkbox" class="no" name="a" v-model="remmber" ></input>
<span>记住密码</span>
</p>
<p>忘记密码</p>
</div>
<script>
export default {
name: "Login",
data() {
return {
remmber:false,//是否记住密码
login_type: 1,//登录方式,短信登录还是密码登录
username: "",// 登录账号
password: "",// 登录密码
mobile: "",//手机号码
sms: "",// 手机短信验证码
}
},
methods:{
loginheader:function () {
//登录函数
this.$axios.post('http://127.0.0.1:8000/users/login',{
'username':this.username,
'password':this.password},{responseType:'json'}
).then(
response=>{
//请求成功,保存登录状态
console.log(response);
//当要记住密码时:
if (this.remmber){
// 记住密码时
let data = response.data;
localStorage.token=data.token;
localStorage.id=data.id;
localStorage.username=data.username;
}else {
//不记住密码
localStorage.removeItem('token');
let data = response.data;
sessionStorage.token=data.token;
sessionStorage.id=data.id;
sessionStorage.username=data.username;
}
//登录成功之后跳转页面
this.$router.go(-1);// 跳转到之前的访问页面
// this.$router.push('/home') // 或者跳转到首页
}
).catch(error=>{
console.log(error)
})
}
},
components: {},
}
</script>
未登录时:
登录成功后:
在header.vue组件中:
<script>
export default {
name:"Header",
props:["current_page"],
data(){
return {
is_login: false, /* 是否登录 */
token:localStorage.token || sessionStorage.token,
}
},
created:function () {
//登录状态判断
if(this.token){
//登录成功
this.is_login=true;
}else {
this.is_login=false;
}
},
methods:{
logout(){
localStorage.clear();
sessionStorage.clear();
this.is_login=false;
alert("退出登录成功!")
}
}
}
</script>
<template>
<div class="header">
<el-container>
<el-header height="80px">
<el-row>
<el-col class="logo" :span="3">
<router-link to="/"><img src="../../assets/head-logo.svg" alt=""></router-link>
</el-col>
<el-col :span="16">
<!-- gutter每一列之间的间隔空隙 -->
<el-row class="nav" :gutter="20">
<el-col :span="3"><router-link :class="current_page==1?'active':''" to="/courses">免费课</router-link></el-col>
<el-col :span="3"><router-link :class="current_page==2?'active':''" to="/courses">轻课</router-link></el-col>
<el-col :span="3"><router-link :class="current_page==3?'active':''" to="/courses">学位课</router-link></el-col>
<el-col :span="3"><router-link :class="current_page==4?'active':''" to="/courses">题库</router-link></el-col>
<el-col :span="3"><router-link :class="current_page==5?'active':''" to="/courses">教育</router-link></el-col>
</el-row>
</el-col>
<el-col v-if="is_login" class="login-bar" :span="5">
<div class="cart-ico">
<b></b>
<img src="../../assets/cart.svg" alt="">
<span>购物车</span>
</div>
<div class="study">学习中心</div>
<div class="member">
<el-dropdown>
<span class="el-dropdown-link">
<img src="../../assets/logo@2x.png" alt="">
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item>我的账户 <i class="el-icon-arrow-right"></i></el-dropdown-item>
<el-dropdown-item>我的账户 <i class="el-icon-arrow-right"></i></el-dropdown-item>
<el-dropdown-item>我的账户 <i class="el-icon-arrow-right"></i></el-dropdown-item>
<el-dropdown-item><span @click="logout">退出登录</span> <i class="el-icon-arrow-right"></i></el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
</el-col>
<el-col v-else class="login-bar" :span="5">
<div class="cart-ico">
<img src="../../assets/cart.svg" alt="">
<span>购物车</span>
</div>
<span class="header-login"><router-link to="/login">登录</router-link></span>
|
<span class="header-register">注册</span>
</el-col>
</el-row>
</el-header>
</el-container>
</div>
</template>
<script>
export default {
name:"Header",
props:["current_page"],
data(){
return {
is_login: false, /* 是否登录 */
token:localStorage.token || sessionStorage.token,
}
},
created:function () {
//登录状态判断
if(this.token){
//登录成功
this.is_login=true;
}else {
this.is_login=false;
}
},
methods:{
logout(){
localStorage.clear();
sessionStorage.clear();
this.is_login=false;
alert("退出登录成功!")
}
}
}
</script>
<style scoped>
.header{
width: 100%;
height: 80px;
background: #fff;
position: fixed;
left: 0;
top: 0;
margin: 0 auto;
z-index: 7;
text-overflow: ellipsis;
white-space: nowrap;
box-shadow: 0 0.5px 0.5px 0 #c9c9c9;
}
.el-container{
width: 1200px;
margin: 0 auto;
}
.el-header{
padding: 0;
}
.el-row,.el-col{
height: 80px;
}
.logo{
width: 118px;
}
.logo,.nav{
line-height: 80px;
}
.logo img{
vertical-align: middle; /* 设置图片垂直居中 */
}
.nav{
margin-left: 30px!important;
margin-right: 0!important;
}
.nav .el-col .active{
padding-bottom: 16px;
padding-left: 5px;
padding-right: 5px;
border-bottom: 4px solid #ffc210;
}
.nav,.study{
text-align: center;
color: #4a4a4a;
}
.nav .el-col:hover,.nav .el-col a:hover,.study:hover,.study a:hover{
color: #000000;
}
.login-bar{
display: flex;
align-items: center;
}
.cart-ico{
width: 88px;
height: 28px;
margin-right: 20px;
background: #f7f7f7;
border-radius: 17px;
display: flex;
align-items: center;
justify-content: space-around;
cursor: pointer;
position: relative;
font-size: 14px;
}
.cart-ico b{
width: 16px;
height: 16px;
line-height: 17px;
font-size: 12px;
color: #fff;
text-align: center;
background: #fa6240;
border-radius: 50%;
transform: scale(.8);
position: absolute;
left: 16px;
top: -1px;
}
.cart-ico img{
width: 15px;
height: auto;
margin-left: 6px;
}
.cart-ico span{
margin-right: 6px;
}
.study{
padding-left: 0;
font-family: PingFangSC-Regular;
letter-spacing: 0;
margin-right: 20px;
font-size: 15px;
cursor: pointer;
}
.member img{
width: 26px;
height: 26px;
border-radius: 50%;
display: inline-block;
cursor: pointer;
vertical-align: middle;
}
.member img:hover{
border: 1px solid rgb(255, 194, 16);
}
.el-dropdown-menu{
left: 1130px!important;
width: 180px;
top: 64px!important;
}
.el-dropdown-menu i{
float:right;
line-height: 36px;
}
.header-login,.header-register{
cursor: pointer;
font-size: 15px;
color: #4a4a4a;
}
.header-login:hover,.header-register:hover{
color: #000000;
}
</style>
header.vue