优化统一鉴权,优化目录结构

This commit is contained in:
2025-09-08 11:32:33 +08:00
parent 9f3b18f2df
commit a33eeef27e
22 changed files with 487 additions and 122 deletions

View File

@@ -1,6 +1,7 @@
package com.onekeycall.videotablet.config;
import com.onekeycall.videotablet.filter.JwtAuthenticationFilter;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -36,12 +37,11 @@ public class SecurityConfig {
.requestMatchers("/api/ws/**", "/topic/**").permitAll()
.requestMatchers("/uploadFile/**").permitAll()
.requestMatchers("/public/**").permitAll()
.requestMatchers("/sn/**").permitAll()
.requestMatchers("/user/**").permitAll()
// .requestMatchers("/sn/**").permitAll()
// .requestMatchers("/user/**").permitAll()
.requestMatchers("/rtc/**").permitAll()
.requestMatchers("/admin/**").hasRole("ADMIN")
// .requestMatchers("/user/**").hasAnyRole("USER", "ADMIN")
.requestMatchers("/user/**").permitAll()
.requestMatchers("/contact/**").permitAll()
.anyRequest().authenticated()
);

View File

@@ -3,6 +3,7 @@ package com.onekeycall.videotablet.config;
import com.onekeycall.videotablet.handler.CustomWebSocketHandler;
import com.onekeycall.videotablet.interceptor.AuthHandshakeInterceptor;
import com.onekeycall.videotablet.utils.JwtUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
@@ -11,16 +12,13 @@ import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry
@Configuration
@EnableWebSocket
@RequiredArgsConstructor
public class WebSocketConfig implements WebSocketConfigurer {
@Autowired
private JwtUtil jwtUtil;
private final JwtUtil jwtUtil;
private final CustomWebSocketHandler webSocketHandler;
public WebSocketConfig(CustomWebSocketHandler webSocketHandler) {
this.webSocketHandler = webSocketHandler;
}
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(webSocketHandler, "/ws") // 客户端连接端点

View File

@@ -1,20 +1,18 @@
package com.onekeycall.videotablet.controller;
package com.onekeycall.videotablet.controller.pub;
import com.onekeycall.videotablet.result.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/public")
public class HelloController {
//引入 redis
@Autowired
private StringRedisTemplate stringRedisTemplate;
@GetMapping("/public/hello")
@GetMapping("/hello")
public Result getMethodName() {
return Result.ok().message("Welcome to Yijiantong");
}
@@ -24,7 +22,7 @@ public class HelloController {
*
* @return
*/
@PostMapping("/public/set")
@PostMapping("/set")
public Result setRedis(@RequestParam(value = "username") String username) {
//存储 key-value 键值对: "username"-"jaychou"
stringRedisTemplate.opsForValue().set("username", username);
@@ -36,7 +34,7 @@ public class HelloController {
*
* @return
*/
@GetMapping("/public/get")
@GetMapping("/get")
public Result getRedis(@RequestParam(value = "username") String username) {
//通过 key 值读取 value
String result = stringRedisTemplate.opsForValue().get(username);

View File

@@ -1,10 +1,11 @@
package com.onekeycall.videotablet.controller;
package com.onekeycall.videotablet.controller.pub;
import com.onekeycall.videotablet.dto.TokenPair;
import com.onekeycall.videotablet.entity.User;
import com.onekeycall.videotablet.result.Result;
import com.onekeycall.videotablet.service.UserService;
import com.onekeycall.videotablet.utils.JwtUtil;
import com.onekeycall.videotablet.utils.TextUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
@@ -77,6 +78,10 @@ public class LoginController {
}
String userId = user.getUserId();
if (TextUtils.isEmpty(user.getPassword())) {
return Result.error().message("用户未设置密码,请使用验证码登录");
}
// 1. 创建认证令牌
Authentication authenticationToken = new UsernamePasswordAuthenticationToken(userId, password);

View File

@@ -1,4 +1,4 @@
package com.onekeycall.videotablet.controller;
package com.onekeycall.videotablet.controller.pub;
import com.onekeycall.videotablet.config.FilePath;
import com.onekeycall.videotablet.entity.DeviceInfo;

View File

@@ -1,4 +1,4 @@
package com.onekeycall.videotablet.controller;
package com.onekeycall.videotablet.controller.sms;
import com.aliyun.dysmsapi20170525.models.SendSmsResponse;
import com.aliyun.tea.TeaException;

View File

@@ -1,4 +1,4 @@
package com.onekeycall.videotablet.controller;
package com.onekeycall.videotablet.controller.sms;
import com.onekeycall.videotablet.result.Result;
import com.onekeycall.videotablet.sms.SendSms;

View File

@@ -1,4 +1,4 @@
package com.onekeycall.videotablet.controller;
package com.onekeycall.videotablet.controller.sn;
import com.google.gson.JsonObject;
import com.onekeycall.videotablet.entity.DeviceInfo;
@@ -160,22 +160,22 @@ public class BindSnController {
// TODO: 2025/8/22 Device_Token在docker无法被接收到使用Device-Token代替
@GetMapping("/get_bind_statu")
public Result getBindStatus(
@RequestHeader("Device-Token") String deviceToken, @RequestHeader("Device-ID") String deviceId,
@RequestHeader("Device-Sig") String deviceSig,
// @RequestHeader("Device-Token") String deviceToken, @RequestHeader("Device-ID") String deviceId,
// @RequestHeader("Device-Sig") String deviceSig,
@RequestParam(value = "sn") String sn) {
if (!jwtUtil.validateDeviceToken(deviceToken, deviceId, sn)) {
return Result.error().message("Invalid token");
}
// if (!jwtUtil.validateDeviceToken(deviceToken, deviceId, sn)) {
// return Result.error().message("Invalid token");
// }
DeviceInfo deviceInfo = deviceSnService.findBySn(sn);
if (deviceInfo == null) {
return Result.notFound().message("sn not found");
}
if (!deviceInfo.getBindSig().equals(deviceSig)) {
return Result.error().message("device sig not match");
}
// if (!deviceInfo.getBindSig().equals(deviceSig)) {
// return Result.error().message("device sig not match");
// }
if (TextUtils.isEmpty(deviceInfo.getBindPhone())) {
return Result.error().message("sn not bind");

View File

@@ -1,12 +1,8 @@
package com.onekeycall.videotablet.controller;
package com.onekeycall.videotablet.controller.sn;
import com.aliyun.core.annotation.Body;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.onekeycall.videotablet.bean.ApkUploadRequest;
import com.onekeycall.videotablet.entity.ApkInfo;
import com.onekeycall.videotablet.entity.DeviceApkInfo;
import com.onekeycall.videotablet.result.Result;
import com.onekeycall.videotablet.service.DeviceApkInfoService;
import org.slf4j.Logger;

View File

@@ -1,4 +1,4 @@
package com.onekeycall.videotablet.controller;
package com.onekeycall.videotablet.controller.sn;
import com.onekeycall.videotablet.entity.Contact;
import com.onekeycall.videotablet.entity.DeviceInfo;
@@ -14,9 +14,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/sn")
@@ -30,41 +28,7 @@ public class DevicesController {
@Autowired
private ContactService contactService;
@GetMapping("/get_sn_list")
public Result register(
@RequestHeader("Authorization") String authHeader, @RequestHeader("Device-ID") String deviceId,
@RequestParam(value = "user_id") String userId, @RequestParam(value = "sn", required = false) String sn) {
// 1. 校验 Authorization
if (!authHeader.startsWith("Bearer ")) {
return Result.error().message("Invalid Authorization header");
}
String token = authHeader.substring(7); // 去掉 "Bearer " 前缀
// 2. 校验 Token
if (!jwtUtil.validateAccessToken(userId, token, deviceId)) {
return Result.error().message("Invalid token");
}
if (TextUtils.isEmpty(sn)) {
List<DeviceInfo> deviceInfos = deviceSnService.findByUserId(userId);
if (deviceInfos == null || deviceInfos.isEmpty()) {
return Result.notFound().message("sn not found");
} else {
return Result.ok().data("deviceInfos", deviceInfos);
}
} else {
DeviceInfo deviceInfo = deviceSnService.findBySn(sn);
if (deviceInfo == null) {
return Result.notFound().message("sn not found");
}
if (!deviceInfo.getUserId().equals(userId)) {
return Result.error().message("sn not belong to user");
}
return Result.ok().data("deviceInfo", deviceInfo);
}
}
@PostMapping("/update_location")
public Result updateLocation(

View File

@@ -1,6 +1,6 @@
package com.onekeycall.videotablet.controller;
package com.onekeycall.videotablet.controller.user;
import com.nimbusds.openid.connect.sdk.claims.UserInfo;
import com.onekeycall.videotablet.controller.pub.LoginController;
import com.onekeycall.videotablet.dto.TokenPair;
import com.onekeycall.videotablet.entity.DeviceApkInfo;
import com.onekeycall.videotablet.entity.DeviceInfo;
@@ -21,6 +21,7 @@ import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@RestController
@@ -105,6 +106,42 @@ public class UserController {
return Result.ok().data("user_info", userInfo);
}
@GetMapping("/get_sn_list")
public Result register(
@RequestHeader("Authorization") String authHeader, @RequestHeader("Device-ID") String deviceId,
@RequestParam(value = "user_id") String userId, @RequestParam(value = "sn", required = false) String sn) {
// 1. 校验 Authorization
if (!authHeader.startsWith("Bearer ")) {
return Result.error().message("Invalid Authorization header");
}
String token = authHeader.substring(7); // 去掉 "Bearer " 前缀
// 2. 校验 Token
if (!jwtUtil.validateAccessToken(userId, token, deviceId)) {
return Result.error().message("Invalid token");
}
if (TextUtils.isEmpty(sn)) {
List<DeviceInfo> deviceInfos = deviceSnService.findByUserId(userId);
if (deviceInfos == null || deviceInfos.isEmpty()) {
return Result.notFound().message("sn not found");
} else {
return Result.ok().data("deviceInfos", deviceInfos);
}
} else {
DeviceInfo deviceInfo = deviceSnService.findBySn(sn);
if (deviceInfo == null) {
return Result.notFound().message("sn not found");
}
if (!deviceInfo.getUserId().equals(userId)) {
return Result.error().message("sn not belong to user");
}
return Result.ok().data("deviceInfo", deviceInfo);
}
}
@GetMapping("/get_sn_location")
public Result getSnLocation(
@RequestHeader("Authorization") String authHeader, @RequestHeader("Device-ID") String deviceId,

View File

@@ -1,11 +1,10 @@
package com.onekeycall.videotablet.controller;
package com.onekeycall.videotablet.controller.user;
import com.onekeycall.videotablet.entity.User;
import com.onekeycall.videotablet.result.Result;
import com.onekeycall.videotablet.service.UserService;
import com.onekeycall.videotablet.utils.JwtUtil;
import com.onekeycall.videotablet.utils.TextUtils;
import jakarta.servlet.http.HttpServletRequest;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;

View File

@@ -121,11 +121,11 @@ public class User implements UserDetails {
}
public String getUsername() {
return nickname;
return userId;
}
public void setUsername(String username) {
this.nickname = username;
this.userId = username;
}
public String getPassword() {

View File

@@ -1,9 +1,22 @@
package com.onekeycall.videotablet.filter;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.onekeycall.videotablet.entity.DeviceInfo;
import com.onekeycall.videotablet.gson.GsonUtils;
import com.onekeycall.videotablet.result.Result;
import com.onekeycall.videotablet.service.DeviceSnService;
import com.onekeycall.videotablet.utils.JwtUtil;
import com.onekeycall.videotablet.utils.TextUtils;
import io.jsonwebtoken.lang.Collections;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
@@ -11,60 +24,176 @@ 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 org.springframework.web.util.ContentCachingRequestWrapper;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
@Component
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {
Logger logger = LoggerFactory.getLogger(JwtAuthenticationFilter.class);
private final UserDetailsService userDetailsService;
private final JwtUtil jwtUtil;
public JwtAuthenticationFilter(UserDetailsService userDetailsService, JwtUtil jwtUtil) {
this.userDetailsService = userDetailsService;
this.jwtUtil = jwtUtil;
}
private final DeviceSnService deviceSnService;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
// 从请求头中获取Token
String authorizationHeader = request.getHeader("Authorization");
// 使用ContentCachingRequestWrapper包装请求以支持多次读取
ContentCachingRequestWrapper wrappedRequest = new ContentCachingRequestWrapper(request);
String username = null;
String jwt = null;
String uripath = wrappedRequest.getRequestURI();
// 新增请求路径日志
logger.debug("Processing request: " + uripath);
if (uripath.startsWith("/user")) {
// 从请求头中获取Token
String authorizationHeader = wrappedRequest.getHeader("Authorization");
// 增强header检查日志
if (authorizationHeader == null) {
logger.debug("Missing Authorization header for: " + wrappedRequest.getRequestURI());
setResponse(response, Result.unAuthorized().message("Missing Authorization header"));
return;
} else {
logger.debug("Found Authorization header");
}
String username = null;
String jwt = null;
// 检查Authorization头是否存在且以Bearer开头
if (authorizationHeader.startsWith("Bearer ")) {
jwt = authorizationHeader.substring(7);
try {
username = jwtUtil.getUsernameFromToken(jwt);
logger.debug("Extracted username: " + username);
} catch (Exception e) {
logger.error("Token解析失败 | Token: " + jwt, e);
setResponse(response, Result.unAuthorized().message("Invalid credentials"));
return; // 重要!验证失败时终止过滤器链
}
}
// 如果获取到用户名且当前上下文没有认证信息
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
// 新增权限检查日志
logger.debug("Loaded user authorities: " + userDetails.getAuthorities());
// 验证Token
if (jwtUtil.validateToken(jwt, userDetails)) {
// 创建认证令牌
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
usernamePasswordAuthenticationToken
.setDetails(new WebAuthenticationDetailsSource().buildDetails(wrappedRequest));
// 将认证信息存入上下文
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
logger.debug("Successfully authenticated user: " + username);
} else {
logger.warn("Token验证失败 | User: " + username);
setResponse(response, Result.unAuthorized().message("Token validation failed"));
return; // 重要!验证失败时终止过滤器链
}
}
} else if (uripath.startsWith("/sn")) {
// 设备绑定相关鉴权
String deviceToken = wrappedRequest.getHeader("Device-Token");
String deviceId = wrappedRequest.getHeader("Device-Id");
String deviceSig = wrappedRequest.getHeader("Device-Sig");
String sn = null;
// 1. 尝试从请求参数获取sn
sn = wrappedRequest.getParameter("sn");
logger.debug("sn from parameter: {}", sn);
// 2. 如果参数中没有sn且是POST请求尝试从JSON请求体获取
if (sn == null && "POST".equalsIgnoreCase(wrappedRequest.getMethod())) {
String contentType = wrappedRequest.getContentType();
if (contentType != null && (contentType.contains("application/json") || contentType.contains("application/*+json"))) {
try {
String body = new String(wrappedRequest.getContentAsByteArray(), StandardCharsets.UTF_8);
if (!TextUtils.isEmpty(body)) {
JsonObject jsonObject = JsonParser.parseString(body).getAsJsonObject();
if (jsonObject.has("sn")) {
sn = jsonObject.get("sn").getAsString();
logger.debug("sn from request body: {}", sn);
}
}
} catch (Exception e) {
logger.error("Failed to parse sn from request body", e);
// 解析失败不中断,继续后续检查
}
}
}
if (deviceToken == null || deviceId == null || deviceSig == null || sn == null) {
logger.warn("Missing device headers | DeviceToken: {} | DeviceID: {} | DeviceSig: {} | SN: {}", deviceToken, deviceId, deviceSig, sn);
setResponse(response, Result.unAuthorized().message("缺少设备认证信息"));
return;
}
DeviceInfo deviceInfo = deviceSnService.findBySn(sn);
if (deviceInfo == null) {
logger.warn("SN not found | SN: {}", sn);
setResponse(response, Result.notFound().message("sn not found"));
return;
}
if (!deviceInfo.getBindSig().equals(deviceSig)) {
setResponse(response, Result.error().message("device sig not match"));
return;
}
if (TextUtils.isEmpty(deviceInfo.getBindPhone())) {
setResponse(response, Result.error().message("sn not bind"));
return;
}
// 检查Authorization头是否存在且以Bearer开头
if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
jwt = authorizationHeader.substring(7);
try {
username = jwtUtil.getUsernameFromToken(jwt);
// 调用SN验证服务假设有SNService
if (!jwtUtil.validateDeviceToken(deviceToken, deviceId, sn)) {
logger.warn("SN验证失败 | DeviceID: {} | SN: {}", deviceId, sn);
setResponse(response, Result.unAuthorized().message("设备验证失败"));
return;
}
logger.debug("SN验证成功 | DeviceID: {} | SN: {}", deviceId, sn);
// 3. 设置认证信息到SecurityContext解决403问题
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
sn, null, Collections.emptyList()); // 可根据需要添加角色权限
authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(wrappedRequest));
SecurityContextHolder.getContext().setAuthentication(authToken);
logger.debug("Set authentication for SN: {}", sn);
} catch (Exception e) {
logger.error("无法获取用户信息或Token无效", e);
logger.error("SN验证异常 | DeviceID: {} | SN: {}", deviceId, sn, e);
setResponse(response, Result.error().message("设备验证服务异常"));
return;
}
}
// 如果获取到用户名且当前上下文没有认证信息
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);
filterChain.doFilter(wrappedRequest, response);
}
private void setResponse(HttpServletResponse response, Result result) throws IOException {
SecurityContextHolder.clearContext();
response.setStatus(HttpServletResponse.SC_OK); // 设置为200状态码
response.setContentType("application/json;charset=utf-8");
response.getWriter().write(GsonUtils.toJSONString(result));
response.getWriter().flush();
response.getWriter().close();
}
}

View File

@@ -0,0 +1,153 @@
package com.onekeycall.videotablet.gson;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.reflect.TypeToken;
import com.onekeycall.videotablet.filter.JwtAuthenticationFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;
import java.util.Objects;
public class GsonUtils {
//https://blog.csdn.net/zte1055889498/article/details/122400299
static Logger logger = LoggerFactory.getLogger(GsonUtils.class);
public static JsonObject getJsonObject(String jsonString) {
JsonObject jsonObject = JsonParser.parseString(jsonString).getAsJsonObject();
return jsonObject;
}
private static final Gson gson;
static {
GsonBuilder builder = new GsonBuilder();
builder.registerTypeAdapterFactory(new NullStringToEmptyAdapterFactory());
builder.registerTypeAdapter(Integer.class, new IntegerDefault0Adapter());
builder.registerTypeAdapter(int.class, new IntegerDefault0Adapter());
builder.disableHtmlEscaping();
builder.enableComplexMapKeySerialization();
// builder.excludeFieldsWithoutExposeAnnotation();
builder.setDateFormat("yyyy-MM-dd HH:mm:ss");
gson = builder.create();
}
public static Type makeJavaType(Type rawType, Type... typeArguments) {
return TypeToken.getParameterized(rawType, typeArguments).getType();
}
public static String toString(Object value) {
if (Objects.isNull(value)) {
return null;
}
if (value instanceof String) {
return (String) value;
}
return toJSONString(value);
}
public static String toJSONString(Object value) {
return gson.toJson(value);
}
public static String toPrettyString(Object value) {
return gson.newBuilder().setPrettyPrinting().create().toJson(value);
}
public static JsonElement fromJavaObject(Object value) {
JsonElement result = null;
if (Objects.nonNull(value) && (value instanceof String)) {
result = parseObject((String) value);
} else {
result = gson.toJsonTree(value);
}
return result;
}
public static JsonElement parseObject(String content) {
return JsonParser.parseString(content);
}
public static JsonElement getJsonElement(JsonObject node, String name) {
return node.get(name);
}
public static JsonElement getJsonElement(JsonArray node, int index) {
return node.get(index);
}
public static <T> T toJavaObject(JsonElement node, Class<T> clazz) {
return gson.fromJson(node, clazz);
}
public static <T> T toJavaObject(JsonElement node, Type type) {
return gson.fromJson(node, type);
}
public static <T> T toJavaObject(JsonElement node, TypeToken<?> typeToken) {
return toJavaObject(node, typeToken.getType());
}
public static <E> List<E> toJavaList(JsonElement node, Class<E> clazz) {
return toJavaObject(node, makeJavaType(List.class, clazz));
}
public static List<Object> toJavaList(JsonElement node) {
return toJavaObject(node, new TypeToken<List<Object>>() {
}.getType());
}
public static <V> Map<String, V> toJavaMap(JsonElement node, Class<V> clazz) {
return toJavaObject(node, makeJavaType(Map.class, String.class, clazz));
}
public static Map<String, Object> toJavaMap(JsonElement node) {
return toJavaObject(node, new TypeToken<Map<String, Object>>() {
}.getType());
}
public static <T> T toJavaObject(String content, Class<T> clazz) {
JsonObject jsonObject = getJsonObject(content);
String jsonString = jsonObject.toString();
try {
return gson.fromJson(jsonString, clazz);
} catch (Exception e) {
logger.error("GsonUtils", "toJavaObject: " + e.getMessage());
}
return null;
}
public static <T> T toJavaObject(String content, Type type) {
return gson.fromJson(content, type);
}
public static <T> T toJavaObject(String content, TypeToken<?> typeToken) {
return toJavaObject(content, typeToken.getType());
}
public static <E> List<E> toJavaList(String content, Class<E> clazz) {
return toJavaObject(content, makeJavaType(List.class, clazz));
}
public static List<Object> toJavaList(String content) {
return toJavaObject(content, new TypeToken<List<Object>>() {
}.getType());
}
public static <V> Map<String, V> toJavaMap(String content, Class<V> clazz) {
return toJavaObject(content, makeJavaType(Map.class, String.class, clazz));
}
public static Map<String, Object> toJavaMap(String content) {
return toJavaObject(content, new TypeToken<Map<String, Object>>() {
}.getType());
}
}

View File

@@ -0,0 +1,35 @@
package com.onekeycall.videotablet.gson;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import com.google.gson.JsonSyntaxException;
import java.lang.reflect.Type;
public class IntegerDefault0Adapter implements JsonSerializer<Integer>, JsonDeserializer<Integer> {
@Override
public Integer deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException {
try {
if (json.getAsString().equals("")) {
return 0;
}
} catch (Exception ignore) {
}
try {
return json.getAsInt();
} catch (NumberFormatException e) {
throw new JsonSyntaxException(e);
}
}
@Override
public JsonElement serialize(Integer src, Type typeOfSrc, JsonSerializationContext context) {
return new JsonPrimitive(src);
}
}

View File

@@ -0,0 +1,45 @@
package com.onekeycall.videotablet.gson;
import com.google.gson.Gson;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;
import java.io.IOException;
public class NullStringToEmptyAdapterFactory<T> implements TypeAdapterFactory {
@Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
Class<T> rawType = (Class<T>) type.getRawType();
if (rawType != String.class) {
return null;
}
return (TypeAdapter<T>) new StringAdapter();
}
public static class StringAdapter extends TypeAdapter<String> {
@Override
public String read(JsonReader reader) throws IOException {
if (reader.peek() == JsonToken.NULL) {
reader.nextNull();
return "";
}
return reader.nextString();
}
@Override
public void write(JsonWriter writer, String value) throws IOException {
if (value == null) {
writer.nullValue();
return;
}
writer.value(value);
}
}
}

View File

@@ -18,6 +18,8 @@ public class CustomWebSocketHandler extends TextWebSocketHandler {
// 存储活跃会话(线程安全)
private final Map<String, WebSocketSession> sessions = new ConcurrentHashMap<>();
@Override
public void afterConnectionEstablished(WebSocketSession session) {
String sessionId = session.getId();

View File

@@ -22,7 +22,7 @@ public class AuthHandshakeInterceptor implements HandshakeInterceptor {
String deviceId = request.getHeaders().getFirst("Device-ID");
String userId = request.getHeaders().getFirst("user_id");
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
if (authHeader == null || !authHeader.startsWith("Bearer ")||userId==null) {
return false;
}
String token = authHeader.substring(7); // 去掉 "Bearer " 前缀

View File

@@ -5,6 +5,7 @@ import com.onekeycall.videotablet.repository.UserRepository;
import com.onekeycall.videotablet.result.Result;
import com.onekeycall.videotablet.utils.AESUtil;
import com.onekeycall.videotablet.utils.SecureIdGenerator;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.core.userdetails.UserDetails;
@@ -17,18 +18,19 @@ import java.util.Date;
import java.util.Map;
@Service
@RequiredArgsConstructor
public class UserService implements UserDetailsService {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
private final RedisTemplate<String, Object> redisTemplate;
@Autowired
public UserService(UserRepository userRepository, PasswordEncoder passwordEncoder, RedisTemplate<String, Object> redisTemplate) {
this.userRepository = userRepository;
this.passwordEncoder = passwordEncoder;
this.redisTemplate = redisTemplate;
}
// @Autowired
// public UserService(UserRepository userRepository, PasswordEncoder passwordEncoder, RedisTemplate<String, Object> redisTemplate) {
// this.userRepository = userRepository;
// this.passwordEncoder = passwordEncoder;
// this.redisTemplate = redisTemplate;
// }
public User getUserByPhone(String phone) {
return userRepository.findUserByPhone(phone).orElse(null);

View File

@@ -1,6 +1,5 @@
package com.onekeycall.videotablet.utils;
import com.onekeycall.videotablet.controller.LoginController;
import com.onekeycall.videotablet.dto.TokenPair;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtException;
@@ -207,7 +206,7 @@ public class JwtUtil {
// 从Token中获取所有声明
private Claims getAllClaimsFromToken(String token) {
return Jwts.parser()
.setSigningKey(secret)
.setSigningKey(Keys.hmacShaKeyFor(secret.getBytes()))
.build()
.parseClaimsJws(token)
.getBody();
@@ -239,14 +238,14 @@ public class JwtUtil {
// 验证Token
public Boolean validateToken(String token, UserDetails userDetails) {
final String username = getUsernameFromToken(token);
return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
boolean isExpired = isTokenExpired(token);
boolean result = username.equals(userDetails.getUsername());
return result && !isExpired;
}
@Value("${jwt.tablet.secret}")
private String TABLET_SECRET;
/**
* 生成设备签名(首次绑定)
*

View File

@@ -56,5 +56,8 @@ logging.pattern.file=%d{yyyy-MM-dd} [%thread] %-5level %logger - %msg%n
logging.logback.rollingpolicy.max-file-size=10MB
logging.logback.rollingpolicy.max-history=30
logging.level.com.onekeycall.videotablet.filter=DEBUG
logging.level.org.springframework.security=DEBUG
mybatis.type-aliases-package=com.onekeycall.videotablet.entity
mybatis.mapperLocations=classpath:mapper/*.xml