You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

JWT认证后首次访问/fanbase返回401,刷新后正常的问题排查

问题描述

基于Flask-JWT-Extended实现JWT认证时遇到异常:用户提交登录表单后,服务器返回200并生成JWT token,控制台显示token已存入localStorage,但首次访问受保护的/fanbase接口时,请求未携带Authorization Header,服务器返回401(提示“Missing Authorization Header”);刷新首页后再次点击访问该接口,token正常携带,服务器返回200。

服务器错误日志

127.0.0.1 - - [05/Oct/2023 12:15:08] "POST /authenticate HTTP/1.1" 200 -
127.0.0.1 - - [05/Oct/2023 12:15:08] "GET /fanbase HTTP/1.1" 401 -

成功请求日志

Token in incoming request: Bearer eyJhbGciOiJIUzI1DSMFNFDHN79878CI6IkpXVCJ9.eyJmcmVzaCI6ZmFsc2UsImlhdCI6MTY5NjUxODkwOCwianRpIjoidufghfuighsigudfggsdufgsihgItY2UxZDAyOWNmYTlmIiwidHlwZSI6ImFjY2VzcyIsInN1YiI6InRlc3RAZ21haWwuY29tIiwibmJmIjoxNjk2NTE4OTA4LCJleHAiOjE2OTY1MTk4MDh9.PC4TdX_XAgl91w1YQ8ZbDj98hfNgNZSHXsue0HphnVY
127.0.0.1 - - [05/Oct/2023 12:20:35] "GET /fanbase HTTP/1.1" 200 -

服务器端Flask代码

import os
from flask import Flask, render_template, jsonify, request
from flask_jwt_extended import JWTManager, jwt_required, create_access_token, get_jwt_identity

# Initialize Flask app
app = Flask(__name__)
app.config['SECRET_KEY'] = 'supersecretkey'
app.config['JWT_SECRET_KEY'] = 'your-jwt-secret'  # replace this with your JWT secret key
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///your_database.db'

# Import after app is initialized
from models import db, User, Role

# Initialize Database
db.init_app(app)

# This is important
with app.app_context():
    db.create_all()

# Initialize JWT Manager
jwt = JWTManager(app)

@app.route('/')
def home():
    image_files = [f for f in os.listdir('static/images/') if f.endswith('.jpeg')]
    return render_template('index.html', image_files=image_files)

@app.route('/login', methods=['GET'])
def login():
    return render_template('login.html')

@app.route('/verify_token', methods=['POST'])
@jwt_required()
def verify_token():
    auth_header = request.headers.get('Authorization')
    print(f"Received auth header: {auth_header}")
    headers = dict(request.headers)
    print("Headers in verify_token:", headers)  # Debug print
    identity = get_jwt_identity()  # Get identity from token
    print("Identity:", identity)  # Debug print
    return jsonify({"message": "Token is valid"}), 200


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

    user = User.query.filter_by(username=username).first()

    # If the user exists, verify the password
    if user:
        if user.password == password:
            access_token = create_access_token(identity=username)  # instead of identity={'username': username}
            return jsonify(access_token=access_token), 200
    elif not user:
        # Create a new user if not found
        new_user = User(username=username, password=password)
        db.session.add(new_user)
        db.session.commit()

        # Issue an access token for the new user
        access_token = create_access_token(identity=username)
        return jsonify(access_token=access_token), 200
    else:
        return jsonify({"authenticated": False}), 401

    
@app.route('/fanbase', methods=['GET'])
@jwt_required()
def fanbase():
    print("Token in incoming request:", request.headers.get("Authorization"))
    username = get_jwt_identity()
    return render_template('fanbase.html', username=username)


if __name__ == '__main__':
    app.run(debug=True)

前端index.html代码

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>My Music Project</title>
  <link rel="stylesheet" type="text/css" href="/static/css/style.css">
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css">
  <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
  <script type="module">
    import jwtDecode from 'https://cdn.jsdelivr.net/npm/jwt-decode@3.1.2/+esm';
    window.jwtDecode = jwtDecode;
  </script>
  <script>
    console.log('jwt_decode should be loaded now', window.jwt_decode);
  </script>
  <script src="/static/js/p5.min.js"></script>
  <script src="/static/js/sketch.js" defer></script>
  <script src="/static/js/script.js" defer></script>
  <script type="text/javascript">
    document.addEventListener("DOMContentLoaded", function() {
      try {
        var image_files = {{ image_files|tojson }};
        var random_index = Math.floor(Math.random() * image_files.length);
        var random_image = "/static/images/" + image_files[random_index];
        document.getElementById("main-container").style.backgroundImage = "url('" + random_image + "')";
      } catch (error) {
        console.error("Error occurred:", error);
      }
    });
  </script>
</head>
<body>
  <div id="p5-container"></div>
  <div id="main-container">
    <!-- Image will be set as the background -->
    <div id="social-icons">
      <a href="https://www.facebook.com/yourpage"><i class="fab fa-facebook-f"></i></a>
      <a href="https://www.instagram.com/super.condutores"><i class="fab fa-instagram"></i></a>
      <a href="https://spotify.link/fJMjUIapADb"><i class="fab fa-spotify"></i></a>
      <a href="https://www.youtube.com/super.condutores"><i class="fab fa-youtube"></i></a>
      <a href="https://www.discord.com/super.condutores"><i class="fab fa-discord"></i></a>
    </div>
    <nav id="main-nav">
      <ul>
        <li><a href="#musica">Música</a></li>
        <li><a href="#videos">Vídeos</a></li>
        <li><a href="#fotos">Fotos</a></li>
        <li><a href="#shows">Shows</a></li>
        <li><a href="#fanbase">Fanbase</a></li>
      </ul>
    </nav>
  </div>

  <script>
    $(document).ready(function() {
      $("li > a[href='#fanbase']").on("click", function(e) {
        e.preventDefault();
        var token = localStorage.getItem('access_token');
        console.log("Retrieved Token:", token);

        if (!token) {
          console.log('No token found, redirecting to login');
          window.location.href = "/login";
          return;
        }

        var headers = {'Authorization': 'Bearer ' + token};
        console.log("Headers:", headers);

        $.ajax({
          beforeSend: function(xhr) { 
            xhr.setRequestHeader('Authorization', 'Bearer ' + token); 
          },
          url: '/fanbase',
          method: 'GET',
          success: function(response) {
            console.log('Successfully fetched fanbase', response);
          },
          error: function() {
            console.log('Failed to fetch fanbase');
          }
        });
      });
    });
  </script>
</body>
</html>

前端login.html代码

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Login</title>
  <link rel="stylesheet" type="text/css" href="/static/css/style.css">
  <script type="text/javascript">
    document.addEventListener("DOMContentLoaded", function() {
      var image_files = ['website.002.jpeg', 'website.012.jpeg', 'website.014.jpeg','website.018.jpeg','website.060.jpeg','website.062.jpeg','website.068.jpeg'];
      var random_index = Math.floor(Math.random() * image_files.length);
      var random_image = "/static/images/" + image_files[random_index];
      document.body.style.backgroundImage = "url('" + random_image + "')";
    });
  </script>
</head>
<body class="login-page">
  <div class="form-container">
    <form id="login-form">
      <label for="username">Username:</label>
      <input type="text" id="username" name="username" required><br>
      <label for="password">Password:</label>
      <input type="password" id="password" name="password" required><br>
      <input type="submit" value="Login">
    </form>
  </div>

  <script>
    document.addEventListener("DOMContentLoaded", function() {
      var form = document.getElementById('login-form');

      form.addEventListener('submit', function(event) {
        event.preventDefault();

        var username = document.getElementById('username').value;
        var password = document.getElementById('password').value;

        fetch('/authenticate', {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json'
          },
          body: JSON.stringify({
            username: username,
            password: password
          })
        })
        .then(response => {
          if (response.status === 200) {
            return response.json();
          } else {
            throw new Error('Failed to authenticate');
          }
        })
        .then(data => {
            if (data.access_token) {
                localStorage.setItem('access_token', data.access_token);
                window.location.href = '/fanbase';  // Comment this out
                console.log('Token stored but not redirecting');  // Add this line for debug
            } else {
                alert('Authentication failed');
            }
            })
        .catch(error => {
          console.log('Error:', error);
          alert('Authentication failed');
        });
      });
    });
  </script>
</body>
</html>

问题原因及解决方案

核心问题

登录成功后直接通过window.location.href = '/fanbase'跳转,此时浏览器发起的是普通GET请求,不会自动携带Authorization Header,导致服务器返回401。而刷新首页后点击访问,是通过index.html中的AJAX请求手动设置了Header,所以成功。

解决方案

方案1:修改登录后跳转逻辑

登录成功后跳转到首页,让用户手动点击Fanbase链接触发AJAX请求:

// login.html中修改跳转代码
.then(data => {
    if (data.access_token) {
        localStorage.setItem('access_token', data.access_token);
        window.location.href = '/'; // 跳转到首页而非直接访问fanbase
        console.log('Token stored, redirecting to home');
    } else {
        alert('Authentication failed');
    }
})

方案2:直接跳转时手动携带Token

如果需要登录后直接进入fanbase页面,可通过AJAX请求获取页面内容并替换当前页面:

// login.html中修改登录成功后的逻辑
.then(data => {
    if (data.access_token) {
        localStorage.setItem('access_token', data.access_token);
        // 用AJAX请求fanbase页面并替换内容
        fetch('/fanbase', {
            headers: {
                'Authorization': 'Bearer ' + data.access_token
            }
        })
        .then(res => res.text())
        .then(html => {
            document.body.innerHTML = html;
        });
    } else {
        alert('Authentication failed');
    }
})

方案3:启用Cookie存储Token(推荐)

在服务器端配置Flask-JWT-Extended使用Cookie存储Token,浏览器会自动在请求中携带Cookie,无需手动设置Header:

# 服务器端添加配置
app.config['JWT_TOKEN_LOCATION'] = ['cookies']
app.config['JWT_COOKIE_SECURE'] = False  # 开发环境设为False,生产环境需配合HTTPS改为True
app.config['JWT_COOKIE_CSRF_PROTECT'] = False  # 不需要CSRF保护可关闭

额外建议

  • 禁止明文存储密码:当前代码中用户密码为明文存储,需使用werkzeug.security的generate_password_hash和check_password_hash进行加密。
  • 处理Token失效场景:在AJAX请求的error回调中清除无效Token并跳转至登录页。

内容的提问来源于stack exchange,提问作者8-Bit Borges

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 12:45:55