首页 > 解决方案 > 用于 Django 和 React SPA 的安全 JWT 身份验证

问题描述

我为我的 Django-React SPA 设置了基本的不安全 JWT 身份验证。我们仍处于开发阶段,现在想开始研究身份验证安全性。

我在初始实现中使用了这两个参考:110% Complete JWT Authentication with Django & React - 2020 以及如何正确设置 Axios 拦截器和 React Context?.

这是我的相关代码。目前我正在使用 rest_framework_simpleJWT 库并在必要时打开以使用不同的库。

Django 设置.py

REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': (
        'rest_framework.permissions.IsAuthenticated',
    ),
    'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework_simplejwt.authentication.JWTAuthentication',
    ),  #
}
SIMPLE_JWT = {
    'ACCESS_TOKEN_LIFETIME': timedelta(minutes=10),
    'REFRESH_TOKEN_LIFETIME': timedelta(minutes=60),
    'ROTATE_REFRESH_TOKENS': True,
    'BLACKLIST_AFTER_ROTATION': False,
    'ALGORITHM': 'HS256',
    'SIGNING_KEY': SECRET_KEY,
    'VERIFYING_KEY': None,
    'AUTH_HEADER_TYPES': ('JWT',),
    'USER_ID_FIELD': 'id',
    'USER_ID_CLAIM': 'user_id',
    'AUTH_TOKEN_CLASSES': ('rest_framework_simplejwt.tokens.AccessToken',),
    'TOKEN_TYPE_CLAIM': 'token_type',
}

网址.py

urlpatterns = [
    path('', index ),
    .
    . 
    path('api/token/obtain/', jwt_views.TokenObtainPairView.as_view(), \
        name='token_create'),  # override sjwt stock token
    path('api/token/refresh/', jwt_views.TokenRefreshView.as_view(), \
        name='token_refresh'),
    url(r'^.*/$', index),
]

axiosApi.js

// axiosApi.js

import axios from 'axios';
import axiosRefreshInstance from './axiosRefreshApi.js';

const csrftoken = getCookie('csrftoken');

const axiosInstance = axios.create({
  baseURL: 'http://127.0.0.1:8000/',
  timeout: 5000,
  headers: {
    'Authorization': "JWT " + localStorage.getItem('access_token'),
    'Content-Type': 'application/json',
    'accept': 'application/json',
    'X-CSRFToken': csrftoken
  }
});


axiosInstance.interceptors.response.use(
  response => response,
  error => {
    console.log("Axios Interceptor response error happened: ");
    console.log("error.config/originalRequest:");
    console.log(error.toJSON());
    const originalRequest = error.config;

    if (error.response.status === 401 &&
      error.response.statusText === "Unauthorized") {

      if (localStorage.getItem('refresh_token')) {
        const refresh_token = localStorage.getItem('refresh_token');
        return axiosRefreshInstance
          .post('api/token/refresh/', {refresh: refresh_token})
          .then((response) => {
            localStorage.setItem('access_token', response.data.access);
            localStorage.setItem('refresh_token', response.data.refresh);
            console.log("new refresh token: ");
            console.log(localStorage.getItem('refresh_token'));
            axiosInstance.defaults.headers['Authorization'] = "JWT " +
              response.data.access;
            originalRequest.headers['Authorization'] = "JWT " +
              response.data.access;
            return axiosInstance(originalRequest);
          })
          .catch(err => {
            // 2. access:invalid and refresh:invalid => redirect to login page
            console.log("Refresh token is expired.");
            console.log(err.config);
          });
      } else {
        console.log("Refresh token is MISSING");
      }
    }
    return Promise.reject(error);
  }
);

export default axiosInstance;

登录.js

import React, { Component } from "react";
import axiosInstance from "../axiosApi.js";

class Login extends Component {
  constructor(props) {
    super(props);
    this.state = {username: "", password: ""};
    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  handleChange(event) {
    //TODO:Don't store login and password in state. 
    //TODO:Implement the spec for onChange
    this.setState({[event.target.name]: event.target.value});
  }

  handleSubmit(event) {
    event.preventDefault();
    const url = "api/token/obtain/";
    const csrftoken = getCookie('csrftoken');
    var i, requestBody={};
    for (i=0; i < event.target.length; i++) {
      requestBody[event.target.elements[i].name] = event.target.elements[i].value;
    }
    const response = axiosInstance.post('api/token/obtain/', requestBody)
      .then(response => {
        axiosInstance.defaults.headers['Authorization'] = "JWT " + response.data.access;
        localStorage.setItem('access_token', response.data.access);
        localStorage.setItem('refresh_token', response.data.refresh);
        })
      .catch (error => {
        throw error;
      })
  }

  render() {
    return (
      <div>Login
        <form onSubmit={this.handleSubmit}>
          <label>
            Username:
            <input name="username" type="text"
              value={this.state.username} onChange={this.handleChange}/>
          </label>
          <label>
            Password:
            <input name="password" type="password"
              value={this.state.password} onChange={this.handleChange}/>
          </label>
          <input type="submit" value="Submit"/>
        </form>
      </div>
    )
  }
}
export default Login;

上述实现使用本地存储来保存 JWT 令牌,这会将应用程序暴露于 XSS 攻击。一种建议的解决方案是将令牌存储在“httponly”cookie 中。然而,javascript 和 React 无法访问 httponly cookie。那么如何使用它呢?我也不知道如何设置 rest_framework_simpleJWT 将这些 cookie 发送回浏览器。任何帮助将不胜感激。

标签: javascriptreactjsauthenticationjwtxss

解决方案


推荐阅读