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
相关产品推荐
相关产品推荐

