From 9f3b18f2df76d99d96c7579fbfcb83927ea6ebed Mon Sep 17 00:00:00 2001 From: tongtongstudio Date: Fri, 5 Sep 2025 18:56:54 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E7=BB=9F=E4=B8=80token?= =?UTF-8?q?=E9=AA=8C=E8=AF=81=EF=BC=8C=E6=9C=AA=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../videotablet/config/CommonConfig.java | 5 +- .../videotablet/config/SecurityConfig.java | 19 ++++- .../controller/UserController.java | 1 + .../videotablet/entity/DeviceApkInfo.java | 2 + .../filter/JwtAuthenticationFilter.java | 70 +++++++++++++++++++ .../onekeycall/videotablet/utils/JwtUtil.java | 60 +++++++++++++++- 6 files changed, 147 insertions(+), 10 deletions(-) create mode 100644 src/main/java/com/onekeycall/videotablet/filter/JwtAuthenticationFilter.java diff --git a/src/main/java/com/onekeycall/videotablet/config/CommonConfig.java b/src/main/java/com/onekeycall/videotablet/config/CommonConfig.java index 9971602..ce2f750 100644 --- a/src/main/java/com/onekeycall/videotablet/config/CommonConfig.java +++ b/src/main/java/com/onekeycall/videotablet/config/CommonConfig.java @@ -7,8 +7,5 @@ import org.springframework.security.crypto.password.PasswordEncoder; @Configuration public class CommonConfig { - @Bean - public PasswordEncoder passwordEncoder() { - return new BCryptPasswordEncoder(); - } + } \ No newline at end of file diff --git a/src/main/java/com/onekeycall/videotablet/config/SecurityConfig.java b/src/main/java/com/onekeycall/videotablet/config/SecurityConfig.java index 5964931..0426f8e 100644 --- a/src/main/java/com/onekeycall/videotablet/config/SecurityConfig.java +++ b/src/main/java/com/onekeycall/videotablet/config/SecurityConfig.java @@ -1,24 +1,31 @@ package com.onekeycall.videotablet.config; +import com.onekeycall.videotablet.filter.JwtAuthenticationFilter; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Lazy; import org.springframework.security.authentication.AuthenticationManager; 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.configuration.EnableWebSecurity; 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.authentication.UsernamePasswordAuthenticationFilter; @Configuration @EnableWebSecurity +@EnableGlobalMethodSecurity(prePostEnabled = true) public class SecurityConfig { - private final UserDetailsService userDetailsService; + private final JwtAuthenticationFilter jwtAuthenticationFilter; // 自定义的JWT认证过滤器 @Autowired - public SecurityConfig(UserDetailsService userDetailsService) { - this.userDetailsService = userDetailsService; + public SecurityConfig(@Lazy JwtAuthenticationFilter jwtAuthenticationFilter) { + this.jwtAuthenticationFilter = jwtAuthenticationFilter; } @Bean @@ -38,6 +45,7 @@ public class SecurityConfig { .requestMatchers("/contact/**").permitAll() .anyRequest().authenticated() ); + http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); // 添加JWT过滤器 return http.build(); } @@ -46,4 +54,9 @@ public class SecurityConfig { public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception { return authConfig.getAuthenticationManager(); } + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } } \ No newline at end of file diff --git a/src/main/java/com/onekeycall/videotablet/controller/UserController.java b/src/main/java/com/onekeycall/videotablet/controller/UserController.java index 79094a9..a6ed15a 100644 --- a/src/main/java/com/onekeycall/videotablet/controller/UserController.java +++ b/src/main/java/com/onekeycall/videotablet/controller/UserController.java @@ -144,6 +144,7 @@ public class UserController { @GetMapping("/get_device_apk_list") public Result getDeviceApkList(@RequestParam String sn) { + DeviceApkInfo deviceApkInfo = deviceApkInfoService.getDeviceApkInfoBySn(sn); return Result.ok().data("deviceApkInfo", deviceApkInfo); } diff --git a/src/main/java/com/onekeycall/videotablet/entity/DeviceApkInfo.java b/src/main/java/com/onekeycall/videotablet/entity/DeviceApkInfo.java index 67d1c9b..152cced 100644 --- a/src/main/java/com/onekeycall/videotablet/entity/DeviceApkInfo.java +++ b/src/main/java/com/onekeycall/videotablet/entity/DeviceApkInfo.java @@ -2,6 +2,7 @@ package com.onekeycall.videotablet.entity; import lombok.Data; 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.Field; @@ -15,6 +16,7 @@ public class DeviceApkInfo { @Id private String id; // MongoDB文档ID + @Indexed @Field("sn") private String sn; // 设备序列号 diff --git a/src/main/java/com/onekeycall/videotablet/filter/JwtAuthenticationFilter.java b/src/main/java/com/onekeycall/videotablet/filter/JwtAuthenticationFilter.java new file mode 100644 index 0000000..f970155 --- /dev/null +++ b/src/main/java/com/onekeycall/videotablet/filter/JwtAuthenticationFilter.java @@ -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); + } +} + diff --git a/src/main/java/com/onekeycall/videotablet/utils/JwtUtil.java b/src/main/java/com/onekeycall/videotablet/utils/JwtUtil.java index d4a1718..d351b74 100644 --- a/src/main/java/com/onekeycall/videotablet/utils/JwtUtil.java +++ b/src/main/java/com/onekeycall/videotablet/utils/JwtUtil.java @@ -12,13 +12,13 @@ import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.security.core.userdetails.UserDetails; import org.springframework.stereotype.Component; import org.springframework.security.oauth2.common.exceptions.InvalidTokenException; -import java.util.Base64; -import java.util.Date; -import java.util.UUID; +import java.util.*; import java.util.concurrent.TimeUnit; +import java.util.function.Function; @Component public class JwtUtil { @@ -188,6 +188,60 @@ public class JwtUtil { 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 getClaimFromToken(String token, Function 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 claims = new HashMap<>(); + return doGenerateToken(claims, userDetails.getUsername()); + } + + // 生成Token的具体实现 + private String doGenerateToken(Map 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}") private String TABLET_SECRET;