Compare commits

..

2 Commits

Author SHA1 Message Date
qingfeng1121
25eeab4940 feat(security): 实现JWT认证并增强API安全控制
添加JWT依赖并实现token生成与验证功能
在控制器方法上添加权限注解保护API端点
更新安全配置以集成JWT过滤器
移除无用的编码测试工具类
修改JWT相关配置为更安全的设置
2025-11-03 16:14:53 +08:00
qingfeng1121
f6d1d719a9 feat: 添加UTF-8编码支持并优化DTO验证
refactor: 重构用户服务密码更新逻辑
fix: 删除不再使用的MarkdownDto类
style: 清理日志文件并优化日志配置
build: 更新pom.xml配置以支持UTF-8编码
docs: 更新application.properties配置文档
2025-10-30 19:00:47 +08:00
26 changed files with 17771 additions and 734 deletions

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

19
pom.xml
View File

@@ -81,6 +81,13 @@
<version>0.18.2</version>
</dependency>
<!-- JWT依赖 -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<!-- JCache API -->
<dependency>
<groupId>javax.cache</groupId>
@@ -138,6 +145,7 @@
<configuration>
<mainClass>com.qf.myafterprojecy.MyAfterProjecyApplication</mainClass>
<skip>false</skip>
<jvmArguments>-Dfile.encoding=UTF-8 -Dsun.stdout.encoding=UTF-8 -Dsun.stderr.encoding=UTF-8</jvmArguments>
</configuration>
<executions>
<execution>
@@ -149,6 +157,17 @@
</executions>
</plugin>
</plugins>
<!-- 确保项目编译和资源处理使用UTF-8编码 -->
<sourceDirectory>src/main/java</sourceDirectory>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
<includes>
<include>**/*</include>
</includes>
</resource>
</resources>
</build>
</project>

View File

@@ -3,11 +3,40 @@ package com.qf.myafterprojecy;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import java.nio.charset.StandardCharsets;
/**
* 应用主类
* 设置系统编码并启动Spring Boot应用
*/
@SpringBootApplication
public class MyAfterProjecyApplication {
public static void main(String[] args) {
// 在应用启动前设置系统编码确保所有输出都使用UTF-8
setSystemEncoding();
// 启动Spring Boot应用
SpringApplication.run(MyAfterProjecyApplication.class, args);
}
/**
* 设置系统编码为UTF-8
* 解决控制台输出和日志中的中文乱码问题
*/
private static void setSystemEncoding() {
// 设置系统属性确保所有输出流都使用UTF-8编码
System.setProperty("file.encoding", StandardCharsets.UTF_8.name());
System.setProperty("sun.stdout.encoding", StandardCharsets.UTF_8.name());
System.setProperty("sun.stderr.encoding", StandardCharsets.UTF_8.name());
// 设置默认字符编码
try {
java.nio.charset.Charset.defaultCharset();
} catch (Exception e) {
// 记录编码设置异常
System.err.println("设置默认字符编码时发生异常: " + e.getMessage());
}
}
}

View File

@@ -0,0 +1,48 @@
package com.qf.myafterprojecy.config;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.web.filter.CharacterEncodingFilter;
import javax.servlet.Filter;
import java.nio.charset.StandardCharsets;
/**
* 字符编码配置类
* 确保所有HTTP请求和响应都使用UTF-8编码解决中文乱码问题
*/
@Configuration
public class CharacterEncodingConfig {
/**
* 创建字符编码过滤器
* 优先级设置为最高,确保在所有其他过滤器之前执行
*/
@Bean
public FilterRegistrationBean<Filter> characterEncodingFilter() {
// 创建字符编码过滤器
CharacterEncodingFilter encodingFilter = new CharacterEncodingFilter();
// 设置请求编码为UTF-8
encodingFilter.setEncoding(StandardCharsets.UTF_8.name());
// 强制请求使用UTF-8编码
encodingFilter.setForceRequestEncoding(true);
// 强制响应使用UTF-8编码
encodingFilter.setForceResponseEncoding(true);
// 创建过滤器注册Bean
FilterRegistrationBean<Filter> registrationBean = new FilterRegistrationBean<>(encodingFilter);
// 设置过滤器顺序为最高优先级
registrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE);
// 为所有请求路径注册过滤器
registrationBean.addUrlPatterns("/*");
return registrationBean;
}
}

View File

@@ -0,0 +1,96 @@
package com.qf.myafterprojecy.config;
import com.qf.myafterprojecy.utils.JwtUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* JWT认证过滤器用于验证token并授权用户
*/
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private static final Logger logger = LoggerFactory.getLogger(JwtAuthenticationFilter.class);
@Autowired
private JwtUtils jwtUtils;
@Autowired
private UserDetailsService userDetailsService;
@Value("${jwt.header:Authorization}")
private String tokenHeader;
@Value("${jwt.token-prefix:Bearer}")
private String tokenPrefix;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
try {
// 获取token
String token = getTokenFromRequest(request);
System.out.println(token);
if (token != null && validateToken(token)) {
// 从token中获取用户名
String username = jwtUtils.getUsernameFromToken(token);
// 加载用户信息
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
// 创建认证对象
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
// 设置认证信息到上下文
SecurityContextHolder.getContext().setAuthentication(authentication);
}
} catch (Exception e) {
logger.error("无法设置用户认证: {}", e);
SecurityContextHolder.clearContext();
}
filterChain.doFilter(request, response);
}
/**
* 从请求头中获取token
*/
private String getTokenFromRequest(HttpServletRequest request) {
String bearerToken = request.getHeader(tokenHeader);
if (bearerToken != null && bearerToken.startsWith(tokenPrefix + " ")) {
return bearerToken.substring(tokenPrefix.length() + 1);
}
return null;
}
/**
* 验证token
*/
private boolean validateToken(String token) {
try {
String username = jwtUtils.getUsernameFromToken(token);
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
return jwtUtils.validateToken(token, userDetails);
} catch (Exception e) {
logger.error("无效的token: {}", e);
return false;
}
}
}

View File

@@ -14,6 +14,7 @@ import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
/**
* Spring Security配置类
@@ -31,6 +32,9 @@ public class SecurityConfig {
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private JwtAuthenticationFilter jwtAuthenticationFilter;
/**
* 配置AuthenticationManager Bean
* 使用AuthenticationConfiguration来获取认证管理器这是更现代的方式
@@ -61,8 +65,10 @@ public class SecurityConfig {
.antMatchers(HttpMethod.GET,"/api/markdowns/**").permitAll()
.antMatchers(HttpMethod.GET,"/api/articles/**").permitAll()
.antMatchers(HttpMethod.GET,"/api/messages/**").permitAll()
.antMatchers(HttpMethod.GET,"/api/categories/**").permitAll()
// 公开post请求
.antMatchers(HttpMethod.POST,"/api/messages/**").permitAll()
.antMatchers(HttpMethod.POST,"/api/users/**").permitAll()
// 管理员才能访问的路径
.antMatchers("/api/admin/**").hasRole("ADMIN")
// 其他所有请求都需要认证
@@ -72,6 +78,18 @@ public class SecurityConfig {
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
// 添加JWT认证过滤器
http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
// 确保Spring Security不会添加额外的CharacterEncodingFilter
// 因为我们在CharacterEncodingConfig中已经配置了自定义的过滤器
http.addFilterBefore((request, response, chain) -> {
// 确保响应使用UTF-8编码
response.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");
chain.doFilter(request, response);
}, JwtAuthenticationFilter.class);
return http.build();
}
}

View File

@@ -1,6 +1,7 @@
package com.qf.myafterprojecy.controller;
import com.qf.myafterprojecy.config.ResponseMessage;
import com.qf.myafterprojecy.utils.JwtUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
@@ -31,6 +32,9 @@ public class AuthController {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private JwtUtils jwtUtils;
/**
* 用户登录请求体
*/
@@ -67,8 +71,7 @@ public class AuthController {
try {
// 创建认证令牌
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword());
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword());
// 执行认证
Authentication authentication = authenticationManager.authenticate(authenticationToken);
@@ -78,12 +81,14 @@ public class AuthController {
// 获取认证后的用户信息
UserDetails userDetails = (UserDetails) authentication.getPrincipal();
// 生成JWT token
String token = jwtUtils.generateToken(userDetails);
// 构建返回数据
Map<String, Object> data = new HashMap<>();
data.put("username", userDetails.getUsername());
data.put("authorities", userDetails.getAuthorities());
data.put("message", "登录成功");
data.put("token", token);
data.put("tokenPrefix", jwtUtils.getTokenPrefix());
return ResponseMessage.success(data, "登录成功");

View File

@@ -8,6 +8,7 @@ import com.qf.myafterprojecy.service.imp.ICategoryAttributeService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
@@ -53,6 +54,7 @@ public class CategoryAttributeController {
* @return 创建结果
*/
@PostMapping
@PreAuthorize("hasRole('ADMIN')")
public ResponseMessage<Category_attribute> createAttribute(@Valid @RequestBody CategoryAttributeDto dto) {
log.info("接收创建分类属性的请求: 分类ID={}, 属性名称={}",
dto.getCategoryid(), dto.getAttributename());
@@ -66,6 +68,7 @@ public class CategoryAttributeController {
* @return 更新结果
*/
@PutMapping("/{id}")
@PreAuthorize("hasRole('ADMIN')")
public ResponseMessage<Category_attribute> updateAttribute(
@PathVariable Integer id,
@Valid @RequestBody CategoryAttributeDto dto) {
@@ -80,6 +83,7 @@ public class CategoryAttributeController {
* @return 删除结果
*/
@DeleteMapping("/{id}")
@PreAuthorize("hasRole('ADMIN')")
public ResponseMessage<Boolean> deleteAttribute(@PathVariable Integer id) {
log.info("接收删除分类属性的请求: ID={}", id);
return categoryAttributeService.deleteCategoryAttribute(id);

View File

@@ -8,6 +8,7 @@ import com.qf.myafterprojecy.service.imp.ICategoryService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
@@ -55,6 +56,7 @@ public class CategoryController {
* @return 返回创建结果
*/
@PostMapping
@PreAuthorize("hasRole('ADMIN')")
public ResponseMessage<Category> createCategory(@Valid @RequestBody CategoryDto categoryDto) {
log.info("接收创建分类的请求: {}", categoryDto.getTypename());
return categoryService.saveCategory(categoryDto);
@@ -67,6 +69,7 @@ public class CategoryController {
* @return 返回更新结果
*/
@PutMapping("/{id}")
@PreAuthorize("hasRole('ADMIN')")
public ResponseMessage<Category> updateCategory(
@PathVariable Integer id,
@Valid @RequestBody CategoryDto categoryDto) {
@@ -80,6 +83,7 @@ public class CategoryController {
* @return 返回删除结果
*/
@DeleteMapping("/{id}")
@PreAuthorize("hasRole('ADMIN')")
public ResponseMessage<Boolean> deleteCategory(@PathVariable Integer id) {
log.info("接收删除分类的请求: {}", id);
return categoryService.deleteCategory(id);

View File

@@ -8,6 +8,7 @@ import com.qf.myafterprojecy.service.imp.IMessageService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@@ -87,6 +88,7 @@ public class MessageController {
* 创建新消息
*/
@PostMapping
@PreAuthorize("isAuthenticated()")
public ResponseMessage<Message> createMessage(@RequestBody MessageDto message) {
logger.info("接收创建消息的请求: {}", message != null ? message.getNickname() : "null");
return messageService.saveMessage(message);
@@ -101,6 +103,7 @@ public class MessageController {
* 根据ID删除消息
*/
@DeleteMapping("/{id}")
@PreAuthorize("hasRole('ADMIN') or #id == authentication.principal.id")
public ResponseMessage<Message> deleteMessage(@PathVariable Integer id) {
logger.info("接收删除消息的请求: {}", id);
return messageService.deleteMessage(id);

View File

@@ -8,6 +8,8 @@ import com.qf.myafterprojecy.service.imp.IUserService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
@@ -15,6 +17,7 @@ import java.util.List;
@RestController
@RequestMapping("/api/users")
@Validated
public class UserController {
private static final Logger logger = LoggerFactory.getLogger(UserController.class);
@@ -38,6 +41,7 @@ public class UserController {
* @return 用户列表
*/
@GetMapping
@PreAuthorize("hasRole('ADMIN')")
public ResponseMessage<List<Users>> getAllUsers() {
logger.info("获取所有用户列表");
return userService.getAllUsers();
@@ -45,10 +49,10 @@ public class UserController {
/**
* 根据用户名获取用户信息
* @param username 用户名
* @return 用户信息
*/
@GetMapping("/username/{username}")
@PreAuthorize("hasRole('ADMIN') or #username == authentication.name")
public ResponseMessage<Users> getUserByUsername(@PathVariable String username) {
logger.info("根据用户名获取用户信息,用户名: {}", username);
return userService.getUserByUsername(username);
@@ -72,6 +76,7 @@ public class UserController {
* @return 更新结果
*/
@PutMapping("/{id}")
@PreAuthorize("hasRole('ADMIN') or #id == authentication.principal.id")
public ResponseMessage<Users> updateUser(@PathVariable Long id, @Valid @RequestBody UserDto userDto) {
logger.info("更新用户信息用户ID: {}", id);
return userService.updateUser(id, userDto);
@@ -83,6 +88,7 @@ public class UserController {
* @return 删除结果
*/
@DeleteMapping("/{id}")
@PreAuthorize("hasRole('ADMIN')")
public ResponseMessage<Boolean> deleteUser(@PathVariable Long id) {
logger.info("删除用户用户ID: {}", id);
return userService.deleteUser(id);
@@ -94,6 +100,7 @@ public class UserController {
* @return 用户列表
*/
@GetMapping("/role/{role}")
@PreAuthorize("hasRole('ADMIN')")
public ResponseMessage<List<Users>> getUsersByRole(@PathVariable int role) {
logger.info("根据角色查询用户列表,角色: {}", role);
return userService.getUsersByRole(role);

View File

@@ -1,35 +1,36 @@
package com.qf.myafterprojecy.pojo.dto;
import lombok.Getter;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
@Getter
public class ArticleDto {
private Integer id;
private Integer articleid;
@NotBlank(message = "标题不能为空")
private String title;
@NotBlank(message = "内容不能为空")
private String content;
private String content;// 如果为空说明是长篇文章 不为空则是短篇说说
@NotNull(message = "属性ID不能为空")
private Integer attributeid;
private String img;
private Integer viewCount;
private Integer likes;
private Integer status;
// Getters and Setters
private String markdownscontent; // 文章内容的Markdown格式
public Integer getId() {
return id;
// Getters and Setters
public Integer getArticleid() {
return articleid;
}
public void setId(Integer id) {
this.id = id;
public void setArticleid(Integer articleid) {
this.articleid = articleid;
}
public String getTitle() {
@@ -48,12 +49,12 @@ public class ArticleDto {
this.content = content;
}
public Integer getStatus() {
return status;
public Integer getAttributeid() {
return attributeid;
}
public void setStatus(Integer status) {
this.status = status;
public void setAttributeid(Integer attributeid) {
this.attributeid = attributeid;
}
public String getImg() {
@@ -63,4 +64,36 @@ public class ArticleDto {
public void setImg(String img) {
this.img = img;
}
public Integer getViewCount() {
return viewCount;
}
public void setViewCount(Integer viewCount) {
this.viewCount = viewCount;
}
public Integer getLikes() {
return likes;
}
public void setLikes(Integer likes) {
this.likes = likes;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
public String getMarkdownscontent() {
return markdownscontent;
}
public void setMarkdownscontent(String markdownscontent) {
this.markdownscontent = markdownscontent;
}
}

View File

@@ -4,6 +4,7 @@ import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
public class CategoryAttributeDto {
private Integer attributeid;
@NotNull(message = "分类ID不能为空")
private Integer categoryid;
@@ -12,6 +13,14 @@ public class CategoryAttributeDto {
private String attributename;
// Getters and Setters
public Integer getAttributeid() {
return attributeid;
}
public void setAttributeid(Integer attributeid) {
this.attributeid = attributeid;
}
public Integer getCategoryid() {
return categoryid;
}

View File

@@ -1,12 +1,18 @@
package com.qf.myafterprojecy.pojo.dto;
import javax.validation.constraints.NotBlank;
import java.time.LocalDateTime;
public class CategoryDto {
private Integer typeid;
@NotBlank(message = "分类名称不能为空")
private String typename;
private String description;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
// Getters and Setters

View File

@@ -1,20 +0,0 @@
package com.qf.myafterprojecy.pojo.dto;
import lombok.Data;
import javax.validation.constraints.NotBlank;
@Data
public class MarkdownDto {
@NotBlank(message = "Markdown内容不能为空")
private String markdownscontent;
public String getMarkdownscontent() {
return markdownscontent;
}
public void setMarkdownscontent(String markdownscontent) {
this.markdownscontent = markdownscontent;
}
}

View File

@@ -18,6 +18,8 @@ public class MessageDto {
private Integer replyid;
private Integer articleid;
private Integer likes;
public Integer getReplyid() {
return replyid;
@@ -26,6 +28,7 @@ public class MessageDto {
public void setReplyid(Integer replyid) {
this.replyid = replyid;
}
public Integer getMessageid() {
return messageid;
}
@@ -81,4 +84,12 @@ public class MessageDto {
public void setArticleid(Integer articleid) {
this.articleid = articleid;
}
public Integer getLikes() {
return likes;
}
public void setLikes(Integer likes) {
this.likes = likes;
}
}

View File

@@ -3,6 +3,8 @@ package com.qf.myafterprojecy.pojo.dto;
import javax.validation.constraints.NotBlank;
public class UserDto {
private Long id;
@NotBlank(message = "用户名不能为空")
private String username;
@@ -15,9 +17,16 @@ public class UserDto {
@NotBlank(message = "手机号不能为空")
private String phone;
@NotBlank(message = "角色不能为空")
private int role;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUsername() {
return username;
}
@@ -57,5 +66,4 @@ public class UserDto {
public void setRole(int role) {
this.role = role;
}
}

View File

@@ -147,7 +147,10 @@ public class UserService implements IUserService {
// 更新用户信息
BeanUtils.copyProperties(userDto, user);
// 保存更新后的用户
// 如果提供了新密码,则进行加密
if (userDto.getPassword() != null && !userDto.getPassword().isEmpty()) {
user.setPassword(passwordEncoder.encode(user.getPassword()));
}
Users updatedUser = usersRepository.save(user);
return ResponseMessage.update(true, updatedUser);
} catch (DataAccessException e) {

View File

@@ -0,0 +1,103 @@
package com.qf.myafterprojecy.utils;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
/**
* JWT工具类用于生成和验证token
*/
@Component
public class JwtUtils {
@Value("${jwt.secret:default_secret_key_for_development}")
private String secret;
@Value("${jwt.expiration:86400000}")
private long expiration;
@Value("${jwt.token-prefix:Bearer}")
private String tokenPrefix;
/**
* 从token中获取用户名
*/
public String getUsernameFromToken(String token) {
return getClaimFromToken(token, Claims::getSubject);
}
/**
* 从token中获取过期时间
*/
public Date getExpirationDateFromToken(String token) {
return getClaimFromToken(token, Claims::getExpiration);
}
/**
* 从token中获取指定的claim
*/
public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
final Claims claims = getAllClaimsFromToken(token);
return claimsResolver.apply(claims);
}
/**
* 获取token中的所有claims
*/
private Claims getAllClaimsFromToken(String token) {
return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
}
/**
* 检查token是否过期
*/
private Boolean isTokenExpired(String token) {
final Date expiration = getExpirationDateFromToken(token);
return expiration.before(new Date());
}
/**
* 生成token
*/
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
claims.put("authorities", userDetails.getAuthorities());
return doGenerateToken(claims, userDetails.getUsername());
}
/**
* 生成token的核心方法
*/
private String doGenerateToken(Map<String, Object> claims, String subject) {
return Jwts.builder()
.setClaims(claims)
.setSubject(subject)
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + expiration))
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
}
/**
* 验证token
*/
public Boolean validateToken(String token, UserDetails userDetails) {
final String username = getUsernameFromToken(token);
return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
}
/**
* 获取token前缀
*/
public String getTokenPrefix() {
return tokenPrefix;
}
}

View File

@@ -61,17 +61,21 @@ logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE
# 日志文件配置
logging.file.name=logs/web_project.log
logging.pattern.file=%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n
# 确保控制台输出使用UTF-8编码
logging.pattern.console=%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n
# 日志编码配置 - 强制使用UTF-8
logging.charset.file=UTF-8
logging.charset.console=UTF-8
# Actuator配置 - 生产环境建议限制暴露的端点
management.endpoints.web.exposure.include=health,info,metrics,prometheus
management.endpoint.health.show-details=when_authorized
management.metrics.export.prometheus.enabled=true
# JWT配置 - 生产环境应使用更安全的密钥和环境变量
jwt.secret=mySecretKey
jwt.secret=myAfterProjectSecretKey2024SecureJwtTokenGeneration
jwt.expiration=86400000
jwt.header=Authorization
jwt.token-prefix=Bearer
jwt.token-prefix=Bearer
# CORS配置 - 生产环境应限制允许的源
cors.allowed-origins=http://localhost:3000
@@ -85,20 +89,41 @@ security.basic.enabled=false
security.ignored=/css/**,/js/**,/images/**,/favicon.ico
# 生产环境建议配置
# server.ssl.key-store=classpath:keystore.p12
# server.ssl.key-store-password=password
# server.ssl.key-store-type=PKCS12
# server.ssl.key-alias=tomcat
# 会话配置
server.servlet.session.timeout=30m
server.session.tracking-modes=cookie
# 国际化配置
spring.mvc.locale-resolver=fixed
spring.web.locale=zh_CN
#
## 响应编码配置
spring.http.encoding.charset=UTF-8
spring.http.encoding.enabled=true
spring.http.encoding.force=true
spring.messages.encoding=UTF-8
# 响应编码配置 - 确保所有响应使用UTF-8编码
server.servlet.encoding.charset=UTF-8
server.servlet.encoding.force=true
server.servlet.encoding.force-request=true
server.servlet.encoding.force-response=true
server.servlet.encoding.enabled=true
# 配置控制台输出编码 - 通过日志系统配置确保中文显示正常
# logging.pattern.console=%clr{%d{yyyy-MM-dd HH:mm:ss.SSS}}{faint} %clr{%5p} %clr{${PID}}{magenta} %clr{---}{faint} %clr{[%15.15t]}{faint} %clr{%-40.40logger{39}}{cyan} %clr{:}{faint} %m%n%wEx
# 配置Maven启动JVM参数需在启动时通过命令行指定或在pom.xml中配置
# 实际使用时请在启动命令中添加:-Dfile.encoding=UTF-8 -Dsun.stdout.encoding=UTF-8 -Dsun.stderr.encoding=UTF-8
# 更详细的日志配置 - 确保所有日志输出正确编码
# logging.level.root=INFO
# logging.level.org.springframework.web=DEBUG
# logging.level.org.springframework.security=INFO
# logging.level.com.qf.myafterprojecy=DEBUG
# 确保数据库连接编码正确
spring.datasource.hikari.data-source-properties.useUnicode=true
spring.datasource.hikari.data-source-properties.serverTimezone=Asia/Shanghai
spring.datasource.hikari.data-source-properties.characterEncoding=utf-8
# 应用性能优化配置
spring.main.allow-bean-definition-overriding=true
spring.main.lazy-initialization=false
# API 文档配置
spring.mvc.pathmatch.matching-strategy=ant_path_matcher