Java+React前后端分离订餐系统实战:JWT鉴权完整实现



本文将深入解析基于Java+React的前后端分离订餐系统,重点介绍JWT鉴权的完整实现方案,包含丰富的代码实例和最佳实践。



1. 系统架构概述


在当今的Web开发领域,前后端分离架构已成为主流选择。本文将介绍的订餐系统采用Spring Boot作为后端框架,React作为前端框架,通过RESTful API进行数据交互,使用JWT(JSON Web Token)进行身份认证和授权。


技术栈介绍


后端技术栈:
- Spring Boot 2.7+:快速开发框架
- Spring Security:安全框架
- JWT:身份认证
- MySQL:数据存储
- Maven:依赖管理


前端技术栈:
- React 18:用户界面库
- Axios:HTTP客户端
- React Router:路由管理
- Ant Design:UI组件库


2. JWT鉴权原理与优势


JWT是一种开放标准(RFC 7519),用于在各方之间安全地传输信息作为JSON对象。JWT由三部分组成:头部(Header)、载荷(Payload)和签名(Signature)。


JWT的优势:
- 无状态:服务器不需要存储会话信息
- 可扩展性:易于跨域认证
- 安全性:基于数字签名,防止篡改


3. 后端JWT实现详解


3.1 依赖配置


首先在pom.xml中添加必要的依赖:


```xml



org.springframework.boot
spring-boot-starter-web


<!-- Spring Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>

<!-- JWT支持 -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>

<!-- 数据库相关 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>


```


3.2 JWT工具类实现


创建JWT工具类,负责Token的生成、解析和验证:


```java
@Component
public class JwtTokenProvider {


private final String jwtSecret = "your-secret-key-change-in-production";
private final long jwtExpirationInMs = 3600000; // 1小时

/
生成JWT Token
/
public String generateToken(Authentication authentication) {
UserPrincipal userPrincipal = (UserPrincipal) authentication.getPrincipal();
Date now = new Date();
Date expiryDate = new Date(now.getTime() + jwtExpirationInMs);

return Jwts.builder()
.setSubject(Long.toString(userPrincipal.getId()))
.setIssuedAt(new Date())
.setExpiration(expiryDate)
.signWith(SignatureAlgorithm.HS512, jwtSecret)
.compact();
}

/
从Token中获取用户ID
/
public Long getUserIdFromJWT(String token) {
Claims claims = Jwts.parser()
.setSigningKey(jwtSecret)
.parseClaimsJws(token)
.getBody();

return Long.parseLong(claims.getSubject());
}

/
验证Token是否有效
/
public boolean validateToken(String authToken) {
try {
Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(authToken);
return true;
} catch (SignatureException ex) {
logger.error("Invalid JWT signature");
} catch (MalformedJwtException ex) {
logger.error("Invalid JWT token");
} catch (ExpiredJwtException ex) {
logger.error("Expired JWT token");
} catch (UnsupportedJwtException ex) {
logger.error("Unsupported JWT token");
} catch (IllegalArgumentException ex) {
logger.error("JWT claims string is empty");
}
return false;
}

}
```


3.3 Spring Security配置


配置Spring Security以集成JWT认证:


```java
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {


@Autowired
private CustomUserDetailsService customUserDetailsService;

@Autowired
private JwtAuthenticationEntryPoint unauthorizedHandler;

@Bean
public JwtAuthenticationFilter jwtAuthenticationFilter() {
return new JwtAuthenticationFilter();
}

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(customUserDetailsService).passwordEncoder(passwordEncoder());
}

@Bean(BeanIds.AUTHENTICATION_MANAGER)
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}

@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}

@Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable()
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests()
.antMatchers("/api/auth/").permitAll()
.antMatchers("/api/menu/").permitAll()
.antMatchers("/api/order/").authenticated()
.antMatchers("/api/admin/").hasRole("ADMIN")
.anyRequest().authenticated();

http.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
}

}
```


3.4 JWT认证过滤器


创建JWT认证过滤器,用于拦截请求并验证Token:


```java
public class JwtAuthenticationFilter extends OncePerRequestFilter {


@Autowired
private JwtTokenProvider tokenProvider;

@Autowired
private CustomUserDetailsService customUserDetailsService;

@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
try {
String jwt = getJwtFromRequest(request);

if (StringUtils.hasText(jwt) && tokenProvider.validateToken(jwt)) {
Long userId = tokenProvider.getUserIdFromJWT(jwt);

UserDetails userDetails = customUserDetailsService.loadUserById(userId);
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));

SecurityContextHolder.getContext().setAuthentication(authentication);
}
} catch (Exception ex) {
logger.error("Could not set user authentication in security context", ex);
}

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;
}

}
```


3.5 用户认证接口


实现用户登录和注册的API接口:


```java
@RestController
@RequestMapping("/api/auth")
public class AuthController {


@Autowired
private AuthenticationManager authenticationManager;

@Autowired
private UserRepository userRepository;

@Autowired
private PasswordEncoder passwordEncoder;

@Autowired
private JwtTokenProvider tokenProvider;

@PostMapping("/login")
public ResponseEntity<?> authenticateUser(@Valid @RequestBody LoginRequest loginRequest) {
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
loginRequest.getUsername(),
loginRequest.getPassword()
)
);

SecurityContextHolder.getContext().setAuthentication(authentication);
String jwt = tokenProvider.generateToken(authentication);

return ResponseEntity.ok(new JwtAuthenticationResponse(jwt));
}

@PostMapping("/register")
public ResponseEntity<?> registerUser(@Valid @RequestBody SignUpRequest signUpRequest) {
if (userRepository.existsByUsername(signUpRequest.getUsername())) {
return new ResponseEntity<>("用户名已存在!", HttpStatus.BAD_REQUEST);
}

if (userRepository.existsByEmail(signUpRequest.getEmail())) {
return new ResponseEntity<>("邮箱已被使用!", HttpStatus.BAD_REQUEST);
}

User user = new User(signUpRequest.getUsername(),
signUpRequest.getEmail(),
signUpRequest.getPassword());

user.setPassword(passwordEncoder.encode(user.getPassword()));
user.setRole("USER"); // 默认用户角色

User result = userRepository.save(user);

URI location = ServletUriComponentsBuilder
.fromCurrentContextPath().path("/api/users/{username}")
.buildAndExpand(result.getUsername()).toUri();

return ResponseEntity.created(location).body("用户注册成功");
}

}
```


3.6 订单相关接口


实现需要JWT认证的订单管理接口:


```java
@RestController
@RequestMapping("/api/order")
public class OrderController {


@Autowired
private OrderService orderService;

@PostMapping("/create")
public ResponseEntity<?> createOrder(@Valid @RequestBody OrderRequest orderRequest,
@CurrentUser UserPrincipal currentUser) {
Order order = orderService.createOrder(orderRequest, currentUser.getId());

return ResponseEntity.ok(order);
}

@GetMapping("/user")
public ResponseEntity<?> getUserOrders(@CurrentUser UserPrincipal currentUser) {
List<Order> orders = orderService.getOrdersByUserId(currentUser.getId());

return ResponseEntity.ok(orders);
}

@GetMapping("/{orderId}")
public ResponseEntity<?> getOrderDetail(@PathVariable Long orderId,
@CurrentUser UserPrincipal currentUser) {
Order order = orderService.getOrderByIdAndUserId(orderId, currentUser.getId());

if (order == null) {
return ResponseEntity.notFound().build();
}

return ResponseEntity.ok(order);
}

}
```


4. 前端React实现


4.1 项目结构和依赖


前端项目使用Create React App创建,主要依赖如下:


json
{
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.8.0",
"axios": "^1.3.0",
"antd": "^5.3.0",
"js-cookie": "^3.0.1"
}
}


4.2 Token管理工具


创建Token管理工具类,用于处理Token的存储和获取:


```javascript
// utils/token.js
const TOKEN_KEY = 'jwt_token';


export const getToken = () => {
return localStorage.getItem(TOKEN_KEY);
};


export const setToken = (token) => {
localStorage.setItem(TOKEN_KEY, token);
};


export const removeToken = () => {
localStorage.removeItem(TOKEN_KEY);
};


export const isAuthenticated = () => {
const token = getToken();
if (!token) return false;


// 简单的Token过期检查(实际应该解析JWT检查exp)
try {
const payload = JSON.parse(atob(token.split('.')[1]));
return payload.exp 1000 > Date.now();
} catch (error) {
return false;
}

};
```


4.3 Axios配置和请求拦截


配置Axios实例,自动添加JWT Token到请求头:


```javascript
// utils/axios.js
import axios from 'axios';
import { getToken, removeToken } from './token';
import { message } from 'antd';


// 创建axios实例
const instance = axios.create({
baseURL: process.env.REACT_APP_API_BASE_URL || 'http://localhost:8080/api',
timeout: 10000,
});


// 请求拦截器
instance.interceptors.request.use(
(config) => {
const token = getToken();
if (token) {
config.headers.Authorization = Bearer ${token};
}
return config;
},
(error) => {
return Promise.reject(error);
}
);


// 响应拦截器
instance.interceptors.response.use(
(response) => {
return response.data;
},
(error) => {
if (error.response) {
switch (error.response.status) {
case 401:
message.error('登录已过期,请重新登录');
removeToken();
window.location.href = '/login';
break;
case 403:
message.error('没有权限访问该资源');
break;
case 500:
message.error('服务器内部错误');
break;
default:
message.error(error.response.data.message || '请求失败');
}
} else {
message.error('网络错误,请检查网络连接');
}
return Promise.reject(error);
}
);


export default instance;
```


4.4 路由保护和权限控制


实现路由保护组件,确保只有登录用户才能访问受保护的路由:


```javascript
// components/PrivateRoute.js
import React from 'react';
import { Navigate } from 'react-router-dom';
import { isAuthenticated } from '../utils/token';


const PrivateRoute = ({ children, requiredRole }) => {
const isAuth = isAuthenticated();


if (!isAuth) {
return <Navigate to="/login" replace />;
}

// 可以在这里添加角色权限检查
// if (requiredRole && !hasRequiredRole(requiredRole)) {
// return <Navigate to="/unauthorized" replace />;
// }

return children;

};


export default PrivateRoute;
```


4.5 用户登录组件


实现用户登录界面和逻辑:


```javascript
// pages/Login.js
import React, { useState } from 'react';
import { Form, Input, Button, Card, message } from 'antd';
import { UserOutlined, LockOutlined } from '@ant-design/icons';
import { useNavigate, Link } from 'react-router-dom';
import axios from '../utils/axios';
import { setToken } from '../utils/token';


const Login = () => {
const [loading, setLoading] = useState(false);
const navigate = useNavigate();


const onFinish = async (values) => {
setLoading(true);
try {
const response = await axios.post('/auth/login', values);
setToken(response.token);
message.success('登录成功!');
navigate('/');
} catch (error) {
message.error('登录失败,请检查用户名和密码');
} finally {
setLoading(false);
}
};

return (
<div style={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
minHeight: '100vh',
background: 'f0f2f5'
}}>
<Card title="用户登录" style={{ width: 400 }}>
<Form
name="login"
onFinish={onFinish}
autoComplete="off"
>
<Form.Item
name="username"
rules={[{ required: true, message: '请输入用户名!' }]}
>
<Input
prefix={<UserOutlined />}
placeholder="用户名"
size="large"
/>
</Form.Item>

<Form.Item
name="password"
rules={[{ required: true, message: '请输入密码!' }]}
>
<Input.Password
prefix={<LockOutlined />}
placeholder="密码"
size="large"
/>
</Form.Item>

<Form.Item>
<Button
type="primary"
htmlType="submit"
loading={loading}
style={{ width: '100%' }}
size="large"
>
登录
</Button>
</Form.Item>
</Form>

<div style={{ textAlign: 'center' }}>
<Link to="/register">没有账号?立即注册</Link>
</div>
</Card>
</div>
);

};


export default Login;
```


4.6 订单管理组件


实现订单列表和下单功能:


```javascript
// pages/Order.js
import React, { useState, useEffect } from 'react';
import { Table, Button, Card, message, Modal, Form, Input, InputNumber } from 'antd';
import { ShoppingCartOutlined } from '@ant-design/icons';
import axios from '../utils/axios';


const Order = () => {
const [orders, setOrders] = useState([]);
const [loading, setLoading] = useState(false);
const [menuItems, setMenuItems] = useState([]);
const [orderModalVisible, setOrderModalVisible] = useState(false);
const [selectedItems, setSelectedItems] = useState([]);
const [form] = Form.useForm();


useEffect(() => {
fetchOrders();
fetchMenu();
}, []);

const fetchOrders = async () => {
setLoading(true);
try {
const response = await axios.get('/order/user');
setOrders(response);
} catch (error) {
message.error('获取订单失败');
} finally {
setLoading(false);
}
};

const fetchMenu = async () => {
try {
const response = await axios.get('/menu');
setMenuItems(response);
} catch (error) {
message.error('获取菜单失败');
}
};

const handleCreateOrder = async (values) => {
try {
const orderData = {
items: selectedItems,
address: values.address,
note: values.note || ''
};

await axios.post('/order/create', orderData);
message.success('下单成功!');
setOrderModalVisible(false);
form.resetFields();
setSelectedItems([]);
fetchOrders(); // 刷新订单列表
} catch (error) {
message.error('下单失败');
}
};

const columns = [
{
title: '订单号',
dataIndex: 'orderNumber',
key: 'orderNumber',
},
{
title: '总金额',
dataIndex: 'totalAmount',
key: 'totalAmount',
render: (amount) => `¥${amount}`
},
{
title: '状态',
dataIndex: 'status',
key: 'status',
render: (status) => {
const statusMap = {
'PENDING': '待处理',
'CONFIRMED': '已确认',
'DELIVERED': '已送达',
'CANCELLED': '已取消'
};
return statusMap[status] || status;
}
},
{
title: '下单时间',
dataIndex: 'createdAt',
key: 'createdAt',
render: (date) => new Date(date).toLocaleString()
}
];

return (
<div>
<Card
title="我的订单"
extra={
<Button
type="primary"
icon={<ShoppingCartOutlined />}
onClick={() => setOrderModalVisible(true)}
>
新建订单
</Button>
}
>
<Table
columns={columns}
dataSource={orders}
loading={loading}
rowKey="id"
/>
</Card>

<Modal
title="创建订单"
open={orderModalVisible}
onCancel={() => setOrderModalVisible(false)}
footer={null}
width={600}
>
<Form form={form} onFinish={handleCreateOrder} layout="vertical">
{/ 菜单选择部分 /}
<div style={{ marginBottom: 16 }}>
<h4>选择菜品</h4>
{menuItems.map(item => (
<div key={item.id} style={{
display: 'flex',
justifyContent: 'space-between',
marginBottom: 8,
padding: 8,
border: '1px solid f0f0f0',
borderRadius: 4
}}>
<span>{item.name} - ¥{item.price}</span>
<InputNumber
min={0}
max={10}
defaultValue={0}
onChange={(value) => {
const newItems = selectedItems.filter(i => i.itemId !== item.id);
if (value > 0) {
newItems.push({
itemId: item.id,
quantity: value,
price: item.price
});
}
setSelectedItems(newItems);
}}
/>
</div>
))}
</div>

<Form.Item
name="address"
label="配送地址"
rules={[{ required: true, message: '请输入配送地址' }]}
>
<Input placeholder="请输入详细的配送地址" />
</Form.Item>

<Form.Item name="note" label="备注">
<Input.TextArea placeholder="特殊要求或备注" rows={3} />
</Form.Item>

<Form.Item>
<Button type="primary" htmlType="submit" style={{ width: '100%' }}>
提交订单
</Button>
</Form.Item>
</Form>
</Modal>
</div>
);

};


export default Order;
```


5. 系统部署和优化建议


5.1 安全最佳实践



  1. 使用HTTPS:在生产环境中强制使用HTTPS

  2. 设置合理的Token过期时间:根据安全要求调整Token有效期

  3. 实现Refresh Token机制:减少用户重复登录

  4. 敏感操作二次验证:重要操作需要额外验证


5.2 性能优化建议



  1. 后端API分页:大数据量查询使用分页

  2. 前端路由懒加载:使用React.lazy实现组件懒加载

  3. 图片和静态资源优化:使用CDN加速资源加载

  4. 数据库索引优化:对常用查询字段添加索引


6. 总结


本文详细介绍了基于Java+React的前后端分离订餐系统的完整实现,重点讲解了JWT鉴权机制的原理和实现方式。通过实际的代码示例,展示了如何在Spring Boot后端集成JWT认证,以及在React前端中处理用户认证状态和权限控制。


这种架构模式具有很好的扩展性和维护性,适合中大型项目的开发。读者可以根据实际需求,在此基础上进行功能扩展和优化,如添加支付集成、实时通知、推荐算法等高级功能。


完整项目源码可以访问GitHub仓库获取,建议在实际项目中根据具体需求进行调整和优化。


Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐