Spring Boot 项目用户模块设计:注册登录、权限管控与敏感数据加密
基于 Spring Boot 的用户模块设计方案,涵盖注册登录流程、权限管控及敏感数据加密技术。核心内容包括密码加盐哈希存储、手机号 AES 加密、MyBatis TypeHandler 自动处理、全局异常处理机制。登录功能采用 JWT 实现无状态认证,结合 Redis 缓存验证码。此外还实现了强制登录拦截器、管理员后台管理及用户列表展示等功能,为构建安全的用户系统提供完整后端架构参考。

基于 Spring Boot 的用户模块设计方案,涵盖注册登录流程、权限管控及敏感数据加密技术。核心内容包括密码加盐哈希存储、手机号 AES 加密、MyBatis TypeHandler 自动处理、全局异常处理机制。登录功能采用 JWT 实现无状态认证,结合 Redis 缓存验证码。此外还实现了强制登录拦截器、管理员后台管理及用户列表展示等功能,为构建安全的用户系统提供完整后端架构参考。

用户注册时,密码和手机号等敏感信息需加密存储,避免明文泄露风险。
Hutool 官方文档:https://hutool.cn/ Maven 仓库地址:https://mvnrepository.com/artifact/cn.hutool
可引入以下依赖使用:
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.25</version>
</dependency>
前后端交互接口
/register POST{
"name": "张三",
"mail": "[email protected]",
"phoneNumber": "13188888888",
"password": "123456789",
"identity": "ADMIN"
}
{
"code": 200,
"data": {
"userId": 22
},
"msg": ""
}
Controller 层接口设计
@Validated 注解进行参数校验,调用 Service 层完成注册逻辑,返回注册结果。UserRegisterParam:注册入参封装(姓名、邮箱、手机号、密码、身份),含 @NotBlank 等校验注解。UserRegisterResult:注册出参封装(用户 ID)。UserIdentityEnum:用户身份枚举(普通用户 NORMAL、管理员 ADMIN)。控制层代码示例:
@RestController
public class UserController {
private static final Logger logger = LoggerFactory.getLogger(UserController.class);
@Autowired
private UserService userService;
@Autowired
private VerificationCodeService verificationCodeService;
/**
* 注册
* @param param
* @return
*/
@PostMapping("/register")
public CommonResult<UserRegisterResult> userRegister(@Validated @RequestBody UserRegisterParam param) {
// 日志打印
logger.info("userRegister UserRegisterParam 用户注册:{}", JacksonUtil.writeValueAsString(param));
// 调用 Service 层服务进行访问
UserRegisterDTO userRegisterDTO = userService.register(param);
return CommonResult.success(convertToUserRegisterResult(userRegisterDTO));
}
private UserRegisterResult convertToUserRegisterResult(UserRegisterDTO userRegisterDTO) {
UserRegisterResult result = new UserRegisterResult();
if (null == userRegisterDTO) {
throw new ControllerException(ControllerErrorCodeConstants.REGISTER_ERROR);
}
result.setUserId(userRegisterDTO.getUserId());
return result;
}
}
使用 SpringBoot 中集成的 Validation 需要引入依赖:
<!-- spring-boot 2.3 及以上的版本只需要引入下面的依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
Service 层接口设计
UserService 接口,UserServiceImpl 实现具体逻辑:
RegexUtil,封装邮箱、手机号、密码的格式校验正则表达式。实现类代码示例:
@Slf4j
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Autowired
private VerificationCodeService verificationCodeService;
/**
* 注册
*
* @param param
* @return
*/
@Override
public UserRegisterDTO register(UserRegisterParam param) {
// 校验用户信息
checkRegisterInfo(param);
// 加密数据
UserDO userDO = new UserDO();
userDO.setUserName(param.getName());
userDO.setEmail(param.getMail());
userDO.setPhoneNumber(newEncrypt(param.getPhoneNumber()));
userDO.setIdentity(param.getIdentity());
if (StringUtils.hasLength(param.getPassword())) {
userDO.setPassword(DigestUtil.sha256Hex(param.getPassword()));
}
// 保存用户数据
userMapper.insert(userDO);
// 构造返回 UserRegisterDTO
UserRegisterDTO userRegisterDTO = new UserRegisterDTO();
userRegisterDTO.setUserId(userDO.getId());
return userRegisterDTO;
}
private void checkRegisterInfo(UserRegisterParam param) {
if (null == param) {
throw new ServiceException(ServiceErrorCodeConstants.REGISTER_INFO_IS_EMPTY);
}
(!RegexUtil.checkMail(param.getMail())) {
(ServiceErrorCodeConstants.MAIL_ERROR);
}
(!RegexUtil.checkMobile(param.getPhoneNumber())) {
(ServiceErrorCodeConstants.PHONE_NUMBER_ERROR);
}
( == UserIdentityEnum.forName(param.getIdentity())) {
(ServiceErrorCodeConstants.IDENTITY_ERROR);
}
(param.getIdentity().equalsIgnoreCase(UserIdentityEnum.ADMIN.name()) && !StringUtils.hasLength(param.getPassword())) {
(ServiceErrorCodeConstants.PASSWORD_IS_EMPTY);
}
(StringUtils.hasLength(param.getPassword()) && !RegexUtil.checkPassword(param.getPassword())) {
(ServiceErrorCodeConstants.PASSWORD_ERROR);
}
(checkMailUsed(param.getMail())) {
(ServiceErrorCodeConstants.MAIL_USED);
}
(checkPhoneNumberUsed(param.getPhoneNumber())) {
(ServiceErrorCodeConstants.PHONE_NUMBER_USED);
}
}
{
userMapper.countByPhone(newEncrypt(phoneNumber));
count > ;
}
{
userMapper.countByMail(mail);
count > ;
}
}
接口分离设计的好处: 有助于创建更加灵活、可维护和可扩展的软件系统。抽象与具体实现分离:接口定义了一组操作的契约,而实现则提供了这些操作的具体行为。这种分离允许改变具体实现而不影响使用接口的客户端代码。支持多态性:接口允许通过共同的接口来引用不同的实现,这是多态性的基础,使得代码更加灵活和通用。提高代码的可读性和可理解性:接口提供了清晰的 API 视图,使得其他开发者能够更容易地理解和使用这些 API。安全性:接口可以隐藏实现细节,只暴露必要的操作,这有助于保护系统的内部状态和实现不被外部直接访问。遵循开闭原则:软件实体应当对扩展开放,对修改封闭。接口与实现的分离使得在不修改客户端代码的情况下扩展系统的功能。促进面向对象的设计:接口与实现的分离鼓励开发者进行面向对象的设计,考虑如何将系统分解为可重用和可组合的组件。
Dao 层接口设计
UserMapper:
insert:插入用户信息,支持自动生成主键。countByPhoneNumber:查询手机号绑定的用户数(校验唯一性)。countByMail:查询邮箱绑定的用户数(校验唯一性)。UserDO,映射用户表,继承 BaseDO(含主键 id、创建时间 gmtCreate、更新时间 gmtModified)。注册页面前端实现
/register 接口,携带用户注册信息。对手机号进行存储时,要先将手机号加密,如果要拿出使用时,还要进行一次解密操作。为简化手机号的自动加解密操作,使用 MyBatis 的 TypeHandler 实现字段处理。
TypeHandler:简单理解就是当处理特殊字段时,实现一些方法,让 mybatis 遇到这些特定字段可以自动运行处理。
BaseTypeHandler<Encrypt>,重写以下方法:
setNonNullParameter:设置参数时对手机号进行 AES 加密。getNullableResult:查询结果时对手机号进行 AES 解密。application.properties 中指定 TypeHandler 的包路径。使用 @RestControllerAdvice + @ExceptionHandler 实现全局异常处理,统一响应格式。可以针对所有异常类型先进行通用处理后,再对特定异常类型进行不同的处理操作。
Exception、ServiceException、ControllerException 三类异常。CommonResult 格式的错误响应,包含错误码和错误提示。采用阿里云短信服务发送验证码,支持短信登录方式。因为没用企业认证,只能使用其提供的一些特定模板,示例如下:
配置
application.properties 中配置阿里云短信服务的 AccessKeyId、AccessKeySecret、签名名称。核心工具类
SMSUtil:封装短信发送逻辑,通过阿里云 SDK 调用短信接口,处理发送结果。CaptchaUtil:基于 Hutool 生成随机数字验证码。CaptchaUtil 示例:
/**
* 随机验证码生成工具
*/
public class CaptchaUtil {
/**
* 生成随机验证码
*
* @param length 验证码位数
* @return
*/
public static String getCaptcha(int length) {
// 自定义纯数字的验证码(随机 4 位数字,可重复)
RandomGenerator randomGenerator = new RandomGenerator("0123456789", length);
LineCaptcha lineCaptcha = cn.hutool.captcha.CaptchaUtil.createLineCaptcha(200, 100);
lineCaptcha.setGenerator(randomGenerator);
// 重新生成 code
lineCaptcha.createCode();
return lineCaptcha.getCode();
}
}
前后端交互接口
/verification-code/send?phoneNumber=13199999999 GET{
"code": 200,
"data": true,
"msg": ""
}
Controller 层接口设计
VerificationCodeService 发送验证码,返回发送结果。Service 层接口设计
VerificationCodeService 接口定义发送验证码和获取验证码的方法。VerificationCodeServiceImpl 实现逻辑:
SMSUtil 发送短信。VerificationCodeServiceImpl 阿里云短信验证码服务类示例:
/**
* 阿里云短信验证码服务类
*/
@Service
public class VerificationCodeServiceImpl implements VerificationCodeService {
@Autowired
private SMSUtil smsUtil;
@Autowired
private RedisUtil redisUtil;
/**
* 发送并缓存验证码
*
* @param phoneNumber
*/
@Override
public void sendVerificationCode(String phoneNumber) {
// 校验手机号
if (!RegexUtil.checkMobile(phoneNumber)) {
throw new ServiceException(ServiceErrorCodeConstants.PHONE_NUMBER_ERROR);
}
// 生成随机验证码,长度 4
String code = CaptchaUtil.getCaptcha(Constants.CODE_LENGTH);
// 发送验证码
smsUtil.sendVerifyCode(phoneNumber, code);
// 缓存验证码
// 一个手机号对应一个验证码,再次接收会覆盖
redisUtil.set(Constants.VERIFICATION_CODE_PREFIX + phoneNumber, code, Constants.VERIFICATION_CODE_TIMEOUT);
}
}
RedisUtil基于 StringRedisTemplate 封装 Redis 操作,避免乱码问题,核心方法:
hasKey:判断键是否存在。setExpire:设置键的过期时间。getExpire:获取键的过期时间。del:删除键。get:获取字符串类型键的值。set:设置字符串类型键的值(支持过期时间)。RedisUtil 示例:
/**
* Redis 工具缓存
*/
@Configuration
public class RedisUtil {
private static final Logger logger = LoggerFactory.getLogger(RedisUtil.class);
@Autowired
private StringRedisTemplate stringRedisTemplate;
// --------- String ---------
/**
* 设置值
*
* @param key
* @param value
* @return
*/
public boolean set(String key, String value) {
try {
stringRedisTemplate.opsForValue().set(key, value);
return true;
} catch (Exception e) {
logger.error("RedisUtil error,set({}, {})", key, value, e);
return false;
}
}
/**
* 设置值(可设置过期时间)
*
* @param key
* @param value
* @param time 单位:秒
* @return
*/
public boolean set(String key, String value, Long time) {
try {
stringRedisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
return true;
} catch (Exception e) {
logger.error("RedisUtil error,set({}, {}, {})", key, value, time, e);
return false;
}
}
String {
{
StringUtils.hasText(key) ? stringRedisTemplate.opsForValue().get(key) : ;
} (Exception e) {
logger.error(, key, e);
;
}
}
}
采用 JWT(JSON Web Token)实现无状态登录,解决集群环境下 Session 共享问题。
JWT 全称:JSON Web Token,官网:https://jwt.io/
JSON Web Token (JWT) 是一个开放的行业标准 (RFC 7519),用于客户端和服务器之间传递安全可靠的信息。 其本质是一个 token,是一种紧凑的 URL 安全方法。
JWT 组成: JWT 由三部分组成,每部分中间使用点 (.) 分隔,例如:aaaaa.bbbbb.cccc
Signature (签名) 此部分用于防止 JWT 内容被篡改,确保安全性。
防止被篡改,而不是防止被解析。
JWT 之所以安全,就是因为最后的签名。JWT 当中任何一个字符被篡改,整个令牌都会校验失败。 就好比身份证,之所以能标识一个人的身份,是因为它不能被篡改,而不是因为内容加密。(任何人都可以看到身份证的信息,JWT 也是)
Payload (负载) 负载部分是存放有效信息的地方,里面是一些自定义内容。比如:
{"userId":"123","userName":"zhangsan"}
也可以存在 JWT 提供的现成字段,比如 exp (过期时间戳) 等。
此部分不建议存放敏感信息,因为此部分可以解码还原原始内容。
JWTUtilgenJwt:生成 JWT 令牌,包含自定义载荷(用户 ID、身份)、签发时间、过期时间(1 小时),使用 HMAC SHA 算法签名。parseJWT:解析 JWT 令牌,验证签名合法性,返回载荷信息。getUserIdFromToken:从令牌中提取用户 ID。JWTUtil 示例:
public class JWTUtil {
private static final Logger logger = LoggerFactory.getLogger(JWTUtil.class);
/**
* 密钥:Base64 编码的密钥
*/
private static final String SECRET = "********";
/**
* 生成安全密钥:将一个 Base64 编码的密钥解码并创建一个 HMAC SHA 密钥。
*/
private static final SecretKey SECRET_KEY = Keys.hmacShaKeyFor(Decoders.BASE64.decode(SECRET));
/**
* 过期时间 (单位:毫秒)
*/
private static final long EXPIRATION = 24 * 60 * 60 * 1000;
/**
* 生成密钥
*
* @param claim {"id": 12, "name":"张山"}
* @return
*/
public static String genJwt(Map<String, Object> claim) {
// 签名算法
String jwt = Jwts.builder()
.setClaims(claim) // 自定义内容 (载荷)
.setIssuedAt(new Date()) // 设置签发时间
.setExpiration( (System.currentTimeMillis() + EXPIRATION))
.signWith(SECRET_KEY)
.compact();
jwt;
}
Claims {
(!StringUtils.hasLength(jwt)) {
;
}
Jwts.parserBuilder().setSigningKey(SECRET_KEY);
;
{
claims = jwtParserBuilder.build().parseClaimsJws(jwt).getBody();
} (Exception e) {
logger.error(, jwt, e);
}
claims;
}
}
支持'电话 + 密码'和'电话 + 验证码'两种登录方式,基于 JWT 返回令牌。
登录流程:
前后端交互接口
/password/login POST{
"loginName": "13199999999",
"password": "123456",
"mandatoryIdentity": "ADMIN"
}
{
"code": 200,
"data": {
"token": "eyJhbGciOiJIUzI1NiJ9...",
"identity": "ADMIN"
},
"msg": ""
}
/message/login POST{
"loginMobile": "13199999999",
"verificationCode": "0475",
"mandatoryIdentity": "ADMIN"
}
{
"code": 200,
"data": {
"token": "eyJhbGciOiJIUzI1NiJ9...",
"identity": "ADMIN"
},
"msg": ""
}
Controller 层接口设计
@Validated 校验,调用 UserService 完成登录逻辑,返回 UserLoginResult(含 token 和身份)。UserPasswordLoginParam:密码登录入参(登录名、密码、强制身份)。ShortMessageLoginParam:验证码登录入参(手机号、验证码、强制身份)。UserLoginResult:登录出参(token、身份)。Service 层接口设计
UserService 接口新增 login 方法,支持不同登录参数类型。UserServiceImpl 实现逻辑:
Dao 层接口设计
UserMapper 新增接口:
selectByEmail:通过邮箱查询用户信息。selectByPhoneNumber:通过手机号查询用户信息。登录页面前端实现
通过拦截器实现非登录页面的强制登录校验。例如,当用户当前尚未登陆,访问抽奖页时希望自动跳转到登录页面。
ajaxSend 方法在请求头中添加 user_token(从 localStorage 获取),让后端取校验。登录拦截器 LoginInterceptor
实现 HandlerInterceptor,重写 preHandle 方法:
user_token。JWTUtil 解析令牌,校验合法性。拦截器类示例:
/**
* 登录拦截器
* */
@Slf4j
@Component
public class LoginInterceptor implements HandlerInterceptor {
/**
* 预处理,业务请求前调用
*
* @param request
* @param response
* @param handler
* @return
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 获取请求头
String token = request.getHeader("user_token");
log.info("获取 Token: {}", token);
log.info("获取请求路径:{}", request.getRequestURI());
// 令牌解析
Claims claims = JWTUtil.parseJWT(token);
if (null == claims) {
log.error("解析 JWT 令牌失败!");
response.setStatus(401);
return false;
}
log.info("解析 JWT 令牌成功!");
return true;
}
}
配置类 AppConfig
实现 WebMvcConfigurer,配置拦截器:
/**)。拦截器排除路径示例:
/**
* 拦截器排除路径
* */
@Configuration
public class AppConfig implements WebMvcConfigurer {
@Autowired
private LoginInterceptor loginInterceptor;
private final List<String> excludes = Arrays.asList(
"/**.html",
"/css/**",
"/js/**",
"/pic/**",
"/*.jpg",
"/*.png",
"/favicon.ico",
"/**/login",
"/register",
"/verification-code/send",
"/winning-records/show"
);
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/**")
.excludePathPatterns(excludes);
}
}
admin.html)通过 iframe 加载子页面,支持导航菜单切换。jumpList 控制跳转行为,admin 参数控制用户身份。前后端交互接口
/base-user/find-list GET(可选参数 identity 筛选身份){
"code": 200,
"data": [
{
"userId": 15,
"userName": "郭靖",
"identity": "NORMAL"
},
{
"userId": 14,
"userName": "王五",
"identity": "ADMIN"
}
],
"msg": ""
}
Controller 层接口设计
UserService 查询用户列表,转换为 UserBaseInfoResult 返回。Service 层接口设计
UserService 新增 findUserList 方法,支持按身份筛选(null 查询所有)。UserServiceImpl 实现逻辑:查询用户列表,转换为 UserDTO,包含用户 ID、姓名、邮箱、手机号、身份。Dao 层接口设计
UserMapper 新增 selectUserList 方法,支持按身份查询用户列表,按 ID 降序排序。人员列表页面前端实现
/base-user/find-list 接口,获取用户列表。
微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online