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

JWT认证下前端篡改本地存储越权编辑帖子的解决方案咨询

解决帖子编辑越权问题的方案

问题背景

我基于React+Node.js开发带JWT认证的帖子编辑功能,登录用户信息存储在localStorage中。目前发现用户可篡改localStorage内的username字段,匹配目标帖子的作者名后就能编辑不属于自己的帖子,存在越权风险,需要优化设计避免这个问题。

现有代码

React 帖子编辑组件代码

import React, { useEffect } from 'react';
import { useState } from 'react';
import { useParams, useNavigate } from 'react-router-dom';
import axios from 'axios';
import { useContext } from 'react';
import { AuthContext } from '../context/authContext';
import config from '../config.json';

const Edit = () => {
  const { currentUser } = useContext(AuthContext);
  const navigate = useNavigate();
  const { id } = useParams();

  const [post, setPost] = useState({
    title: '',
    desc: '',
    cat: '',
  });

  const handleChange = (e) => {
    setPost((prev) => ({ ...prev, [e.target.name]: e.target.value }));
  };

  const handleSubmit = async (e) => {
    e.preventDefault();
    try {
      await axios.put(`${config.SERVER_URL}/api/posts/${id}`, post);
      navigate('/');
    } catch (err) {
      console.log(err);
    }
  };

  const getData = async () => {
    try {
      const data = await axios.get(`${config.SERVER_URL}/api/posts/${id}`);
      console.log(data.data[0]);
      setPost(data.data[0]);
    } catch (err) {
      console.log(err);
    }
  };

  useEffect(() => {
    getData();
  }, []);

  if (currentUser) {
    return (
      <>
        {currentUser.username === post.username ? (
          <>
            <div className='auth text-center'>
              <div className='display-6 m-4'>Update Post</div>
              <form>
                <div>
                  <input
                    type='text'
                    className='m-4 p-2 rounded-4'
                    placeholder='Title'
                    value={post.title}
                    name='title'
                    onChange={handleChange}
                  />
                </div>
                <div>
                  <textarea
                    type='text'
                    className='m-4 p-2 rounded-4'
                    placeholder='Description'
                    value={post.desc}
                    name='desc'
                    onChange={handleChange}
                  />
                </div>
                <div>
                  <input
                    type='text'
                    className='m-4 p-2 rounded-4'
                    placeholder='Category'
                    value={post.cat}
                    name='cat'
                    onChange={handleChange}
                  />
                </div>
                <div className='text-center m-4'>
                  <button className='btn btn-primary' onClick={handleSubmit}>
                    Update Post
                  </button>
                </div>
              </form>
            </div>
          </>
        ) : (
          <>
            <div className='auth display-6'>You cannot edit this post!</div>
          </>
        )}
      </>
    );
  } else {
    return (
      <>
        <div className='auth display-6'>Not Logged in!</div>
      </>
    );
  }
};

export default Edit;

Auth Context 代码

import axios from 'axios';
import { createContext, useEffect, useState } from 'react';
import config from '../config.json';

export const AuthContext = createContext();

export const AuthContexProvider = ({ children }) => {
  const [currentUser, setCurrentUser] = useState(
    JSON.parse(localStorage.getItem('user')) || null
  );

  const login = async (inputs) => {
    const res = await axios.post(`${config.SERVER_URL}/api/auth/login`, inputs);
    setCurrentUser(res.data);
  };

  const logout = async (inputs) => {
    await axios.post(`${config.SERVER_URL}/api/auth/logout`);
    setCurrentUser(null);
  };

  useEffect(() => {
    localStorage.setItem('user', JSON.stringify(currentUser));
  }, [currentUser]);

  return (
    <AuthContext.Provider value={{ currentUser, login, logout }}>
      {children}
    </AuthContext.Provider>
  );
};

Node.js 后端更新帖子接口代码

export const updatePost = (req, res) => {
  const token = req.cookies.access_token;
  if (!token) return res.status(401).json('Not authenticated!');

  jwt.verify(token, 'jwtkey', (err, userInfo) => {
    if (err) return res.status(403).json('Token is not valid!');

    const values = [req.body.title, req.body.desc, req.body.cat];
    const id = req.params.id;
    const q = 'Update posts set `title`=?,`desc`=?,`cat`=? where id = ? ;';

    db.query(q, [...values, id], (err, data) => {
      if (err) return res.status(400).send(err);

      return res.status(200).send(data);
    });
  });
};

核心优化方案

1. 后端强制校验权限(必须做,前端校验仅为用户体验)

前端的任何校验都可以被篡改,所以权限校验必须在后端完成。修改后端updatePost接口,在更新前先查询该帖子的作者ID,和JWT解析出的当前用户ID对比,只有匹配才允许更新:

export const updatePost = (req, res) => {
  const token = req.cookies.access_token;
  if (!token) return res.status(401).json('未认证!');

  jwt.verify(token, 'jwtkey', (err, userInfo) => {
    if (err) return res.status(403).json('Token无效!');

    const postId = req.params.id;
    // 先查询帖子的作者ID
    const checkOwnerQ = 'SELECT user_id FROM posts WHERE id = ?';
    db.query(checkOwnerQ, [postId], (err, postData) => {
      if (err) return res.status(400).send(err);
      // 帖子不存在或作者ID不匹配当前用户
      if (!postData.length || postData[0].user_id !== userInfo.id) {
        return res.status(403).json('无权编辑此帖子!');
      }
      // 权限校验通过,执行更新
      const values = [req.body.title, req.body.desc, req.body.cat];
      const updateQ = 'UPDATE posts SET `title`=?,`desc`=?,`cat`=? WHERE id = ?';
      db.query(updateQ, [...values, postId], (err, data) => {
        if (err) return res.status(400).send(err);
        return res.status(200).json('帖子更新成功!');
      });
    });
  });
};

注意:你的JWT在签发时,需要把用户的id(而不只是username)存入payload,比如登录接口签发token时:

// 登录接口示例
jwt.sign({id: user.id, username: user.username}, 'jwtkey', {expiresIn: '1d'}, (err, token) => {
  // 返回token给前端,存入cookie
});

2. 前端优化(提升用户体验,减少无效请求)

虽然后端已经做了强制校验,但前端可以优化逻辑,避免用户输入后才发现无权编辑:

  • 不要依赖localStorage中的username做校验,可使用jwt-decode库解析JWT中的用户ID,和帖子返回的作者ID对比(仅为体验优化,不能替代后端校验)。
  • 或者在后端的帖子详情接口中,直接返回当前用户是否有权限编辑该帖子的标识,前端根据这个标识展示内容。

修改前端getData函数示例(假设后端返回的帖子数据包含authorId字段):

const getData = async () => {
  try {
    const data = await axios.get(`${config.SERVER_URL}/api/posts/${id}`);
    setPost(data.data[0]);
  } catch (err) {
    console.log(err);
  }
};

前端判断时改为对比用户ID:

{currentUser.id === post.authorId ? (...) : (...)}

3. 加强前端用户信息存储安全性

  • 避免在localStorage中存储敏感的用户权限信息,必要的用户标识优先存入HttpOnly Cookie(你的后端已经用Cookie存token,这是正确的做法)。
  • 不要在前端直接暴露用户的敏感ID或权限字段,只保留必要的展示信息。

关键总结

  • 后端权限校验是核心:所有操作必须在后端验证当前用户是否拥有对应权限,前端校验仅用于优化用户体验,不能作为安全屏障。
  • JWT payload中要包含唯一且不可篡改的用户标识(比如用户ID),而不是仅用可被篡改的username。
  • 永远不要信任前端传来的任何权限相关信息,所有权限判断都要基于后端从数据库或可靠来源获取的数据。

内容的提问来源于stack exchange,提问作者Rohith Badrinath

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 06:44:59