优化统一鉴权,优化目录结构
This commit is contained in:
@@ -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()
|
||||
);
|
||||
|
||||
@@ -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") // 客户端连接端点
|
||||
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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");
|
||||
@@ -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;
|
||||
@@ -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(
|
||||
@@ -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,
|
||||
@@ -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;
|
||||
@@ -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() {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
153
src/main/java/com/onekeycall/videotablet/gson/GsonUtils.java
Normal file
153
src/main/java/com/onekeycall/videotablet/gson/GsonUtils.java
Normal 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());
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 " 前缀
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
/**
|
||||
* 生成设备签名(首次绑定)
|
||||
*
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user