增加统一token验证,未完成

This commit is contained in:
2025-09-05 18:56:54 +08:00
parent 153137379d
commit 9f3b18f2df
6 changed files with 147 additions and 10 deletions

View File

@@ -7,8 +7,5 @@ import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration @Configuration
public class CommonConfig { public class CommonConfig {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
} }

View File

@@ -1,24 +1,31 @@
package com.onekeycall.videotablet.config; package com.onekeycall.videotablet.config;
import com.onekeycall.videotablet.filter.JwtAuthenticationFilter;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration @Configuration
@EnableWebSecurity @EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig { public class SecurityConfig {
private final UserDetailsService userDetailsService; private final JwtAuthenticationFilter jwtAuthenticationFilter; // 自定义的JWT认证过滤器
@Autowired @Autowired
public SecurityConfig(UserDetailsService userDetailsService) { public SecurityConfig(@Lazy JwtAuthenticationFilter jwtAuthenticationFilter) {
this.userDetailsService = userDetailsService; this.jwtAuthenticationFilter = jwtAuthenticationFilter;
} }
@Bean @Bean
@@ -38,6 +45,7 @@ public class SecurityConfig {
.requestMatchers("/contact/**").permitAll() .requestMatchers("/contact/**").permitAll()
.anyRequest().authenticated() .anyRequest().authenticated()
); );
http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); // 添加JWT过滤器
return http.build(); return http.build();
} }
@@ -46,4 +54,9 @@ public class SecurityConfig {
public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception { public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception {
return authConfig.getAuthenticationManager(); return authConfig.getAuthenticationManager();
} }
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
} }

View File

@@ -144,6 +144,7 @@ public class UserController {
@GetMapping("/get_device_apk_list") @GetMapping("/get_device_apk_list")
public Result getDeviceApkList(@RequestParam String sn) { public Result getDeviceApkList(@RequestParam String sn) {
DeviceApkInfo deviceApkInfo = deviceApkInfoService.getDeviceApkInfoBySn(sn); DeviceApkInfo deviceApkInfo = deviceApkInfoService.getDeviceApkInfoBySn(sn);
return Result.ok().data("deviceApkInfo", deviceApkInfo); return Result.ok().data("deviceApkInfo", deviceApkInfo);
} }

View File

@@ -2,6 +2,7 @@ package com.onekeycall.videotablet.entity;
import lombok.Data; import lombok.Data;
import org.springframework.data.annotation.Id; import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.index.Indexed;
import org.springframework.data.mongodb.core.mapping.Document; import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.Field; import org.springframework.data.mongodb.core.mapping.Field;
@@ -15,6 +16,7 @@ public class DeviceApkInfo {
@Id @Id
private String id; // MongoDB文档ID private String id; // MongoDB文档ID
@Indexed
@Field("sn") @Field("sn")
private String sn; // 设备序列号 private String sn; // 设备序列号

View File

@@ -0,0 +1,70 @@
package com.onekeycall.videotablet.filter;
import com.onekeycall.videotablet.utils.JwtUtil;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
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 java.io.IOException;
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final UserDetailsService userDetailsService;
private final JwtUtil jwtUtil;
public JwtAuthenticationFilter(UserDetailsService userDetailsService, JwtUtil jwtUtil) {
this.userDetailsService = userDetailsService;
this.jwtUtil = jwtUtil;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
// 从请求头中获取Token
String authorizationHeader = request.getHeader("Authorization");
String username = null;
String jwt = null;
// 检查Authorization头是否存在且以Bearer开头
if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
jwt = authorizationHeader.substring(7);
try {
username = jwtUtil.getUsernameFromToken(jwt);
} catch (Exception e) {
logger.error("无法获取用户信息或Token无效", e);
}
}
// 如果获取到用户名且当前上下文没有认证信息
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
// 验证Token
if (jwtUtil.validateToken(jwt, userDetails)) {
// 创建认证令牌
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
usernamePasswordAuthenticationToken
.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
// 将认证信息存入上下文
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
}
// 继续过滤器链
filterChain.doFilter(request, response);
}
}

View File

@@ -12,13 +12,13 @@ import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.security.oauth2.common.exceptions.InvalidTokenException; import org.springframework.security.oauth2.common.exceptions.InvalidTokenException;
import java.util.Base64; import java.util.*;
import java.util.Date;
import java.util.UUID;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.function.Function;
@Component @Component
public class JwtUtil { public class JwtUtil {
@@ -188,6 +188,60 @@ public class JwtUtil {
redisTemplate.delete(redisKey); redisTemplate.delete(redisKey);
} }
// 从Token中获取用户名
public String getUsernameFromToken(String token) {
return getClaimFromToken(token, Claims::getSubject);
}
// 从Token中获取过期时间
public Date getExpirationDateFromToken(String token) {
return getClaimFromToken(token, Claims::getExpiration);
}
// 从Token中获取自定义声明
public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
final Claims claims = getAllClaimsFromToken(token);
return claimsResolver.apply(claims);
}
// 从Token中获取所有声明
private Claims getAllClaimsFromToken(String token) {
return Jwts.parser()
.setSigningKey(secret)
.build()
.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<>();
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() + accessExpire))
.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));
}
@Value("${jwt.tablet.secret}") @Value("${jwt.tablet.secret}")
private String TABLET_SECRET; private String TABLET_SECRET;