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

Spring Boot+React费用添加异常:无法获取用户邮箱数据

问题:Spring Boot与React集成实现费用添加功能时无法获取登录用户邮箱

问题现象

后端通过Postman调用添加费用接口正常,但与React前端联调时失败;控制台日志显示系统使用anonymousUser作为邮箱查询用户,触发UsernameNotFoundException。

后端代码

UserRepository

@Repository
public interface UserRepository extends JpaRepository<User, Long>{
    Boolean existsByEmail(String email);
    Optional<User> findByEmail(String email);
}

CustomUserDetailsService

@Service
public class CustomUserDetailsService implements UserDetailsService {
    @Autowired
    private UserRepository userRepository;
    @Override
    public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
        User existingUser = userRepository.findByEmail(email).orElseThrow(() -> new UsernameNotFoundException("User not found for the email:"+email));
        return new org.springframework.security.core.userdetails.User(existingUser.getEmail(), existingUser.getPassword(), getGrantedAuthority(existingUser));
    }
    private Collection<GrantedAuthority> getGrantedAuthority(User user) {
        Collection<GrantedAuthority> authorities = new ArrayList<>();
        authorities.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
        return authorities;
    }
}

ExpenseService与实现类

public interface ExpenseService {
    Expense saveExpenseDetails(Expense expense);
}

@Service
public class ExpenseServiceImpl implements ExpenseService {
    @Autowired
    private ExpenseRepository expenseRepo;
    @Autowired
    private UserService userService;
    @Override
    public Expense saveExpenseDetails(Expense expense) {
        expense.setUser(userService.getLoggedInUser());
        return expenseRepo.save(expense);
    }
}

UserService与实现类

@Service
public class UserServiceImpl implements UserService {
    @Autowired
    private PasswordEncoder bcryptEncoder;
    @Autowired
    private UserRepository userRepository;
    @Override
    public User getLoggedInUser() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        String email = authentication.getName();
        return userRepository.findByEmail(email).orElseThrow(() ->new UsernameNotFoundException("User not found for the email "+email));
    }
}

控制器接口

@ResponseStatus(value = HttpStatus.CREATED)
@PostMapping("/expenses")
public Expense saveExpenseDetails(@Valid @RequestBody Expense expense) {
    return expenseService.saveExpenseDetails(expense);
}

前端代码

import React, { Component } from "react";
import Base from "./Base";
import axios from "axios";
import { toast } from "react-toastify";

class AddExpenseComponent extends Component {
  constructor(props) {
    super(props);
    this.state = {
      name: "",
      amount: "",
      category: "",
      date: "",
    };
    this.changeNameHandler = this.changeNameHandler.bind(this);
    this.changePriceHandler = this.changePriceHandler.bind(this);
    this.changecategoryHandler = this.changecategoryHandler.bind(this);
    this.changeDateHandler = this.changeDateHandler.bind(this);
    this.saveOrUpdateExpense = this.saveOrUpdateExpense.bind(this);
  }

  saveOrUpdateExpense = (e) => {
    e.preventDefault();
    let expense = {
      name: this.state.name,
      amount: this.state.amount,
      category: this.state.category,
      date: this.state.date,
    };
    console.log("expense => " + JSON.stringify(expense));
    axios.post("http://localhost:8080/api/v1/expenses", expense,{ headers: { "Authorization": localStorage.getItem("jwtToken")}});
    toast.success("Expense added successfully")
  };

  changeNameHandler = (event) => {
    this.setState({ name: event.target.value });
  };

  changePriceHandler = (event) => {
    this.setState({ amount: event.target.value });
  };

  changecategoryHandler = (event) => {
    this.setState({ category: event.target.value });
  };

  changeDateHandler = (event) => {
    this.setState({ date: event.target.value });
  };

  cancel() {
    this.props.history.push("/user/dashboard");
  }

  render() {
    return (
      <Base>
      <div>
        <br></br>
        <div className="container">
          <div className="row">
            <div className="card col-md-6 offset-md-3 offset-md-3">
              <h3 className="text-center">Add Expense</h3>
              <div className="card-body">
                <form>
                  <div className="form-group">
                    <label> Name: </label>
                    <input
                      placeholder="Name"
                      name="name"
                      className="form-control"
                      required
                      value={this.state.name}
                      onChange={this.changeNameHandler}
                    />
                  </div>
                  <div className="form-group">
                    <label> Price: </label>
                    <input
                      placeholder="Price"
                      name="amount"
                      className="form-control"
                      required
                      value={this.state.amount}
                      onChange={this.changePriceHandler}
                    />
                  </div>
                  <div className="form-group">
                    <label for="category"> category: </label>
                    <select
                      class="form-control"
                      id="category"
                      value={this.state.category}
                      required
                      onChange={this.changecategoryHandler}
                    >
                      <option value="Food">Food</option>
                      <option value="Toys">Toys</option>
                      <option value="Stationary">Stationary</option>
                      <option value="Electronics">Electronics</option>
                      <option value="Education">Education</option>
                      <option value="Insurance">Insurance</option>
                      <option value="Clothing">Clothing</option>
                      <option value="Transportation">Transportation</option>
                      <option value="Medicine">Medicine</option>
                      <option value="Groceries">Groceries</option>
                      <option value="House Utilities">House Utilities</option>
                    </select>
                  </div>
                  <div className="form-group">
                    <label> Date: </label>{" "}
                    <input
                      placeholder="Date"
                      type="date"
                      name="date"
                      className="form-control"
                      required
                      value={this.state.date}
                      onChange={this.changeDateHandler}
                    />{" "}
                  </div>
                  <br></br>
                  <button
                    className="btn btn-success"
                    onClick={this.saveOrUpdateExpense}
                  >
                    Save
                  </button>
                  <button
                    className="btn btn-danger"
                    onClick={this.cancel.bind(this)}
                    style={{ marginLeft: "10px" }}
                  >
                    Cancel
                  </button>
                </form>
              </div>
            </div>
          </div>
        </div>
      </div>
      </Base>
    );
  }
}

export default AddExpenseComponent;

错误日志(翻译后)

org.hibernate.SQL: select user0_.id as id1_1_, user0_.address as address2_1_, user0_.email as email3_1_, user0_.name as name4_1_, user0_.password as password5_1_, user0_.phoneno as phoneno6_1_ from users_tables user0_ where user0_.email=?
o.h.type.descriptor.sql.BasicBinder  : 绑定参数[1]为[VARCHAR] - [anonymousUser]
.m.m.a.ExceptionHandlerExceptionResolver: 已解决异常[org.springframework.security.core.userdetails.UsernameNotFoundException: 未找到邮箱为anonymousUser的用户]

解决方案

1. 修正前端JWT令牌格式

Spring Security默认要求JWT令牌在Authorization头中以Bearer 前缀开头,当前前端代码直接使用localStorage中的令牌,缺少前缀。修改前端axios请求代码:

axios.post("http://localhost:8080/api/v1/expenses", expense, { 
  headers: { 
    "Authorization": `Bearer ${localStorage.getItem("jwtToken")}`
  }
});

2. 验证令牌有效性

在前端控制台执行console.log(localStorage.getItem("jwtToken")),确认令牌已正确存储且未过期。若令牌不存在,检查登录逻辑是否将令牌存入localStorage。

3. 配置后端跨域支持

若浏览器拦截了请求头,导致Authorization无法传递到后端,需配置CORS允许携带凭证和自定义头:

@Configuration
public class CorsConfig {
    @Bean
    public CorsFilter corsFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowCredentials(true);
        config.addAllowedOriginPattern("*"); // 生产环境建议指定前端域名
        config.addAllowedHeader("*");
        config.addAllowedMethod("*");
        source.registerCorsConfiguration("/**", config);
        return new CorsFilter(source);
    }
}

4. 检查后端JWT认证配置

确保后端已配置JWT过滤器,能正确解析Authorization头中的令牌并将用户信息存入SecurityContext。示例过滤器核心逻辑:

@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
    @Autowired
    private JwtTokenProvider tokenProvider;
    @Autowired
    private CustomUserDetailsService userDetailsService;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String token = getJwtFromRequest(request);
        if (StringUtils.hasText(token) && tokenProvider.validateToken(token)) {
            String email = tokenProvider.getEmailFromToken(token);
            UserDetails userDetails = userDetailsService.loadUserByUsername(email);
            UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
            authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
            SecurityContextHolder.getContext().setAuthentication(authentication);
        }
        filterChain.doFilter(request, response);
    }

    private String getJwtFromRequest(HttpServletRequest request) {
        String bearerToken = request.getHeader("Authorization");
        if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
            return bearerToken.substring(7);
        }
        return null;
    }
}

并在SecurityFilterChain中添加该过滤器:

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http.csrf(csrf -> csrf.disable())
        .authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
        .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
        .addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
    return http.build();
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.10 18:05:17