Skip to content

Commit 19ef4e8

Browse files
committed
refactor: 重构后端认证和响应处理逻辑
重构JWT认证过滤器,提取公共工具类处理认证逻辑 添加ResponseUtils统一处理响应格式 优化BoardController使用工具类简化代码 前端统一使用自定义axios实例 添加开发工具依赖和热重载配置
1 parent 2ebad3b commit 19ef4e8

11 files changed

Lines changed: 167 additions & 65 deletions

File tree

backend/pom.xml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,14 @@
7171
<optional>true</optional>
7272
</dependency>
7373

74+
<!-- Spring Boot DevTools for hot reload -->
75+
<dependency>
76+
<groupId>org.springframework.boot</groupId>
77+
<artifactId>spring-boot-devtools</artifactId>
78+
<scope>runtime</scope>
79+
<optional>true</optional>
80+
</dependency>
81+
7482
<!-- JWT -->
7583
<dependency>
7684
<groupId>io.jsonwebtoken</groupId>
@@ -103,6 +111,10 @@
103111
<plugin>
104112
<groupId>org.springframework.boot</groupId>
105113
<artifactId>spring-boot-maven-plugin</artifactId>
114+
<configuration>
115+
<fork>true</fork>
116+
<addResources>true</addResources>
117+
</configuration>
106118
</plugin>
107119
</plugins>
108120
</build>

backend/src/main/java/com/easydraw/controller/AppController.java

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,43 @@ public AppController(UserService userService, JwtService jwtService) {
2424
this.userService = userService;
2525
this.jwtService = jwtService;
2626
}
27+
28+
/**
29+
* 模糊处理邮箱地址
30+
* @param email 邮箱地址
31+
* @return 模糊处理后的邮箱地址
32+
*/
33+
private String maskEmail(String email) {
34+
if (email == null || email.isEmpty()) {
35+
return email;
36+
}
37+
int atIndex = email.indexOf('@');
38+
if (atIndex <= 2) {
39+
// 邮箱前缀太短,只显示第一个字符
40+
return email.substring(0, 1) + "***" + email.substring(atIndex);
41+
} else {
42+
// 显示前两个字符,其余用***替换
43+
return email.substring(0, 2) + "***" + email.substring(atIndex);
44+
}
45+
}
46+
47+
/**
48+
* 模糊处理ID
49+
* @param id ID字符串
50+
* @return 模糊处理后的ID
51+
*/
52+
private String maskId(String id) {
53+
if (id == null || id.isEmpty()) {
54+
return id;
55+
}
56+
if (id.length() <= 8) {
57+
// ID太短,只显示前4个字符
58+
return id.substring(0, Math.min(4, id.length())) + "***";
59+
} else {
60+
// 显示前4个字符和后4个字符,中间用***替换
61+
return id.substring(0, 4) + "***" + id.substring(id.length() - 4);
62+
}
63+
}
2764

2865
@GetMapping("/user")
2966
public Map<String, Object> getUser(@RequestHeader(value = "Authorization", required = false) String authorization) {
@@ -37,9 +74,9 @@ public Map<String, Object> getUser(@RequestHeader(value = "Authorization", requi
3774
UserDto userDto = userService.getUser(userId);
3875

3976
Map<String, Object> user = new HashMap<>();
40-
user.put("id", userDto.getId());
77+
user.put("id", maskId(userDto.getId()));
4178
user.put("name", userDto.getUsername());
42-
user.put("email", userDto.getEmail());
79+
user.put("email", maskEmail(userDto.getEmail()));
4380
user.put("role", userDto.getRole());
4481

4582
Map<String, Object> response = new HashMap<>();

backend/src/main/java/com/easydraw/controller/BoardController.java

Lines changed: 12 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@
22

33
import com.easydraw.dto.BoardDto;
44
import com.easydraw.service.BoardService;
5+
import com.easydraw.utils.ResponseUtils;
56
import org.springframework.beans.factory.annotation.Autowired;
7+
import org.springframework.security.authentication.InsufficientAuthenticationException;
68
import org.springframework.security.core.Authentication;
79
import org.springframework.security.core.context.SecurityContextHolder;
810
import org.springframework.web.bind.annotation.*;
911

10-
import java.util.HashMap;
1112
import java.util.List;
1213
import java.util.Map;
1314
import java.util.UUID;
@@ -27,63 +28,44 @@ private UUID getSecurityUUID() {
2728
// 从认证信息获取用户ID
2829
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
2930
if (authentication == null || !authentication.isAuthenticated()) {
30-
// 临时方案,实际应该抛出认证异常
31-
return UUID.fromString("550e8400-e29b-41d4-a716-446655440001");
31+
// 抛出认证异常
32+
throw new InsufficientAuthenticationException("User not authenticated");
3233
}
33-
// 这里应该从JWT token中获取用户ID,暂时使用用户名作为ID
34-
return UUID.fromString(authentication.getName());
34+
// 从JWT token中获取用户ID
35+
String principal = authentication.getName();
36+
return UUID.fromString(principal);
3537
}
3638

3739
@PostMapping
3840
public Map<String, Object> createBoard(@RequestBody BoardRequest request) {
3941
UUID creatorId = this.getSecurityUUID();
4042
BoardDto boardDto = boardService.createBoard(request.getName(), request.getCategory(), creatorId);
41-
42-
Map<String, Object> response = new HashMap<>();
43-
response.put("data", boardDto);
44-
response.put("status", "success");
45-
return response;
43+
return ResponseUtils.buildSuccessResponse(boardDto);
4644
}
4745

4846
@GetMapping
4947
public Map<String, Object> getBoards() {
5048
UUID creatorId = this.getSecurityUUID();
5149
List<BoardDto> boards = boardService.getBoards(creatorId);
52-
53-
Map<String, Object> response = new HashMap<>();
54-
response.put("data", boards);
55-
response.put("status", "success");
56-
return response;
50+
return ResponseUtils.buildSuccessResponse(boards);
5751
}
5852

5953
@GetMapping("/{id}")
6054
public Map<String, Object> getBoard(@PathVariable String id) {
6155
BoardDto boardDto = boardService.getBoard(id);
62-
63-
Map<String, Object> response = new HashMap<>();
64-
response.put("data", boardDto);
65-
response.put("status", "success");
66-
return response;
56+
return ResponseUtils.buildSuccessResponse(boardDto);
6757
}
6858

6959
@PutMapping("/{id}")
7060
public Map<String, Object> updateBoard(@PathVariable String id, @RequestBody BoardRequest request) {
7161
BoardDto boardDto = boardService.updateBoard(id, request.getName());
72-
73-
Map<String, Object> response = new HashMap<>();
74-
response.put("data", boardDto);
75-
response.put("status", "success");
76-
return response;
62+
return ResponseUtils.buildSuccessResponse(boardDto);
7763
}
7864

7965
@DeleteMapping("/{id}")
8066
public Map<String, Object> deleteBoard(@PathVariable String id) {
8167
boardService.deleteBoard(id);
82-
83-
Map<String, Object> response = new HashMap<>();
84-
response.put("data", null);
85-
response.put("status", "success");
86-
return response;
68+
return ResponseUtils.buildSuccessResponse(null);
8769
}
8870

8971
// 内部类,用于接收请求参数

backend/src/main/java/com/easydraw/filter/JwtAuthenticationFilter.java

Lines changed: 5 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
package com.easydraw.filter;
22

33
import com.easydraw.service.JwtService;
4+
import com.easydraw.utils.AuthenticationUtils;
45
import io.jsonwebtoken.Claims;
56
import org.springframework.beans.factory.annotation.Autowired;
6-
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
7-
import org.springframework.security.core.authority.SimpleGrantedAuthority;
8-
import org.springframework.security.core.context.SecurityContextHolder;
97
import org.springframework.stereotype.Component;
108
import org.springframework.web.filter.OncePerRequestFilter;
119

@@ -14,7 +12,6 @@
1412
import jakarta.servlet.http.HttpServletRequest;
1513
import jakarta.servlet.http.HttpServletResponse;
1614
import java.io.IOException;
17-
import java.util.Collections;
1815

1916
@Component
2017
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@@ -29,25 +26,11 @@ public JwtAuthenticationFilter(JwtService jwtService) {
2926
@Override
3027
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
3128
String authorizationHeader = request.getHeader("Authorization");
29+
String token = AuthenticationUtils.extractTokenFromHeader(authorizationHeader);
3230

33-
if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
34-
String token = authorizationHeader.substring(7);
35-
36-
if (jwtService.validateToken(token)) {
37-
Claims claims = jwtService.extractClaims(token);
38-
String username = claims.getSubject();
39-
String role = (String) claims.get("role");
40-
41-
// 创建认证对象
42-
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
43-
username,
44-
null,
45-
Collections.singletonList(new SimpleGrantedAuthority("ROLE_" + role.toUpperCase()))
46-
);
47-
48-
// 设置认证信息到上下文
49-
SecurityContextHolder.getContext().setAuthentication(authentication);
50-
}
31+
if (token != null && jwtService.validateToken(token)) {
32+
Claims claims = jwtService.extractClaims(token);
33+
AuthenticationUtils.setAuthenticationFromClaims(claims);
5134
}
5235

5336
filterChain.doFilter(request, response);

backend/src/main/java/com/easydraw/service/JwtService.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,10 @@ public class JwtService {
1919
private final long expirationTime = 86400000; // 24小时
2020

2121
public JwtService() {
22-
// 生成安全的密钥
23-
this.secretKey = Keys.secretKeyFor(SignatureAlgorithm.HS256);
22+
// 使用固定的密钥,确保token在应用重启后仍然有效
23+
// 注意:在生产环境中,应该使用环境变量或配置文件来管理密钥
24+
String secret = "your-secret-key-for-jwt-token-generation"; // 替换为更安全的密钥
25+
this.secretKey = Keys.hmacShaKeyFor(secret.getBytes());
2426
}
2527

2628
// 生成token
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package com.easydraw.utils;
2+
3+
import io.jsonwebtoken.Claims;
4+
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
5+
import org.springframework.security.core.authority.SimpleGrantedAuthority;
6+
import org.springframework.security.core.context.SecurityContextHolder;
7+
8+
import java.util.Collections;
9+
10+
public class AuthenticationUtils {
11+
12+
/**
13+
* 从JWT claims创建并设置认证对象
14+
* @param claims JWT claims
15+
*/
16+
public static void setAuthenticationFromClaims(Claims claims) {
17+
String userId = (String) claims.get("userId");
18+
String role = (String) claims.get("role");
19+
20+
// 创建认证对象
21+
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
22+
userId,
23+
null,
24+
Collections.singletonList(new SimpleGrantedAuthority("ROLE_" + role.toUpperCase()))
25+
);
26+
27+
// 设置认证信息到上下文
28+
SecurityContextHolder.getContext().setAuthentication(authentication);
29+
}
30+
31+
/**
32+
* 从请求头中提取token
33+
* @param authorizationHeader Authorization请求头
34+
* @return token字符串,如果没有则返回null
35+
*/
36+
public static String extractTokenFromHeader(String authorizationHeader) {
37+
if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
38+
return authorizationHeader.substring(7);
39+
}
40+
return null;
41+
}
42+
43+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package com.easydraw.utils;
2+
3+
import java.util.HashMap;
4+
import java.util.Map;
5+
6+
public class ResponseUtils {
7+
8+
/**
9+
* 构建成功响应
10+
* @param data 响应数据
11+
* @return 响应Map
12+
*/
13+
public static Map<String, Object> buildSuccessResponse(Object data) {
14+
Map<String, Object> response = new HashMap<>();
15+
response.put("data", data);
16+
response.put("status", "success");
17+
return response;
18+
}
19+
20+
/**
21+
* 构建错误响应
22+
* @param message 错误消息
23+
* @return 响应Map
24+
*/
25+
public static Map<String, Object> buildErrorResponse(String message) {
26+
Map<String, Object> response = new HashMap<>();
27+
response.put("data", null);
28+
response.put("status", "error");
29+
response.put("message", message);
30+
return response;
31+
}
32+
33+
}

src/stores/app.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import axios from "axios";
1+
import axios from "../utils/axios";
22
import { defineStore } from "pinia";
33

44
export const useAppStore = defineStore("app", {

src/utils/axios.ts

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,15 @@ service.interceptors.request.use(
2222
}
2323
);
2424

25+
// 处理未授权错误的公共函数
26+
const handleUnauthorizedError = () => {
27+
// 清除本地存储的token和用户信息
28+
localStorage.removeItem('token');
29+
localStorage.removeItem('user');
30+
// 跳转到登录页面
31+
window.location.href = '/login';
32+
};
33+
2534
// 响应拦截器
2635
service.interceptors.response.use(
2736
response => {
@@ -30,11 +39,12 @@ service.interceptors.response.use(
3039
error => {
3140
// 处理401错误(未授权)
3241
if (error.response && error.response.status === 401) {
33-
// 清除本地存储的token和用户信息
34-
localStorage.removeItem('token');
35-
localStorage.removeItem('user');
36-
// 跳转到登录页面
37-
window.location.href = '/login';
42+
handleUnauthorizedError();
43+
}
44+
// 处理403错误(禁止访问)
45+
if (error.response && error.response.status === 403) {
46+
console.error('403 Forbidden:', error);
47+
handleUnauthorizedError();
3848
}
3949
return Promise.reject(error);
4050
}

src/views/BoardList.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@
9494
<script setup lang="ts">
9595
import { ref, onMounted, nextTick } from 'vue';
9696
import { useRouter } from 'vue-router';
97-
import axios from 'axios';
97+
import axios from '../utils/axios';
9898
import { ElMessageBox, ElMessage } from 'element-plus';
9999
100100
const router = useRouter();

0 commit comments

Comments
 (0)