【问题标题】:What does JWT being stateless really means?JWT 无状态的真正含义是什么?
【发布时间】:2021-05-07 19:33:25
【问题描述】:

您好,提前致谢,

我已经使用 django-rest-framework-simplejwt 和 React 成功设置了 JWT 身份验证,但我仍然对优势以及特别是数据库命中感到非常困惑。 我将 simplejwt 与 ROTATE_REFRESH_TOKENS': True 'BLACKLIST_AFTER_ROTATION': True 一起使用,当我的 access_token 过期时,我通过 /api/token/refresh 请求一个新令牌并将旧令牌列入黑名单,我正在使用 axios 拦截器自动执行该操作。

但在我的理解中,JWt 的好处是它们是无状态的,这意味着我不必每次想要发出需要身份验证权限的请求时都访问用户数据库表。 即使是这样的简单视图也会出现问题:

class IsConnecteddAPI(APIView):

    permission_classes = [permissions.IsAuthenticated]

    def get(self, request, *args, **kwargs):
        data = "You seem to be connected"

        return Response(data, status=status.HTTP_200_OK)

使用 django-silk 我看到当我使用有效的访问令牌调用它时,它仍然对我的用户表执行 1 次查询,这正常吗?如果是这样,为什么我们说 JWT 是无状态的?我真的很困惑。

如果需要,那是我的 axios 代码:

import axios from "axios";


const baseURL = "http://localhost:5000";

const axiosInstance = axios.create({
  baseURL: baseURL,
  timeout: 5000,
  headers: {
    Authorization: localStorage.getItem("accesstoken")
      ? "JWT " + localStorage.getItem("accesstoken")
      : null,
    "Content-Type": "application/json",
    accept: "application/json",
  },
});

const axioAnonymousInstance = axios.create({
  baseURL: baseURL,
  timeout: 5000,
  headers: {
    "Content-Type": "application/json",
    accept: "application/json",
  },
});

axiosInstance.interceptors.response.use(
  (response) => {
    return response;
  },
  async function (error) {
    const originalRequest = error.config;

    if (typeof error.response === "undefined") {
      alert(
        "A server/network error occurred. " +
          "Looks like CORS might be the problem. " +
          "Sorry about this - we will get it fixed shortly."
      );
      return Promise.reject(error);
    }

    if (
      error.response.status === 401 &&
      originalRequest.url === baseURL + "token/refresh/"
    ) {
      window.location.href = "/login/";
      return Promise.reject(error);
    }

    if (
      error.response.data.code === "token_not_valid" &&
      error.response.status === 401 &&
      error.response.statusText === "Unauthorized"
    ) {
      const refreshToken = localStorage.getItem("refreshtoken");

      if (refreshToken) {
        const tokenParts = JSON.parse(atob(refreshToken.split(".")[1]));

        // exp date in token is expressed in seconds, while now() returns milliseconds:
        const now = Math.ceil(Date.now() / 1000);
        console.log(tokenParts.exp);

        if (tokenParts.exp > now) {
          return axioAnonymousInstance
            .post("/api/token/refresh/", { refresh: refreshToken })
            .then((response) => {
              localStorage.setItem("accesstoken", response.data.access);
              localStorage.setItem("refreshtoken", response.data.refresh);

              axiosInstance.defaults.headers["Authorization"] =
                "JWT " + response.data.access;
              originalRequest.headers["Authorization"] =
                "JWT " + response.data.access;

              return axiosInstance(originalRequest);
            })
            .catch((err) => {
              // redirect ro /login here if wanted
              console.log("axios Safe Instance error");
              console.log(err);
              // window.location.href = "/login/";
            });
        } else {
          console.log("Refresh token is expired", tokenParts.exp, now);
          window.location.href = "/login/";
        }
      } else {
        console.log("Refresh token not available.");
        window.location.href = "/login/";
      }
    }

    // specific error handling done elsewhere
    return Promise.reject(error);
  }
);

export { axiosInstance, axioAnonymousInstance };

(我知道我不应该使用 localStorage 但无论如何)

我通常会调用这个函数来向上面写的视图发出简单的请求:

 const IsConnected = () => {
    axiosInstance
      .get("/api/is_connected/")
      .then((response) => {
        if (response.status === 200) {
          console.log(response.data);
          console.log("Is connected : CONNECTED ");
        } else {
          console.log("IS connected : not connected");
        }
      })
      .catch((error) => {
        console.log("Is connected : NOT CONNECTED");
        console.log(error);
      });
  };

【问题讨论】:

    标签: django django-rest-framework jwt


    【解决方案1】:

    如果没有确切查询的细节命中您的数据库,很难判断发生了什么(数据库查询必须源自中间件,因为您的代码中没有任何内容可以执行此操作,我怀疑它是 django 的 CsrfViewMiddleware) .不过,关于你的JWT是无状态的问题,我建议你看看官方introduction

    基本上,JWT 发生的情况是您的服务器使用服务器的密钥对令牌执行签名验证(请注意某些problems)。如果验证通过,则存储在 JWT 中的数据是可信的,可以按原样读取,这就是不需要数据库查询的原因。当然,这确实意味着您的用户将确切地知道他们的令牌中存储了什么,因为数据是一个简单的 base64 编码 JSON 对象。

    【讨论】:

    • 非常感谢您的回复,是的,我知道它是可读的,用户只需要 JSON.parse(atob(refreshToken.split(".")[1])) 来解码它.我已经在我的设置中注释掉了 CSRFMiddleware,但仍然得到相同的结果。如果有帮助,我已经截图了细节imgur.com/a/F29Dlroimgur.com/yDzkQYVimgur.com/oQPLn1o
    • 可能我忘记了最重要的部分imgur.com/m2C6NNE
    • @Heroe__ 我建议您查看回溯第 7 行指向的 get_user 函数。看起来您的查询来自那里。
    • 似乎是在这里github.com/SimpleJWT/django-rest-framework-simplejwt/blob/… Attempts to find and return a user using the given validated token. 的 drf-simplejwt 库中定义的函数,所以它基本上是在查询数据库吧?
    • @Heroe__ 是的。如果您查看您发布的文件的第 114 行,这是一个直接的数据库查询。如果要避免 db 查询,可以考虑第 124 行定义的类。
    猜你喜欢
    • 2017-03-26
    • 2017-05-06
    • 2012-03-30
    • 2011-10-10
    • 2012-08-03
    • 2012-01-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多