访客登录认证模块
This commit is contained in:
1 parent
4f688ffd2e
commit
a895c16ee2
7 files changed
+200
-29
No files matched your search
+1
-1
@@ -20,7 +20,7 @@ public interface CacheConstants {
|
||||
*/
|
||||
String DEFAULT_CODE_KEY = "DEFAULT_CODE_KEY:";
|
||||
|
||||
String VISITOR_CODE_KEY = "visitor_code_key";
|
||||
String VISITOR_CODE_KEY = "visitor_code_key:";
|
||||
|
||||
|
||||
String SMS_CODE_KEY = "smsCode_";
|
||||
|
||||
@@ -14,11 +14,16 @@
|
||||
|
||||
<dependencies>
|
||||
<!-- Token-->
|
||||
<!-- <dependency>-->
|
||||
<!-- <groupId>io.jsonwebtoken</groupId>-->
|
||||
<!-- <artifactId>jjwt</artifactId>-->
|
||||
<!-- <version>0.9.1</version>-->
|
||||
<!-- </dependency>-->
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt</artifactId>
|
||||
<version>0.9.1</version>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-jwt</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 幂等插件使用 -->
|
||||
<dependency>
|
||||
<groupId>com.bsd</groupId>
|
||||
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
package com.bsd.pjcloud.integrated.config;
|
||||
|
||||
import cn.hutool.jwt.JWT;
|
||||
import cn.hutool.jwt.JWTUtil;
|
||||
import com.bsd.pjcloud.integrated.service.VisitorsService;
|
||||
import com.bsd.pjcloud.integrated.service.VisitorUserService;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
|
||||
@Component
|
||||
public class VisitorTokenInterceptor implements HandlerInterceptor {
|
||||
@Value("${visitor.jwt-secret}")
|
||||
private String secret;
|
||||
|
||||
private final VisitorUserService visitorUserService;
|
||||
|
||||
public VisitorTokenInterceptor(@Lazy VisitorUserService visitorUserService) {
|
||||
this.visitorUserService = visitorUserService;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
|
||||
String token = request.getHeader("token");
|
||||
if (token == null || token.isEmpty()) {
|
||||
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
|
||||
response.getWriter().write("Unauthorized: token missing");
|
||||
return false;
|
||||
}
|
||||
JWT jwt = JWTUtil.parseToken(token);
|
||||
boolean verify = jwt.setKey(secret.getBytes()).verify();
|
||||
if (!verify) {
|
||||
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
|
||||
response.getWriter().write("Unauthorized: token invalid");
|
||||
return false;
|
||||
}
|
||||
// 校验过期时间
|
||||
Object expObj = jwt.getPayload("exp");
|
||||
if (expObj == null || Long.parseLong(expObj.toString()) < System.currentTimeMillis()) {
|
||||
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
|
||||
response.getWriter().write("Unauthorized: token expired");
|
||||
return false;
|
||||
}
|
||||
Object userType = jwt.getPayload("userType");
|
||||
if (userType == null || !"visitor".equals(userType.toString())) {
|
||||
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
|
||||
response.getWriter().write("Unauthorized: not visitor");
|
||||
return false;
|
||||
}
|
||||
// 账号状态校验
|
||||
Object idObj = jwt.getPayload("id");
|
||||
if (idObj == null) {
|
||||
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
|
||||
response.getWriter().write("Unauthorized: no user id");
|
||||
return false;
|
||||
}
|
||||
Long userId = Long.valueOf(idObj.toString());
|
||||
com.bsd.pjcloud.integrated.entity.VisitorUserEntity user = visitorUserService.getById(userId);
|
||||
if (user == null || "1".equals(user.getStatus())) {
|
||||
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
|
||||
response.getWriter().write("Unauthorized: account locked or not exist");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
package com.bsd.pjcloud.integrated.config;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
@Configuration
|
||||
@RequiredArgsConstructor
|
||||
public class WebMvcConfig implements WebMvcConfigurer {
|
||||
private final VisitorTokenInterceptor visitorTokenInterceptor;
|
||||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry) {
|
||||
registry.addInterceptor(visitorTokenInterceptor)
|
||||
.addPathPatterns("/app/visitor/**");
|
||||
}
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
package com.bsd.pjcloud.integrated.controller;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.jwt.JWT;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.bsd.pjcloud.common.core.constant.CacheConstants;
|
||||
import com.bsd.pjcloud.common.core.util.R;
|
||||
import com.bsd.pjcloud.common.security.annotation.Inner;
|
||||
import com.bsd.pjcloud.integrated.entity.CarInfoEntity;
|
||||
import com.bsd.pjcloud.integrated.entity.VisitorUserEntity;
|
||||
import com.bsd.pjcloud.integrated.service.VisitorUserService;
|
||||
import com.bsd.pjcloud.integrated.service.VisitorsService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springdoc.core.annotations.ParameterObject;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 访客用户
|
||||
*
|
||||
* @author zjf
|
||||
* @date 2025-07-17 14:58:26
|
||||
*/
|
||||
@RestController
|
||||
@RequiredArgsConstructor
|
||||
@RequestMapping("/app/visitor" )
|
||||
@Tag(description = "visitorUser" , name = "访客用户管理" )
|
||||
@SecurityRequirement(name = HttpHeaders.AUTHORIZATION)
|
||||
@Inner(value = false)
|
||||
public class AppVisitorController {
|
||||
|
||||
private final VisitorUserService visitorUserService;
|
||||
@Autowired
|
||||
private RedisTemplate redisTemplate;
|
||||
|
||||
private final VisitorsService visitorsService;
|
||||
|
||||
@Operation(summary = "分页查询" , description = "分页查询" )
|
||||
@GetMapping("/page" )
|
||||
public R getCarInfoPage(@ParameterObject Page page, @ParameterObject CarInfoEntity carInfo) {
|
||||
|
||||
return R.ok(visitorsService.list());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+42
-25
@@ -3,6 +3,7 @@ package com.bsd.pjcloud.integrated.controller;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.jwt.JWT;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
@@ -14,7 +15,6 @@ import com.bsd.pjcloud.common.log.annotation.SysLog;
|
||||
import com.bsd.pjcloud.common.security.annotation.Inner;
|
||||
import com.bsd.pjcloud.integrated.entity.VisitorUserEntity;
|
||||
import com.bsd.pjcloud.integrated.service.VisitorUserService;
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
@@ -24,10 +24,12 @@ import lombok.RequiredArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
import org.springdoc.core.annotations.ParameterObject;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.security.oauth2.jwt.Jwt;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
@@ -45,39 +47,52 @@ import java.util.concurrent.TimeUnit;
|
||||
@RequestMapping("/app/visitorUser" )
|
||||
@Tag(description = "visitorUser" , name = "访客用户管理" )
|
||||
@SecurityRequirement(name = HttpHeaders.AUTHORIZATION)
|
||||
@Inner(value = false)
|
||||
public class AppVisitorUserController {
|
||||
|
||||
private final VisitorUserService visitorUserService;
|
||||
@Autowired
|
||||
private RedisTemplate redisTemplate;
|
||||
|
||||
@Value("${visitor.jwt-secret}")
|
||||
private String secret;
|
||||
|
||||
|
||||
// 登录验证
|
||||
@PostMapping("/login")
|
||||
// @Inner(value = false)
|
||||
@Transactional
|
||||
public R<Map<String, Object>> login(
|
||||
@RequestParam String phone,
|
||||
@RequestParam String smsCode,
|
||||
@RequestParam String randomStr,
|
||||
@RequestParam String imageCode,
|
||||
@RequestParam(value = "realName", required = false) String realName
|
||||
) {
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
|
||||
//0. 验证图片验证码
|
||||
if (!verifyImageCode(randomStr, imageCode)) {
|
||||
return R.failed("图片验证码错误或已过期");
|
||||
}
|
||||
|
||||
// 1. 验证短信验证码
|
||||
if (verifySmsCode(phone,randomStr, smsCode)) {
|
||||
if (!verifySmsCode(phone, smsCode)) {
|
||||
return R.failed("短信验证码错误或已过期");
|
||||
}
|
||||
|
||||
// 2. 查找或创建账户
|
||||
VisitorUserEntity account = visitorUserService.getOne(new LambdaQueryWrapper<VisitorUserEntity>().eq(VisitorUserEntity::getPhone, phone));
|
||||
account.setLastLogin(LocalDateTime.now());
|
||||
if (account == null){
|
||||
account = new VisitorUserEntity();
|
||||
account.setPhone(phone);
|
||||
account.setRealName(realName);
|
||||
account.setStatus("0");
|
||||
account.setRegisterTime(LocalDateTime.now());
|
||||
account.setLastLogin(LocalDateTime.now());
|
||||
visitorUserService.save(account);
|
||||
}else {
|
||||
account.setLastLogin(LocalDateTime.now());
|
||||
// 3. 检查账户状态
|
||||
if (account.getStatus().equals("1")) {
|
||||
response.put("code", 4006);
|
||||
@@ -87,16 +102,14 @@ public class AppVisitorUserController {
|
||||
visitorUserService.updateById( account);
|
||||
}
|
||||
|
||||
Map<String, Object> stringStringHashMap = new HashMap<>();
|
||||
stringStringHashMap.put("phone", phone);
|
||||
stringStringHashMap.put("realName", realName);
|
||||
stringStringHashMap.put("randomStr", randomStr);
|
||||
stringStringHashMap.put("smsCode", smsCode);
|
||||
String token=Jwts.builder().setSubject(account.getId().toString())
|
||||
.setIssuedAt(new Date())
|
||||
.setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 24 * 7))
|
||||
.setClaims(stringStringHashMap).compact();
|
||||
|
||||
String token = cn.hutool.jwt.JWT.create()
|
||||
.setPayload("id", account.getId())
|
||||
.setPayload("phone", account.getPhone())
|
||||
.setPayload("userType", "visitor")
|
||||
.setPayload("realName", account.getRealName())
|
||||
.setPayload("exp", System.currentTimeMillis() + 1000 * 60 * 60 * 24 * 7)
|
||||
.setKey(secret.getBytes())
|
||||
.sign();
|
||||
|
||||
response.put("code", 200);
|
||||
response.put("message", "登录成功");
|
||||
@@ -105,21 +118,25 @@ public class AppVisitorUserController {
|
||||
return R.ok(response);
|
||||
}
|
||||
|
||||
private boolean verifySmsCode(String phone, String randomStr, String smsCode) {
|
||||
String key = CacheConstants.SMS_VISITOR_CODE_KEY + phone + "_" + randomStr;
|
||||
return !redisTemplate.opsForValue().get(key).equals(smsCode);
|
||||
}
|
||||
|
||||
|
||||
private String getClientIp(HttpServletRequest request) {
|
||||
String ip = request.getHeader("X-Forwarded-For");
|
||||
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
|
||||
ip = request.getRemoteAddr();
|
||||
private boolean verifyImageCode(String randomStr, String imageCode) {
|
||||
String key = CacheConstants.VISITOR_CODE_KEY + randomStr;
|
||||
Object imageCodeValueObj = redisTemplate.opsForValue().get(key);
|
||||
if (imageCodeValueObj == null) {
|
||||
return false;
|
||||
}
|
||||
return ip;
|
||||
String imageCodeValue = imageCodeValueObj.toString();
|
||||
return imageCodeValue.equals(imageCode);
|
||||
}
|
||||
|
||||
|
||||
private boolean verifySmsCode(String phone, String smsCode) {
|
||||
String key = CacheConstants.SMS_VISITOR_CODE_KEY + phone;
|
||||
Object smsCodeValueObj = redisTemplate.opsForValue().get(key);
|
||||
if (smsCodeValueObj == null) {
|
||||
return false;
|
||||
}
|
||||
String o = smsCodeValueObj.toString();
|
||||
return o.equals(smsCode);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -23,3 +23,5 @@ spring:
|
||||
# username: root
|
||||
# password: root
|
||||
# url: jdbc:mysql://pjcloud-mysql:3306/pjcloudx_integrated?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=GMT%2B8&allowMultiQueries=true
|
||||
visitor:
|
||||
jwt-secret: "u8Jk2n3Lk4pQ5r6T7s8V9w0X1y2Z3a4B"
|
||||
Reference in new issue
Block a user