登录和刷新token优化

This commit is contained in:
2025-08-21 09:14:33 +08:00
parent 8277e54662
commit 66d039b507
25 changed files with 854 additions and 69 deletions

View File

@@ -6,6 +6,8 @@ import com.onekeycall.videotablet.result.Result;
import com.onekeycall.videotablet.sms.SendSms;
import com.onekeycall.videotablet.utils.TextUtils;
import org.apache.commons.lang3.RandomStringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
@@ -20,6 +22,8 @@ import java.util.Map;
public class AliyunSmsController {
private static final int LOGIN_CODE_TTL = 5;
Logger logger = LoggerFactory.getLogger(AliyunSmsController.class);
//引入 redis
@Autowired
private StringRedisTemplate stringRedisTemplate;
@@ -83,18 +87,18 @@ public class AliyunSmsController {
} catch (TeaException error) {
// 此处仅做打印展示,请谨慎对待异常处理,在工程项目中切勿直接忽略异常。
// 错误 message
System.out.println(error.getMessage());
logger.error(error.getMessage());
// 诊断地址
System.out.println(error.getData().get("Recommend"));
logger.error(error.getData().get("Recommend").toString());
com.aliyun.teautil.Common.assertAsString(error.message);
return Result.error().data("sms", error.message);
} catch (Exception _error) {
TeaException error = new TeaException(_error.getMessage(), _error);
// 此处仅做打印展示,请谨慎对待异常处理,在工程项目中切勿直接忽略异常。
// 错误 message
System.out.println(error.getMessage());
logger.error(error.getMessage());
// 诊断地址
System.out.println(error.getData().get("Recommend"));
logger.error(error.getData().get("Recommend").toString());
com.aliyun.teautil.Common.assertAsString(error.message);
return Result.error().data("sms", error.message);
}

View File

@@ -8,14 +8,27 @@ import com.onekeycall.videotablet.service.UserService;
import com.onekeycall.videotablet.utils.JwtUtil;
import com.onekeycall.videotablet.utils.PushUtils;
import com.onekeycall.videotablet.utils.TextUtils;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import org.apache.commons.lang3.RandomStringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.oauth2.common.exceptions.InvalidTokenException;
import org.springframework.web.bind.annotation.*;
import javax.crypto.SecretKey;
import java.util.Base64;
import java.util.Date;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
@RestController
@RequestMapping("/public")
@RequestMapping("/sn")
public class BindSnController {
@Autowired
@@ -25,17 +38,27 @@ public class BindSnController {
@Autowired
private DeviceSnService deviceSnService;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
/**
* 用户app发送绑定推送到手机
*
* @param authHeader
* @param deviceId
* @param userId
* @param sn
* @return
*/
@PostMapping("/bind_sn")
public Result bindSn(
@RequestHeader("Authorization") String authHeader, @RequestHeader("Device-ID") String deviceId,
@RequestParam(value = "user_id") String userId, @RequestParam(value = "sn") 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");
}
@@ -43,7 +66,6 @@ public class BindSnController {
User user = userService.getUserByUserId(userId);
String userPhone = user.getPhone();
// 3. 校验 sn 是否存在
DeviceInfo deviceInfo = deviceSnService.findBySn(sn);
if (deviceInfo == null) {
return Result.error().message("sn not found");
@@ -55,9 +77,10 @@ public class BindSnController {
try {
String randomString = RandomStringUtils.randomAlphanumeric(32);
// PushUtils.aliyunAsyncPush(randomString, userPhone, sn);
PushUtils.tpnsPush(randomString, userPhone, sn);
String verifyKey = RandomStringUtils.randomAlphanumeric(32);
// PushUtils.aliyunAsyncPush(verifyKey, userPhone, sn);
PushUtils.tpnsPush(verifyKey, userPhone, sn);
redisTemplate.opsForValue().set(sn, verifyKey, 1, TimeUnit.MINUTES);
return Result.ok().message("send message success");
} catch (Exception e) {
e.printStackTrace();
@@ -66,41 +89,63 @@ public class BindSnController {
}
/**
* 平板根据返回的数据绑定手机
*
* @param authHeader
* @param deviceId
* @param userId
* @param sn
* @param verifyKey
* @return
*/
@PostMapping("/device_bind")
public Result deviceBind(
@RequestHeader("Authorization") String authHeader, @RequestHeader("Device-ID") String deviceId,
@RequestParam(value = "user_id") String userId, @RequestParam(value = "sn") String sn,
@RequestParam(value = "verify_key") String verifyKey) {
// 1. 校验 Authorization 头
String redisVerifyKey = (String) redisTemplate.opsForValue().get(sn);
if (redisVerifyKey == null) {
return Result.notFound().message("verify key not found");
}
if (!Objects.equals(redisVerifyKey, verifyKey)) {
return Result.error().message("verify key is not same");
}
if (!authHeader.startsWith("Bearer ")) {
return Result.error().message("Invalid Authorization header");
return Result.unAuthorized().message("Invalid Authorization header");
}
String token = authHeader.substring(7); // 去掉 "Bearer " 前缀
// 2. 校验 Token
if (!jwtUtil.validateAccessToken(userId, token, deviceId)) {
return Result.error().message("Invalid token");
return Result.unAuthorized().message("Invalid token");
}
User user = userService.getUserByUserId(userId);
if (user == null) {
return Result.error().message("user not found");
return Result.notFound().message("user not found");
}
String userPhone = user.getPhone();
// 3. 校验 sn 是否存在
DeviceInfo oldDeviceInfo = deviceSnService.findBySn(sn);
if (oldDeviceInfo == null) {
return Result.error().message("sn not found");
return Result.notFound().message("sn not found");
}
if (!TextUtils.isEmpty(oldDeviceInfo.getBindPhone())) {
return Result.error().message("sn already bind");
}
String deviceSig = jwtUtil.generateDeviceSig(sn);
String deviceToken = jwtUtil.generateDeviceToken(sn, deviceId);
oldDeviceInfo.setBindPhone(userPhone);
oldDeviceInfo.setDeviceAlias(user.getNickname() + "的平板");
oldDeviceInfo.setBindTime(new Date());
oldDeviceInfo.setDeviceModel(deviceId);
oldDeviceInfo.setBindSig(deviceSig);
oldDeviceInfo.setToken(deviceToken);
oldDeviceInfo.setSn(sn);
deviceSnService.save(oldDeviceInfo);
@@ -113,4 +158,32 @@ public class BindSnController {
}
}
/**
* 获取平板sn绑定状态
*
* @param Device_Token
* @param deviceId
* @param Device_Sig
* @param sn
* @return
*/
@GetMapping("/get_bind_statu")
public Result getBindStatus(
@RequestHeader("Device_Token") String Device_Token, @RequestHeader("Device-ID") String deviceId,
@RequestHeader("Device_Sig") String Device_Sig, @RequestParam(value = "sn") String sn) {
DeviceInfo deviceInfo = deviceSnService.findBySn(sn);
if (deviceInfo == null) {
return Result.notFound().message("sn not found");
}
if (TextUtils.isEmpty(deviceInfo.getBindPhone())) {
return Result.error().message("sn not bind");
}
return Result.ok().message("sn bind");
}
}

View File

@@ -0,0 +1,24 @@
package com.onekeycall.videotablet.controller;
import com.onekeycall.videotablet.entity.DeviceInfo;
import com.onekeycall.videotablet.entity.User;
import com.onekeycall.videotablet.result.Result;
import com.onekeycall.videotablet.service.DeviceSnService;
import com.onekeycall.videotablet.service.UserService;
import com.onekeycall.videotablet.utils.JwtUtil;
import com.onekeycall.videotablet.utils.PushUtils;
import com.onekeycall.videotablet.utils.TextUtils;
import org.apache.commons.lang3.RandomStringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/sn")
public class DevicesController {
@Autowired
private JwtUtil jwtUtil;
@Autowired
private DeviceSnService deviceSnService;
}

View File

@@ -5,6 +5,8 @@ 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 org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
@@ -26,6 +28,8 @@ public class LoginController {
@Autowired
private JwtUtil jwtUtil;
Logger logger = LoggerFactory.getLogger(LoginController.class);
@Autowired
public LoginController(UserService userService, AuthenticationManager authenticationManager) {
this.userService = userService;
@@ -65,6 +69,8 @@ public class LoginController {
public Result phoneLogin(
@RequestHeader("Device-ID") String deviceId,
@RequestParam String phone, @RequestParam String password) {
logger.info("phoneLogin: phone={}, password={}, deviceId={}", phone, password, deviceId);
User user = userService.getUserByPhone(phone);
if (user == null) {
return Result.error().message("手机号未注册");
@@ -93,7 +99,8 @@ public class LoginController {
public Result registerByPhone(
@RequestParam String phone, @RequestParam String code,
@RequestParam(value = "verify_key") String verifyKey, @RequestParam(value = "device_id") String deviceId) {
//
logger.info("registerByPhone: phone={}, code={}, verifyKey={}, deviceId={}", phone, code, verifyKey, deviceId);
// if (TextUtils.isEmpty(verifyKey)) {
// return Result.error().message("verify key is empty", HttpStatus.BAD_REQUEST);
// }
@@ -109,6 +116,7 @@ public class LoginController {
}
try {
User user = userService.registerByPhone(phone, code, deviceId, new Date());
logger.info("loginByPhoneCode: user={}", user.toString());
TokenPair tokenPair = jwtUtil.generateTokenPair(user.getUserId(), deviceId);
//返回给app保存access_token用来加入header请求接口refresh_token用来更换access_token
Map<String, Object> tokenMap = new HashMap<>();
@@ -122,7 +130,8 @@ public class LoginController {
return Result.error().message(e.getMessage());
}
} else {
return Result.error().message("verify key is expired");
// return Result.error().message("verify key is expired");
return Result.error().message("验证码已过期,请重新获取");
}
}
@@ -142,6 +151,7 @@ public class LoginController {
}
try {
User user = userService.loginByPhone(phone, code);
logger.info("loginByPhoneCode: user={}", user);
// 生成并返回JWT令牌实际项目中需要实现JWT逻辑
TokenPair tokenPair = jwtUtil.generateTokenPair(user.getUserId(), deviceId);
Map<String, Object> tokenMap = new HashMap<>();
@@ -158,9 +168,11 @@ public class LoginController {
return Result.error().message("verify key is expired");
}
}
// @PostMapping("/device_login")
// public Result loginByDeviceSn(){
//
// }
}

View File

@@ -5,18 +5,24 @@ import com.onekeycall.videotablet.entity.User;
import com.onekeycall.videotablet.result.Result;
import com.onekeycall.videotablet.service.DeviceSnService;
import com.onekeycall.videotablet.service.UserService;
import com.onekeycall.videotablet.utils.CXAESUtil;
import com.onekeycall.videotablet.utils.JwtUtil;
import com.onekeycall.videotablet.utils.PushUtils;
import com.onekeycall.videotablet.utils.TextUtils;
import org.apache.commons.lang3.RandomStringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/public")
public class ManageSnController {
Logger logger = LoggerFactory.getLogger(ManageSnController.class);
@Autowired
private JwtUtil jwtUtil;
@@ -50,8 +56,46 @@ public class ManageSnController {
DeviceInfo deviceInfo = new DeviceInfo();
deviceInfo.setSn(sn);
deviceInfo.setAddTime(new Date());
deviceInfo.setDeviceModel(deviceId);
deviceSnService.save(deviceInfo);
return Result.ok();
}
@GetMapping("/decode_sn")
public Result decodeSn(
@RequestHeader("Authorization") String authHeader, @RequestHeader("Device-ID") String deviceId,
@RequestParam(value = "user_id") String userId, @RequestParam(value = "encrypt_sn") String encryptSn) throws Exception {
logger.info("Authorization = {}, Device-ID = {}, user_id = {}, encrypt_sn = {}", authHeader, deviceId, userId, encryptSn);
// 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");
}
// 3. 解密 sn
String sn = CXAESUtil.decrypt(CXAESUtil.key, encryptSn);
logger.info("sn = {}", sn);
if (TextUtils.isEmpty(sn)) {
return Result.error().message("sn decrypt failed");
}
DeviceInfo deviceInfo = deviceSnService.findBySn(sn);
if (deviceInfo == null) {
return Result.error().message("sn not found");
}
if (!TextUtils.isEmpty(deviceInfo.getBindPhone())) {
return Result.ok().message("sn already bind");
}
Map<String, Object> map = new HashMap<>();
map.put("sn", sn);
return Result.ok().data(map);
}
}

View File

@@ -12,6 +12,8 @@ import com.tencentcloudapi.sms.v20210111.models.SendSmsRequest;
import com.tencentcloudapi.sms.v20210111.models.SendSmsResponse;
import com.tencentcloudapi.sms.v20210111.models.SendStatus;
import org.apache.commons.lang3.RandomStringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
@@ -27,6 +29,8 @@ import java.util.Map;
public class TencentSmsController {
private static final int LOGIN_CODE_TTL = 5;
Logger logger = LoggerFactory.getLogger(TencentSmsController.class);
@Autowired
private StringRedisTemplate stringRedisTemplate;
@@ -144,10 +148,10 @@ public class TencentSmsController {
SendSmsResponse res = client.SendSms(req);
// 输出json格式的字符串回包
System.out.println(SendSmsResponse.toJsonString(res));
logger.info(SendSmsResponse.toJsonString(res));
// 也可以取出单个值您可以通过官网接口文档或跳转到response对象的定义处查看返回字段的定义
// System.out.println(res.getRequestId());
// logger.info(res.getRequestId());
/* 当出现以下错误码时,快速解决方案参考
* [FailedOperation.SignatureIncorrectOrUnapproved](https://cloud.tencent.com/document/product/382/9558#.E7.9F.AD.E4.BF.A1.E5.8F.91.E9.80.81.E6.8F.90.E7.A4.BA.EF.BC.9Afailedoperation.signatureincorrectorunapproved-.E5.A6.82.E4.BD.95.E5.A4.84.E7.90.86.EF.BC.9F)
@@ -168,7 +172,7 @@ public class TencentSmsController {
map.put("expireAt", expireAt);
Map<String, Object> codeMap = new HashMap<>(map);
codeMap.put("code", code);
System.out.println(codeMap);
logger.info(codeMap.toString());
if (!sent) {
//4.保存验证码到Redis,并且设置有效期5分钟
redisTemplate.opsForValue().set(phone, codeMap, Duration.ofMinutes(5));

View File

@@ -0,0 +1,93 @@
package com.onekeycall.videotablet.controller;
import com.nimbusds.openid.connect.sdk.claims.UserInfo;
import com.onekeycall.videotablet.dto.TokenPair;
import com.onekeycall.videotablet.entity.DeviceInfo;
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 org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/user")
public class UserController {
private final UserService userService;
private final AuthenticationManager authenticationManager;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private JwtUtil jwtUtil;
Logger logger = LoggerFactory.getLogger(LoginController.class);
@Autowired
public UserController(UserService userService, AuthenticationManager authenticationManager) {
this.userService = userService;
this.authenticationManager = authenticationManager;
}
@PostMapping("/refresh_token")
public Result refreshToken(
@RequestHeader(value = "Authorization", required = false) String authHeader, @RequestHeader("Device-ID") String deviceId,
@RequestParam (value = "user_id") String userId, @RequestParam("refresh_token") String refreshToken) {
logger.info("refreshToken: Authorization={} userId={} deviceId={} refreshToken={}", authHeader, userId, deviceId, refreshToken);
try {
// 验证refreshToken的有效性
if (!jwtUtil.validateRefreshToken(refreshToken, userId)) {
return Result.error().message("无效的refresh token");
}
// 从refreshToken中获取用户ID
TokenPair tokenPair = jwtUtil.refreshAccessToken(refreshToken, deviceId);
// 构建返回结果
Map<String, Object> tokenMap = new HashMap<>();
tokenMap.put("access_token", tokenPair.getAccess_token());
return Result.ok().data(tokenMap);
} catch (Exception e) {
logger.error("刷新token失败", e);
return Result.error().message("刷新token失败: " + e.getMessage());
}
}
@GetMapping("/get_user_info")
public Result getUserInfo(
@RequestHeader("Authorization") String authHeader, @RequestHeader("Device-ID") String deviceId,
@RequestParam(value = "user_id") String userId
) {
// 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");
}
User user = userService.getUserByUserId(userId);
Map<String, Object> userInfo = new HashMap<>();
userInfo.put("user_id", user.getUserId());
userInfo.put("phone", user.getPhone());
userInfo.put("nickname", user.getNickname());
userInfo.put("avatar", user.getAvatar());
return Result.ok().data("user_info", userInfo);
}
}

View File

@@ -1,5 +1,6 @@
package com.onekeycall.videotablet.controller;
import com.onekeycall.videotablet.entity.User;
import com.onekeycall.videotablet.result.Result;
import com.onekeycall.videotablet.service.UserService;
import com.onekeycall.videotablet.utils.JwtUtil;
@@ -27,6 +28,32 @@ public class UserPasswordController {
this.authenticationManager = authenticationManager;
}
@PostMapping("/set_first_password")
public Result setFirstPassword(@RequestHeader("Authorization") String authHeader, @RequestHeader("Device-ID") String deviceId,
@RequestParam(value = "user_id") String userId, @RequestParam(value = "nick_name") String nickName,
@RequestParam String password, @RequestParam(value = "verify_password") String verifyPassword) {
if (!authHeader.startsWith("Bearer ")) {
return Result.error().message("Invalid Authorization header");
}
User user = userService.getUserByUserId(userId);
if (user == null) {
return Result.error().message("user not found");
}
String token = authHeader.substring(7); // 去掉 "Bearer " 前缀
if (!jwtUtil.validateAccessToken(userId, token, deviceId)) {
return Result.error().message("Invalid token");
}
if (!StringUtils.equals(password, verifyPassword)) {
return Result.error().message("password is not same");
}
userService.setPasswordByUserId(userId, password);
return Result.ok().message("set first password success");
}
@PostMapping("/phone_set_password")
public Result setPasswordByPhone(
@RequestHeader("Authorization") String authHeader, @RequestHeader("Device-ID") String deviceId,