跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
Javajava

Spring Boot 用户模块实战:注册登录、JWT 认证与敏感数据加密

围绕 Spring Boot 的用户模块,从敏感字段加密开始,用 SHA-256 处理密码、AES 加密手机号,并通过 MyBatis TypeHandler 实现自动加解密。注册接口对邮箱、手机号、密码做完整校验,Service 层负责业务逻辑,Controller 统一异常处理。登录支持密码和短信验证码两种方式,结合 Redis 存储验证码,使用 JWT 签发令牌。前端请求头携带 token,后端拦截器校验,未登录返回 401。最后给出后台用户列表的查询实现,形成一套完整的用户认证与管理方案。

萤火微光发布于 2026/6/300 浏览
Spring Boot 用户模块实战:注册登录、JWT 认证与敏感数据加密

构建一个用户模块,少不了注册、登录、权限拦截这些常规功能。下面从头梳理一下 Spring Boot 项目里的实现思路,涵盖敏感字段加密、JWT 令牌、短信验证码、强制登录拦截,以及后台管理的用户列表。

敏感字段加密

用户注册时,密码和手机号都不能明文存库。

  • 密码:用加盐 SHA-256。注册时给每个用户生成随机盐值,把密码和盐拼起来哈希,只存结果,不可逆。
  • 手机号:业务上偶尔需要明文(比如发短信),所以用 AES 对称加密。存的时候加密,用的时候解密。

家国工具类我习惯用 Hutool,加解密、验证码生成这些都能搞定。

<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.8.25</version>
</dependency>

Hutool 官方文档:https://hutool.cn/
Maven 仓库:https://mvnrepository.com/artifact/cn.hutool

注册接口

注册参数包含姓名、邮箱、手机号、密码、身份(普通用户或管理员)。前端表单校验后发起 POST,后端用 @Validated 做基本校验。

请求体:

{
  "name": "张三",
  "mail": "[email protected]",
  "phoneNumber": "13188888888",
  "password": "123456789",
  "identity": "ADMIN"
}

返回用户 ID:

{
  "code": 200,
  "data": { "userId": 22 },
  "msg": ""
}

Controller 层很简单,接收 UserRegisterParam,调 Service 拿到 UserRegisterDTO 再转成返回对象:

@RestController
public class UserController {
    private static final Logger logger = LoggerFactory.getLogger(UserController.class);

    @Autowired
    private UserService userService;

    @Autowired
    private VerificationCodeService verificationCodeService;

    @PostMapping("/register")
    public CommonResult<UserRegisterResult> userRegister(@Validated @RequestBody UserRegisterParam param) {
        logger.info("userRegister UserRegisterParam 用户注册:{}", JacksonUtil.writeValueAsString(param));
        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;
    }
}

如果用的是 Spring Boot 2.3 以上,要额外引入 validation 依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

Service 层里做实质性校验:邮箱格式、手机号格式、身份合法、密码长度至少6位、邮箱和手机号唯一性。然后加密手机号、对密码做 SHA-256,再入库。

@Slf4j
@Service
public class UserServiceImpl implements UserService {
    @Autowired
    private UserMapper userMapper;

    @Autowired
    private VerificationCodeService verificationCodeService;

    @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 dto = new UserRegisterDTO();
        dto.setUserId(userDO.getId());
        return dto;
    }

    private void checkRegisterInfo(UserRegisterParam param) {
        if (null == param) throw new ServiceException(ServiceErrorCodeConstants.REGISTER_INFO_IS_EMPTY);
        if (!RegexUtil.checkMail(param.getMail())) throw new ServiceException(ServiceErrorCodeConstants.MAIL_ERROR);
        if (!RegexUtil.checkMobile(param.getPhoneNumber())) throw new ServiceException(ServiceErrorCodeConstants.PHONE_NUMBER_ERROR);
        if (null == UserIdentityEnum.forName(param.getIdentity())) throw new ServiceException(ServiceErrorCodeConstants.IDENTITY_ERROR);
        if (param.getIdentity().equalsIgnoreCase(UserIdentityEnum.ADMIN.name()) && !StringUtils.hasLength(param.getPassword()))
            throw new ServiceException(ServiceErrorCodeConstants.PASSWORD_IS_EMPTY);
        if (StringUtils.hasLength(param.getPassword()) && !RegexUtil.checkPassword(param.getPassword()))
            throw new ServiceException(ServiceErrorCodeConstants.PASSWORD_ERROR);
        if (checkMailUsed(param.getMail())) throw new ServiceException(ServiceErrorCodeConstants.MAIL_USED);
        if (checkPhoneNumberUsed(param.getPhoneNumber())) throw new ServiceException(ServiceErrorCodeConstants.PHONE_NUMBER_USED);
    }

    private boolean checkPhoneNumberUsed(String phoneNumber) {
        return userMapper.countByPhone(newEncrypt(phoneNumber)) > 0;
    }

    private boolean checkMailUsed(String mail) {
        return userMapper.countByMail(mail) > 0;
    }
}

Dao 层用 MyBatis,UserMapper 负责插入和唯一性查询。UserDO 映射用户表,继承 BaseDO 包含主键、创建时间、更新时间。

手机号自动加解密:TypeHandler

因为手机号存库前要加密、读出来要解密,每个地方手动处理很烦。MyBatis 的 TypeHandler 可以让我们只在字段上标记一下,框架自动处理。

  • 定义一个 Encrypt 类型包装要加密的字符串。
  • 写一个 EncryptTypeHandler 实现 BaseTypeHandler<Encrypt>,重写 setNonNullParameter(加密)和 getNullableResult(解密)。
  • 在 application.properties 里配好 TypeHandler 的包路径。

这样 Service 层就能忽略加解密细节了。

控制层通用异常处理

用 @RestControllerAdvice + @ExceptionHandler 统一处理异常,避免每个方法都 try-catch。这里分别捕获 Exception、ServiceException、ControllerException,记录日志后返回统一的 CommonResult 错误格式。

短信验证码登录

因为没有企业认证,用的是阿里云短信服务的测试模板。配置里填好 AccessKeyId、AccessKeySecret 和签名。

CaptchaUtil 用 Hutool 生成随机数字验证码:

public class CaptchaUtil {
    public static String getCaptcha(int length) {
        RandomGenerator randomGenerator = new RandomGenerator("0123456789", length);
        LineCaptcha lineCaptcha = cn.hutool.captcha.CaptchaUtil.createLineCaptcha(200, 100);
        lineCaptcha.setGenerator(randomGenerator);
        lineCaptcha.createCode();
        return lineCaptcha.getCode();
    }
}

发送短信的接口:GET /verification-code/send?phoneNumber=13199999999,返回 true/false。

Service 层实现:

  1. 校验手机号格式。
  2. 生成验证码。
  3. 调用 SMSUtil 发短信。
  4. 把验证码存进 Redis,设置 60 秒过期。
@Service
public class VerificationCodeServiceImpl implements VerificationCodeService {
    @Autowired
    private SMSUtil smsUtil;
    @Autowired
    private RedisUtil redisUtil;

    @Override
    public void sendVerificationCode(String phoneNumber) {
        if (!RegexUtil.checkMobile(phoneNumber)) {
            throw new ServiceException(ServiceErrorCodeConstants.PHONE_NUMBER_ERROR);
        }
        String code = CaptchaUtil.getCaptcha(Constants.CODE_LENGTH);
        smsUtil.sendVerifyCode(phoneNumber, code);
        redisUtil.set(Constants.VERIFICATION_CODE_PREFIX + phoneNumber, code, Constants.VERIFICATION_CODE_TIMEOUT);
    }
}

Redis 工具类

为避免序列化乱码,基于 StringRedisTemplate 封装了 RedisUtil,提供常见的 get、set(支持过期时间)、hasKey、del 等方法。

@Configuration
public class RedisUtil {
    private static final Logger logger = LoggerFactory.getLogger(RedisUtil.class);

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    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;
        }
    }

    public String get(String key) {
        try {
            return StringUtils.hasText(key) ? stringRedisTemplate.opsForValue().get(key) : null;
        } catch (Exception e) {
            logger.error("RedisUtil error,get({})", key, e);
            return null;
        }
    }
}

JWT 令牌

用 JWT 实现无状态登录,集群环境下不需要管 Session 共享。JWT 由 Header、Payload、Signature 三部分组成,签名能防止篡改,但内容本身是 Base64 编码的,不要往里面放敏感信息。

JWTUtil 封装了生成和解析方法。密钥从 Base64 字符串生成 HMAC SHA 密钥,过期时间设为 24 小时。

public class JWTUtil {
    private static final Logger logger = LoggerFactory.getLogger(JWTUtil.class);
    private static final String SECRET = "********";
    private static final SecretKey SECRET_KEY = Keys.hmacShaKeyFor(Decoders.BASE64.decode(SECRET));
    private static final long EXPIRATION = 24 * 60 * 60 * 1000;

    public static String genJwt(Map<String, Object> claim) {
        return Jwts.builder()
                .setClaims(claim)
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION))
                .signWith(SECRET_KEY)
                .compact();
    }

    public static Claims parseJWT(String jwt) {
        if (!StringUtils.hasLength(jwt)) return null;
        try {
            return Jwts.parserBuilder().setSigningKey(SECRET_KEY).build().parseClaimsJws(jwt).getBody();
        } catch (Exception e) {
            logger.error("解析令牌错误,jwt:{}", jwt, e);
            return null;
        }
    }
}

管理员登录

支持两种方式:电话+密码,或者电话+验证码。登录成功返回 JWT 和用户身份。

密码登录接口:POST /password/login

{
  "loginName": "13199999999",
  "password": "123456",
  "mandatoryIdentity": "ADMIN"
}

验证码登录接口:POST /message/login

{
  "loginMobile": "13199999999",
  "verificationCode": "0475",
  "mandatoryIdentity": "ADMIN"
}

响应体:

{
  "code": 200,
  "data": { "token": "eyJhbGci...", "identity": "ADMIN" },
  "msg": ""
}

Service 层要区分登录类型:

  • 密码登录:校验登录名(邮箱或手机号),查出用户,验证身份和密码,然后签发令牌。
  • 验证码登录:校验手机号,查出用户,校验身份,从 Redis 取出验证码对比,通过后签发令牌。

Dao 层提供 selectByEmail 和 selectByPhoneNumber 两个查询方法。

前端登录页用 tab 切换两种方式,表单校验、倒计时重发验证码这些都在前端处理。登录成功把 token 和身份存 localStorage,然后跳转后台首页。

强制登录拦截

未登录的用户访问受保护页面时,自动拦住跳登录页。

前端在所有 AJAX 请求头里带上 user_token(从 localStorage 取),后端解析令牌,无效就返回 401。前端 ajaxSend 里统一处理 401,把页面重定向到登录页(包括 iframe 嵌套的情况)。

后端拦截器 LoginInterceptor 实现 HandlerInterceptor:

@Slf4j
@Component
public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String token = request.getHeader("user_token");
        log.info("获取 Token: {}", token);
        Claims claims = JWTUtil.parseJWT(token);
        if (null == claims) {
            log.error("解析 JWT 令牌失败!");
            response.setStatus(401);
            return false;
        }
        return true;
    }
}

AppConfig 配置拦截器,放行登录、注册、验证码发送等接口,以及静态资源:

@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 加载子页,侧边栏切换功能。管理员新增用户和前端注册流程差不多,只是新增完直接跳转到用户列表(通过 URL 参数控制)。

用户列表接口:GET /base-user/find-list,可选 identity 参数筛选身份。返回数组里包含 userId、userName、identity。

Service 层 findUserList 方法根据身份可选查询,按 ID 降序;Dao 层对应 selectUserList。

前端拿到数据后渲染表格,未登录时同样会触发拦截跳转。

就这样,一个基础的用户模块就搭起来了。从加密、注册、登录到 JWT 和拦截器,各层代码都比较清晰。实际项目中还可以加上角色权限细分、Token 刷新等,但核心逻辑大差不差。

目录

  1. 敏感字段加密
  2. 注册接口
  3. 手机号自动加解密:TypeHandler
  4. 控制层通用异常处理
  5. 短信验证码登录
  6. Redis 工具类
  7. JWT 令牌
  8. 管理员登录
  9. 强制登录拦截
  10. 用户管理后台
  • 免费图片AI生成工具免费生成了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 免费图片视频在线生成30秒,将你的创意变成现实开始设计
  • X/Twitter免费视频下载器免登陆无限额度免费视频解析下载了解详情
  • 100+免费在线小游戏爽一把
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • 华为交换机初始配置实录:Console 登录、管理 IP 与 Web 网管开通
  • 从后端到前端:AI Agent 跨语言全栈项目实战记录(Java + Python + Vue3)
  • 在银河麒麟 V10 上使用 Docker 和 Compose 部署 .NET 8 WebAPI
  • 汉诺塔问题的递归与非递归 C++ 解法
  • 7款国内AI助手横评:豆包、元宝、千问、Kimi、DeepSeek、MiniMax、GLM
  • 2026 算法求职:为什么我劝你深耕多模态大模型
  • 2023年网络安全趋势观察:十个绕不开的方向
  • 用 MGeo 和 Neo4j 搭建中文地址语义知识图谱
  • 网络安全基础知识详解:概念、威胁与防护策略
  • 位运算实战:判断字符唯一、丢失的数字、两整数之和等
  • AR眼镜光学镜头设计实战:从规格到量产的决策记录
  • 一个高效的 Dinic 最大流实现(附带最小割计算)
  • 在飞书里用 Openclaw 运行多个机器人:配置与分工实践
  • 从零构建国产电视剧评分数据集:一个爬虫实战记录
  • 金仓 KES 迁移适配实测:从 Oracle 到 SQL Server 的兼容性验证
  • 用LangChain手搓一个数学Agent:工具、执行器与边界控制
  • 代码中介翻译:从语言模型蒸馏视觉推理数据的探索
  • C++ 字符串操作实战:std::string 的常用接口、迭代器与内存管理
  • OpenClaw 对接飞书:让聊天框变成电脑遥控器
  • Python接口自动化测试:从零封装到可维护框架

相关免费在线工具

  • Keycode 信息

    查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online

  • Escape 与 Native 编解码

    JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online

  • JavaScript / HTML 格式化

    使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online

  • JavaScript 压缩与混淆

    Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online