从Django,Flask到Express(四)Session和JWT

Django

Session

登录,登出演示

前端 vue3

<template>
  <div>
    <button v-if="loggedIn" @click="logout">退出</button>
    <div v-else>
      <input type="text" v-model="username" placeholder="用户名" />
      <input type="password" v-model="password" placeholder="密码" />
      <button @click="login">登录</button>
    </div>
    <div v-if="loggedIn">{{ user.username }} [{{ user.email }}]</div>
  </div>
</template>
<script>
import { ref } from "vue";
import axios from "axios";

export default {
  name: "App",
  setup() {
    const username = ref("");
    const password = ref("");
    const loggedIn = ref(false);
    const user = ref({
      username: "",
      email: "",
    });

    async function login() {
      try {
        const response = await axios.post("/api/login/", {
          username: username.value,
          password: password.value,
        });
        if (response.status === 200) {
          loggedIn.value = true;
          getUser();
        }
      } catch (error) {
        console.error(error);
      }
    }

    async function getUser() {
      try {
        const response = await axios.get("/api/user/detail/");
        user.value = response.data;
      } catch (error) {
        console.error(error);
      }
    }

    async function logout() {
      try {
        await axios.get("/api/logout/");
        loggedIn.value = false;
        user.value = {
          username: "",
          email: "",
        };
      } catch (error) {
        console.error(error);
      }
    }

    return {
      username,
      password,
      loggedIn,
      user,
      login,
      getUser,
      logout,
    };
  },
};
</script>

后端

# urls.py
from django.urls import path
from .views import LoginAPIView, user_detail, logout_view

urlpatterns = [
    path('login/', LoginAPIView.as_view(), name='login-api'),
    path('user/detail/', user_detail, name='user-detail'),
    path('logout/', logout_view, name='logout'),
]

# views.py
from django.contrib.auth import authenticate, login, logout
from django.contrib.auth.decorators import login_required
from django.http import JsonResponse, HttpResponseRedirect
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.decorators import api_view, permission_classes
from rest_framework.permissions import IsAuthenticated

class LoginAPIView(APIView):
    def post(self, request):
        username = request.data.get('username')
        password = request.data.get('password')
        user = authenticate(username=username, password=password)
        if user is not None:
            login(request, user)
            return Response(status=200)
        else:
            return Response(status=401, data={"message": "Invalid credentials"})

@api_view(['GET'])
@permission_classes([IsAuthenticated])
def user_detail(request):
    user = request.user
    data = {
        'username': user.username,
        'email': user.email,
        'first_name': user.first_name,
        'last_name': user.last_name
    }
    return JsonResponse(data)

@login_required
def logout_view(request):
    logout(request)
    return HttpResponseRedirect('/login/')

需要注意的是,在前端代码中,我们通过axios发送HTTP请求;而在后端代码中,我们使用了Django Rest Framework提供的装饰器实现了对登录状态的判断,并且通过permission_classes指定了需要验证auth权限。

JWT

前端

安装必需的库:

npm install axios vue-router vue3-jwt
在您的Vue项目入口文件中(如main.js)添加以下内容以在整个应用程序中使用Vue Router和Vue3-JWT:



import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import { jwt } from 'vue3-jwt'
import axios from "axios"

const app = createApp(App)

app.use(router)
app.use(jwt, {
  storage: window.localStorage,
  keyName: 'access_token', // 存储JWT的访问令牌关键字 
}, axios)

app.mount('#app')

接下来,我们可以编写登录页面组件,并在其中使用Axios来获取有效的JWT令牌:

<template>
  <div>
    <h2>登录页面</h2>

    <form @submit.prevent="login">
      <input type="text" v-model="username" placeholder="用户名">
      <input type="password" v-model="password" placeholder="密码">
      <button type="submit">登录</button>
    </form>
  </div>
</template>


<script>
import { ref } from 'vue'

export default {
  setup() {
    const username = ref('')
    const password = ref('')
    
    async function login() {
      try {
        const response = await axios.post('/api/login/', {
          username: username.value,
          password: password.value
        })

        const accessToken = response.data.token

        $jwt.setAccessToken(accessToken) // 将Access Token存储到localStorage中
        $router.push('/') // 跳转到应用程序首页
      } catch (error) {
        console.error(error)
      }
    }

    return {
      username,
      password,
      login,
    }
  }
}
</script>

接下来,我们可以创建一个需要身份验证的组件(例如用户信息的详情页面),并使用Vue3-JWT和Axios进行JWT身份验证:

<template>
  <div>
    <h2>用户详情页</h2>

    <p>当前用户</p>
    <pre>{{ user }}</pre>
  </div>
</template>

<script>
import { ref, onMounted } from 'vue'

export default {
  setup() {
    const user = ref(null)

    onMounted(async () => {
      try {
        const response = await axios.get('/api/user/detail/', {
          headers: {
            Authorization: `Bearer ${$jwt.accessToken}`
          }
        })
        user.value = response.data
      } catch (error) {
        console.error(error)
      }
    })

    return {
      user
    }
  }
}
</script>

在该示例中,我们在setup函数中使用onMounted钩子函数发送GET请求以获取当前登录用户的详细信息。由于我们已将存储在localStorage中的有效JWT令牌添加到Vue3-JWT实例中,因此我们在Axios请求中自动携带里含有设置好的Authorization头字段作为JWT身份验证标识。

最后,在路由配置文件中将这些组件添加到正确的路径:

import { createRouter, createWebHistory } from 'vue-router'
import LoginView from '@/views/LoginView.vue'
import UserDetailView from '@/views/UserDetailView.vue'
import { requireAuth } from './guards/auth' // 身份验证守卫

const routes = [
  {
    path: '/login',
    component: LoginView
  },
  {
    path: '/',
    component: UserDetailView,
    meta: { requiresAuth: true } // 将此路由保护起来,需要身份验证
  }
]

const router = createRouter({
  history: createWebHistory(process.env.BASE_URL),
  routes
})

router.beforeEach(requireAuth) // 应用程序中所有路由都严格要求身份验证

export default router

在该示例中,我们使用了一个自定义的路由守卫(route guard)来限制只有已经登录过的用户可以访问受保护的页面。如果未登录,则该守卫将用户重定向到登录页面。

后端

以下是使用Django REST Framework (DRF)实现JSON Web Token (JWT)身份验证的示例:

首先,安装必需的库:

pip install djangorestframework-jwt
接下来,在您的Django项目的settings.py文件中添加以下内容:


INSTALLED_APPS = [
    # ...其他应用程序
    'rest_framework',
    'rest_framework_jwt',
]

REST_FRAMEWORK = {
    # ...其他配置,比如默认渲染器或解析器等
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
        # 可以添加其他身份验证类,如SessionAuthentication 
    ),
}

JWT_AUTH = {
    'JWT_SECRET_KEY': SECRET_KEY,  # Django的SECRET_KEY
    'JWT_ALGORITHM': 'HS256',  # 使用HS256算法签发JWT
    'JWT_VERIFY_EXPIRATION': True,  # 验证JWT是否过期 
    # 更多JWT配置,如自定义JWT_PAYLOAD_HANDLER和JWT_RESPONSE_PAYLOAD_HANDLER可以在文档中找到。
}

接下来,让我们编写视图(Views)并使用JWT进行身份验证。例如,我们将创建一个验证当前用户身份的API端点:

from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated

class CurrentUserView(APIView):
    permission_classes = [IsAuthenticated]

    def get(self, request):
        # 使用IsAuthenticated权限类,只有在有有效JWT时才允许访问该端点。

        # 现在,我们可以使用request.user获取当前用户对象。
        user = request.user
        serialized_user = UserSerializer(user).data

        return Response(serialized_user)

最后,为了获取有效的JWT令牌,我们可以编写一个视图来处理登录和生成JWT:

from rest_framework_jwt.views import ObtainJSONWebToken

class LoginView(ObtainJSONWebToken):
    # 在这里定义其他自定义逻辑,如额外的验证。

    def post(self, request, *args, **kwargs):
        # 登录并获取有效的JWT
        response = super(LoginView, self).post(request, *args, **kwargs)

        # 响应通常返回带有token键的字典。
        response.data['user'] = UserSerializer(request.user).data
        # 附加该用户的序列化数据

        return response

现在,当用户成功登录后,我们将发送有效JWT令牌作为响应。之后,他们可以通过使用该令牌访问需要身份验证的API端点。

Flask

Session

from flask import Flask, request, jsonify, session
from werkzeug.security import generate_password_hash, check_password_hash

app = Flask(__name__)
app.secret_key = "thisissecret"

users = {
    "user1": {"username": "user1", "password": generate_password_hash("password1"), "email": "user1@example.com"},
    "user2": {"username": "user2", "password": generate_password_hash("password2"), "email": "user2@example.com"}
}

@app.route('/api/login', methods=['POST'])
def login():
    username = request.json.get('username')
    password = request.json.get('password')

    user = None
    for u in users.values():
        if u['username'] == username:
            user = u
            break

    if user and check_password_hash(user['password'], password):
        session['logged_in'] = True
        session['user'] = {'username': user['username'], 'email': user['email']}
        return jsonify({"message": "login success"})
    else:
        return jsonify({"message": "Invalid credentials."}), 401

@app.route('/api/user/detail', methods=['GET'])
def user_detail():
    if not session.get('logged_in'):
        return jsonify({"message": "User not logged in."}), 401

    user = session.get('user')
    data = {
        'username': user['username'],
        'email': user['email']
    }
    return jsonify(data)

@app.route('/api/logout', methods=['POST'])
def logout():
    session.pop('logged_in', None)
    session.pop('user', None)
    return jsonify({"message": "logout success"})

在这个示例中,我们重新定义了三个接口:/api/login、/api/user/detail 和 /api/logout。其中,/api/login 仍然用于接收包含用户名和密码的 POST 请求,并将用户信息存储到 session 中;但返回值从重定向变为了一个 JSON 格式的消息。同理,/api/logout 同时也返回一个 JSON 格式的成功提示。

/api/user/detail 是一个 GET 请求,需要先检查是否已登录(通过读取 session)。如果未登录,则返回错误信息。否则返回当前登录用户的详细信息。

值得注意的是,Flask 的 session 实现默认把数据保存在 cookie 中,因此建议启用 HTTPS 连接以提高安全性。

JWT

下面提供使用 Flask 和 JWT 实现用户登录系统的示例代码。JWT(JSON Web Token)是一种广泛使用的认证标准,可用于在客户端和服务器之间安全地传输信息。



# app.py
from flask import Flask, request, jsonify
from werkzeug.security import generate_password_hash, check_password_hash
import jwt
from functools import wraps

app = Flask(__name__)
app.config['SECRET_KEY'] = 'thisissecret'

users = {
    "user1": {"username": "user1", "password": generate_password_hash("password1"), "email": "user1@example.com"},
    "user2": {"username": "user2", "password": generate_password_hash("password2"), "email": "user2@example.com"}
}

def token_required(f):
    @wraps(f)
    def decorated(*args, **kwargs):
        token = request.headers.get('Authorization')

        if not token:
            return jsonify({'message': 'Token is missing!'}), 401

        try:
            data = jwt.decode(token.split()[1], app.config['SECRET_KEY'])
        except Exception as e:
            return jsonify({'message': 'Token is invalid!'}), 401

        return f(*args, **kwargs)

    return decorated

@app.route('/api/login', methods=['POST'])
def login():
    username = request.json.get('username')
    password = request.json.get('password')

    user = None
    for u in users.values():
        if u['username'] == username:
            user = u
            break

    if user and check_password_hash(user['password'], password):
        token = jwt.encode({'username': user['username']}, app.config['SECRET_KEY'])
        return jsonify({'token': token.decode('UTF-8')})
    else:
        return jsonify({"message": "Invalid credentials."}), 401

@app.route('/api/user/detail', methods=['GET'])
@token_required
def user_detail():
    data = {
        'username': request.args.get('username'),
        'email': request.args.get('email')
    }
    return jsonify(data)
   

在这个示例中,我们首先引入了 JWT 模块,并使用 wraps 装饰器定义了 token_required 装饰器,用于装饰需要认证的视图函数。该装饰器通过检查请求头中的 Authorization 属性,验证客户端是否提供有效的 token。如果未提供或无效,则返回错误响应;否则执行原来需要认证的视图。

/api/login 请求的处理与 Django 示例类似,接收包含用户名和密码的 JSON 数据。验证成功后,使用 JWT 加密用户信息,并将 token 返回给客户端。客户端需在后续每次请求中通过请求头(例如 Authorization: Bearer )携带该 token。

/api/user/detail 是一个 GET 请求,用于返回当前登录用户的详细信息。由于需要认证,我们通过装饰器 @token_required 确保只有提供了有效 token 的请求才能访问该页面。在视图函数内部,我们从请求参数中获取用户名和电子邮件等用户信息,然后返回 JSON 格式的数据。

需要注意的是,本示例中的 JWT 实现方式简单明了,但并未考虑到 token 过期、刷新等诸多细节问题。如果需要深入了解 JWT,建议参考官方文档或相关资料进行学习和实践。同时,生产环境下还需加强对 token 安全性的考虑,例如使用 HTTPS 协议、设置有效期限等。

Express

session

Node.js 和 Express 框架实现类似功能的代码示例:



const express = require('express');
const app = express();

const bodyParser = require('body-parser');
const session = require('express-session');
const bcrypt = require('bcrypt');

app.use(bodyParser.json());
app.use(session({
    secret: 'thisissecret',
    resave: false,
    saveUninitialized: true
}));

const users = {
    "user1": {"username": "user1", "password": bcrypt.hashSync("password1", 10), "email": "user1@example.com"},
    "user2": {"username": "user2", "password": bcrypt.hashSync("password2", 10), "email": "user2@example.com"}
};

app.post('/api/login', (req, res) => {
    const { username, password } = req.body;

    const user = Object.values(users).find(u => u.username === username);

    if (user && bcrypt.compareSync(password, user.password)) {
        req.session.logged_in = true;
        req.session.user = { username: user.username, email: user.email };
        return res.status(200).json({ message: 'login success' });
    } else {
        return res.status(401).json({ message: 'Invalid credentials.' });
    }
});

app.get('/api/user/detail', (req, res) => {
    if (!req.session.logged_in) {
        return res.status(401).json({ message: 'User not logged in.' });
    }

    const data = {
        username: req.session.user.username,
        email: req.session.user.email
    };
    return res.status(200).json(data);
});

app.post('/api/logout', (req, res) => {
    req.session.destroy();
    return res.status(200).json({ message: 'logout success' });
});

const port = process.env.PORT || 3000;
app.listen(port, () => console.log(`Server listening on port ${port}`));


主要使用了 Express 的路由和中间件机制,使用 bcrypt 库进行密码哈希加密和比对。用户登录成功后使用 express-session 记录用户状态,并实现退出登录清除 session 信息的功能。

需要注意的是,同样为了保障安全性,在部署该应用时也需要设置合理的 CORS 配置和 CSRF 防护等安全措施。

JWT

const express = require('express');
const app = express();

const bodyParser = require('body-parser');
const bcrypt = require('bcrypt');
const jwt = require('jsonwebtoken');

app.use(bodyParser.json());

const users = {
    "user1": {"username": "user1", "password": bcrypt.hashSync("password1", 10), "email": "user1@example.com"},
    "user2": {"username": "user2", "password": bcrypt.hashSync("password2", 10), "email": "user2@example.com"}
};

function token_required(req, res, next) {
    const token = req.headers.authorization;

    if (!token) {
        return res.status(401).json({ message: 'Token is missing!' });
    }

    try {
        const data = jwt.verify(token.split(' ')[1], process.env.SECRET_KEY || 'thisissecret')
        req.user = { username: data.username };
        next();
    } catch (err) {
        console.error(err);
        return res.status(401).json({ message: 'Token is invalid!' });
    }
}

app.post('/api/login', (req, res) => {
    const { username, password } = req.body;

    const user = Object.values(users).find(u => u.username === username);

    if (user && bcrypt.compareSync(password, user.password)) {
        const token = jwt.sign({ username: user.username }, process.env.SECRET_KEY || 'thisissecret');
        return res.status(200).json({ token: token });
    } else {
        return res.status(401).json({ message: 'Invalid credentials.' });
    }
});

app.get('/api/user/detail', token_required, (req, res) => {
    const data = {
        username: req.user.username,
        email: users[req.user.username].email
    };
    return res.status(200).json(data);
});

const port = process.env.PORT || 3000;
app.listen(port, () => console.log(`Server listening on port ${port}`));

主要实现了 token_required 中间件,使用 jwt 库进行 token 的签发和验证,并通过 req.user 记录当前用户信息。在登录成功后返回 token 给前端,并在需要鉴权的路由中添加 token_required 中间件,判断请求头中是否带有有效的 token,如果验证通过,则将解码后的用户信息存入 req.user 对象中,并继续执行后续逻辑。

同样,为保障安全性,在部署该应用时需要设置合理的 CORS 配置和 CSRF 防护等安全措施。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值