第27周JavaSpringboot电商进阶开发 1.企业级用户验证

课程笔记:注册邮箱验证

一、概述

从本小节开始,将学习如何进行注册邮箱验证。主要任务是给项目配置一个公共邮箱(可自己注册或由公司提供),用于向用户发送验证码,帮助用户完成注册流程。课程中以QQ邮箱为例,介绍在Spring Boot中发送邮件(包括正文、标题等)的方法。用户收到验证码后填写回来,与系统发送的进行比对,一致则验证成功,反之则失败。

二、注册邮箱验证的两种主流方式

  1. 验证码验证(国内常用,课程采用方式)

    • 流程:用户填写邮箱后点击发送验证码按钮,系统向该邮箱发送随机生成的验证码。用户收到后填写回来,系统进行比对校验。
    • 原理:验证码生成、校验及重复校验。若后续需进行短信验证,只需将发邮件改为发短信,其他校验原理一致。
  2. 唯一链接验证(国外常用)

    • 原理:给用户邮箱发送一个带有长链接的邮件,链接访问某接口并带参数(含个人信息)。每个邮箱的链接唯一,能访问该链接说明用户是邮箱主人。
    • 流程:用户点击唯一链接后,系统验证通过,确认是真实用户。

三、课程采用的验证码验证流程

  1. 用户在网页点击发送验证码按钮,系统检查该邮箱是否已注册。
    • 未注册:将用户信息放入缓存,向邮箱发送验证码。
    • 已注册:拒绝注册(不允许两个用户使用同一邮箱)。
  2. 用户在邮箱中收到验证码,复制到网站继续注册,提交验证码。
  3. 系统在缓存中校验验证码是否正确、是否过期。
    • 不正确或过期:拒绝注册。
    • 正确:用户注册成功。

课程笔记:邮件发送功能的开发与完善

一、邮箱配置(以 QQ 邮箱为例)

  1. 进入邮箱设置:点击邮箱界面的"设置"选项。
  2. 找到第三方服务:在设置页面的"常规"选项下,找到"第三方服务"相关设置。
  3. 开启服务并获取授权码
    • 如果服务未开启,点击"开启服务"。
    • 开启后,系统会生成一个授权码,该授权码专门用于程序验证,不同于普通登录密码。
    • 授权码相当于独立密码,用于提高账号安全性,需妥善保存,后续在程序配置中会用到。
  4. 其他邮箱配置类似:如 163 邮箱等,配置过程大致相同,找到类似"第三方服务"或"授权码"相关设置,进行相应配置。

二、给 User 类添加字段

  1. 进入 mybatis 配置类:找到对应的配置类,注释掉其他表,仅保留 User 表。
  2. 更新数据库表:在数据库中找到对应的 User 表,添加 email 字段,类型为 varchar(100),注释为"邮箱地址"。
  3. 重新生成旧类:运行 mybatis generator,生成新的 User 类。
  4. 备份自定义代码:提前复制保存 mapper 中自动生成代码后添加的自定义方法和 SQL 语句,避免重新生成后被覆盖。
  5. 恢复自定义代码:生成新类后,将备份的代码粘贴回来,并引入相关包。

三、引入邮件发送依赖

在项目中添加 spring-boot-starter-mail 依赖,指定版本为 2.3.4.RELEASE。

四、开发发送邮件接口

  1. 在 UserController 中添加接口

    • 方法头:发送邮件,路径为 sendEmail,参数为 emailaddress(邮件地址)。
    • 返回格式匹配。
  2. 实现方法

    • 校验邮件地址是否有效
      • 使用 utu 包中的 InternetAddress 类的 validate 方法。
      • 创建 Email 工具类,新增 isValidEmailAddress 方法,返回 boolean 值。
      • 在该方法中,使用 try-catch 包裹 InternetAddress(email).validate(),若无异常返回 true,否则返回 false。
      • 使用该方法检查传入的 emailaddress,若无效,返回错误响应(枚举值为 INVALID_EMAIL,中文为"非法的邮件地址")。
    • 检查是否已注册:若邮件地址有效,进一步检查是否已注册。
    • 发送邮件:若以上校验均通过,执行邮件发送逻辑。

五、检查邮件地址是否已注册

  1. 在 User Service 中新增方法

    • 方法名:public boolean checkEmailRegistered(String emailaddress)
    • 使用 UserMapper 的 selectOneByEmailaddress 方法,根据传入的邮件地址查询用户。
    • 若返回的 User 对象不为空,说明邮件地址已被注册,返回 false;否则返回 true。
  2. 在 UserMapper 中补充 SQL 语句

    • 方法名:selectOneByEmailaddress(String emailaddress)
    • SQL 语句:SELECT * FROM imcomo_user WHERE emailaddress = #{emailaddress} LIMIT 1

六、邮件发送逻辑实现

  1. 在 UserController 中调用检查方法

    • 在通过邮件地址合法性验证后,调用 userService.checkEmailRegistered(emailaddress)
    • 若返回值为 false(邮件地址已被注册),返回错误响应(枚举值为 EMAIL_ALREADY_BEEN_REGISTERED,中文为"email 地址已被注册")。
  2. 创建 EmailService 接口及实现类

    • 接口方法:void sendSimpleMessage(String to, String subject, String text)
    • 实现类:EmailServiceImpl,使用 JavaMailSender 发送邮件。
  3. 配置邮件相关属性

    • 在配置文件中设置邮件主机、端口号、用户名、授权码、编码及验证相关属性。
  4. 完善邮件发送方法

    • sendSimpleMessage 方法中,创建 SimpleMailMessage 对象,设置发件人(从常量中获取)、收件人、主题和正文。
    • 使用 JavaMailSender 的 send 方法发送邮件。
  5. 在 UserController 中调用邮件发送服务

    • 引入 EmailService。
    • 调用 emailService.sendSimpleMessage,传入邮件地址、主题(从常量中获取)和验证码相关正文。

七、生成随机验证码

  1. 在 Email 工具类中新增方法

    • 方法名:public static String generateVerificationCode()
    • 创建包含数字、大写字母、小写字母的字符列表。
    • 使用 Collections.shuffle 打乱列表顺序。
    • 取列表前六位字符作为验证码。
    • 返回生成的验证码字符串。
  2. 测试验证码生成方法:编写测试方法,打印生成的验证码,验证其随机性和正确性。

八、限制重复发送邮件

  1. 引入 Redis 依赖 :添加 redisson 依赖,用于操作 Redis。

  2. 在 EmailService 中新增方法

    • 方法名:public boolean saveEmailToRedis(String emailaddress, String verificationCode)
    • 获取 Redis 客户端,连接到本地 Redis。
    • 使用 getBucket 方法传入邮箱地址作为 key,获取对应的 bucket。
    • 检查 bucket 中是否存在值:
      • 不存在:使用 set 方法存入验证码,设置过期时间为 60 秒,单位为秒,返回 true。
      • 存在:返回 false,表示 60 秒内已发送过邮件。
  3. 在 UserController 中调用并处理

    • 调用 emailService.saveEmailToRedis,传入邮箱地址和验证码。
    • 根据返回值判断:
      • 返回 true:发送邮件,返回成功响应。
      • 返回 false:返回错误响应,提示邮件已发送,请稍后再试。

九、完善邮件发送内容

将生成的验证码添加到邮件正文内容中。

十、测试验证

  1. 测试非法邮件地址:发送格式错误的邮件地址,验证是否被拦截。
  2. 测试已注册邮箱:将邮箱地址设置为已注册的用户,验证是否被拦截。
  3. 测试重复发送:短时间内反复发送邮件,验证是否被拦截。

十一、总结

通过校验邮件地址合法性、检查是否已注册、限制重复发送以及使用 Redis 缓存验证码,完善了邮件发送接口的功能,提高了系统的安全性和稳定性。

课程笔记:注册接口升级与邮箱验证总结

一、注册接口升级

  1. 调整入参

    • 增加 emailaddress(邮箱地址)和 verificationcode(验证码)两个参数。
  2. 增加校验

    • 非空校验 :对邮箱和验证码进行非空校验,新建异常类 26email不能为空27验证码不能为空
    • 邮箱是否已注册校验 :调用 checkEmailRegister 方法,判断邮箱是否已被注册。
    • 邮箱和验证码匹配校验 :在 emailService 中新增 checkEmailAndCode 方法,通过 Redis 获取存储的验证码并与用户传入的验证码进行比对。
  3. 数据存储

    • 注册成功时,将邮箱地址存入数据库用户表中。

二、校验流程

  1. 非空校验:确保用户名、密码、邮箱和验证码均不为空。
  2. 邮箱注册状态校验:防止重复注册。
  3. 验证码匹配校验:确保用户提供的验证码与 Redis 中存储的验证码一致。

三、验证码存储选择

  1. 为什么选择 Redis 而不是数据库
    • 临时性:验证码仅一次有效,注册成功后即无作用,无需长期存储。
    • 过期机制:Redis 提供方便的过期时间设置,自动处理验证码过期,而数据库难以实现自动过期。

四、总结

通过升级注册接口,增加邮箱和验证码参数及相关校验,确保了用户注册流程的安全性和完整性。使用 Redis 存储验证码,利用其过期机制,有效防止了恶意重复注册和验证码滥用。整个流程包括发送验证码、校验邮箱是否已注册、验证码匹配校验以及最终的用户注册,各步骤紧密衔接,确保了用户注册信息的准确性和系统安全性。

课程笔记:登录状态的保存和验证

一、学习背景

在企业级权限认证中,登录是第一步,不仅需要验证用户身份,还要保存登录状态,以便用户后续操作时系统能够识别其身份。

二、HTTP 无状态特性

HTTP 协议是无状态的,意味着每个请求都是独立的,请求之间不携带状态信息。即使前一个请求通过验证,下一个请求仍需重新验证。这要求我们采取措施保存凭证,以解决无状态带来的问题。

三、凭证保存机制

1. 第一次登录验证

用户第一次登录时,需要提供用户名和密码等信息进行严格的身份验证。

2. Session 的创建

验证通过后,服务器为用户创建一个 Session,Session 是保存在服务器端的数据结构,用于跟踪用户状态。

服务器返回给客户端一个 Cookie,其中包含 Session ID。客户端(如浏览器)在后续请求中携带此 Cookie,服务器通过 Session ID 找到对应的 Session,从而识别用户身份。

  1. 用户发送登录请求:包含用户名和密码。
  2. 服务器验证并创建 Session:验证通过后,生成 Session 并返回包含 Session ID 的 Cookie。
  3. 客户端保存 Cookie:浏览器保存 Cookie,在后续请求中自动携带。
  4. 服务器识别用户:通过 Cookie 中的 Session ID 找到对应的 Session,获取用户状态和数据。

五、特殊情况处理

如果客户端禁用 Cookie,可以采用 URL 重写的方式,在每个请求的 URL 后附加必要的身份参数,以便服务器进行校验。

六、总结

本小节介绍了登录状态保存和验证的基本原理和流程,重点讲解了 HTTP 无状态特性下如何通过 Session 和 Cookie 解决用户身份识别问题。下一个小节将深入探讨 Session 的细节和应用。

课程笔记:深入理解Session

一、Session 的安全性

  1. 用户空间的独立性

    • 每个用户的Session空间是独立的,即使使用相同的key存储数据,也不会相互影响。
    • Tomcat会为每个用户分配独立的Session ID,每个Session ID对应自己的空间。
  2. Session ID 的生成规则

    • 目标:保证唯一性,防止重复。
    • 方法:通常结合随机数、当前时间(过滤大部分同时触发的情况)和JVM的ID值(区分不同服务器)。

二、Session 劫持与防护

  1. Session 劫持

    • 概念:攻击者窃取用户的Session ID,冒充用户进行操作。
    • 风险:可能导致用户数据泄露、被恶意操作等。
  2. 防护措施

    • HttpOnly 标记:服务器告知客户端,Session Cookie不允许通过前端代码读取,仅允许浏览器读取。
    • Secure 标记:如果项目支持HTTPS,标记Session Cookie仅在HTTPS协议下传输。

三、Session 的缺点

  1. 扩展性差

    • 分布式环境下,多台机器需要同步和复制Session,处理复杂。
  2. 服务端存储压力

    • 用户量大时,存储大量Session数据(无论在内存、Redis还是数据库中)都会带来挑战。

四、实际操作演示

  1. Postman 测试流程

    • 默认Cookie中包含Session ID,每个用户的Session ID唯一。
    • 删除Cookie后请求,服务端会创建新的Session并要求设置Cookie。
    • 设置Cookie后,后续请求携带该Session ID,服务端据此识别用户。
  2. 浏览器中的Session Cookie

    • 查看请求中的Cookie,包含JSession Cookie,代表用户唯一标识。
    • 注意保护Session ID,防止泄露。

五、总结

Session用于保存用户状态,其安全性至关重要。通过理解Session的工作原理、Session ID的生成规则以及劫持与防护措施,可以更好地保障用户数据安全。Session存在扩展性和存储压力的缺点,后续将学习JWT来克服这些问题。

课程笔记:JWT 介绍与原理

一、JWT 的重要性

  1. 独特优势:相比 Session 和 Cookie,JWT 有自身的特点和优势。
  2. 项目应用:后续小节将把项目中原有的 Session 验证方式升级为 JWT。

二、JWT 的基本概念

  1. 定义:JWT(JSON Web Token)是一种流行的用于网站身份验证的认证方案。
  2. 官网jwt.io ,提供调试工具用于编码和解码。

三、JWT 的组成

JWT 由三部分组成,每部分之间用英文半角句号(.)分隔:

  1. Header(头部)

    • 包含两个功能:签名算法和令牌类型。
    • 示例:{"alg": "HS256", "typ": "JWT"},其中 alg 是签名算法,typ 是令牌类型。
  2. Payload(消息体)

    • 是 JWT 中最重要的部分,用于放置业务相关数据。
    • 示例:{"sub": "1234567890", "name": "John Doe", "iat": 1516239022},其中 name 可改为用户名、身份、用户 ID 等。
  3. Signature(签名)

    • 用于验证消息是否被更改过,保证数据安全。
    • 生成方式:将 Header 和 Payload 编码后,用 Secret 进行签名。

四、JWT 的生成与验证

  1. 生成

    • Header 和 Payload 分别经过 Base64 URL 编码。
    • 使用 Secret 对编码后的 Header 和 Payload 进行签名,生成 Signature。
    • 三部分组合成完整的 JWT。
  2. 验证

    • 服务端解码 JWT,验证 Signature 的有效性。
    • 如果 Signature 无效,说明 JWT 被篡改。

五、Session 与 JWT 的对比

流程对比

  1. Session 流程

    • 客户端发起 HTTP 请求,服务端创建 Session,生成唯一的 Session ID。
    • 服务端要求客户端保存 Session ID 到 Cookie。
    • 后续请求客户端携带 Cookie,服务端通过 Session ID 识别用户。
  2. JWT 流程

    • 客户端发起 HTTP 请求,服务端校验用户名和密码。
    • 校验通过后,服务端将用户信息转换为 JWT 并发送给客户端。
    • 后续请求客户端携带 JWT,服务端解码 JWT 获取用户信息。

优缺点对比

  1. Session

    • 优点:简单方便,适合小规模网站。
    • 缺点
      • 扩展性差:用户量大时,分布式架构下存储和同步 Session 成本高。
      • 需要存储数据:服务端需为每个用户开辟空间存储 Session。
  2. JWT

    • 优点
      • 减少存储开销:服务端无需存储用户信息,直接将信息编码到 JWT 中。
      • 可扩展性强:分布式架构下,各服务器可独立校验 JWT。
      • 可用于交换信息:直接携带用户相关业务数据。
      • 防止篡改:签名机制保证 JWT 的完整性。
    • 缺点
      • 默认不加密:不适合保存敏感信息,如密码。
      • 无法临时废止:一旦发出,无法主动使其失效,需设置合理过期时间。
      • 有效期评估难:过长或过短的过期时间都会带来问题。
      • 网络开销高:相比 Session ID,JWT 字符串较长。

六、总结

JWT 凭借其减少存储开销和良好的扩展性,在互联网公司中应用越来越广泛。尽管存在一些缺点,但通过合理设置和使用,可以充分发挥其优势。后续小节将进入 JWT 的实际开发应用。

课程笔记:项目实战 - 用户校验从Session升级为JWT

一、项目实战目标

将用户校验从传统的Session Cookie升级为JWT。

二、主要修改内容

  1. 登录接口升级:不再保存Session,改为在登录时生成JWT Token并返回给用户。
  2. 过滤器修改:包括用户过滤器和管理员过滤器,以适应JWT校验。
  3. 用户获取方式升级:调整从请求中获取用户信息的方式。

三、实战步骤

1. 引入JWT依赖

pom.xml中添加JWT依赖:

xml 复制代码
<dependency>
    <groupId>com.off0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>3.14.0</version>
</dependency>

手动刷新项目,确保依赖正确下载。

2. 修改登录接口

(1) 引入必要的包和工具

确保项目中已引入JWT相关的包和工具。

(2) 来到UserController

找到login接口,准备进行改造。

(3) 保留原有登录逻辑

保留用户名和密码的判空逻辑,以及通过用户名和密码获取用户信息的逻辑。

(4) 生成JWT Token

在原有登录逻辑的基础上,添加JWT Token的生成代码:

java 复制代码
// 定义算法
Algorithm algorithm = Algorithm.HMAC256(Constant.JWT_KEY);

// 创建JWT
String token = JWT.create()
    .withClaim(Constant.USERNAME, user.getUsername())
    .withClaim(Constant.USER_ID, user.getId())
    .withClaim(Constant.USER_ROW, user.getRow())
    .withExpiresAt(new Date(System.currentTimeMillis() + Constant.JWT_EXPIRE_TIME))
    .sign(algorithm);

// 返回token
return APIResponse.success(token);

3. 定义常量

Constant类中添加以下常量:

java 复制代码
public static final String JWT_KEY = "your_secret_key"; // JWT密钥
public static final String USERNAME = "username"; // 用户名常量
public static final String USER_ID = "user_id"; // 用户ID常量
public static final String USER_ROW = "user_row"; // 用户行常量
public static final long JWT_EXPIRE_TIME = 1000L * 60 * 60 * 24 * 1000; // JWT过期时间(1000天)

四、总结

通过上述步骤,完成了将用户校验从Session升级为JWT的主要工作。登录接口已改造为生成并返回JWT Token,后续请求中客户端需将Token放在请求头中,由服务器进行校验。接下来,还需对过滤器和用户获取方式进行相应升级,以全面支持JWT校验。

课程笔记:项目实战 - JWT 校验与过滤器升级

一、项目重启与环境准备

  1. 重启项目:清理缓存、删除target目录,重新生成项目文件。
  2. 解决包不存在问题
    • 刷新Maven依赖,手动触发下载。
    • 调整IDEA设置(打开override、importing选项,检测JDK版本,配置process resources)。
    • 清空IDEA缓存(File -> Invalidate Caches)。

二、获取JWT Token

  1. 测试新接口:使用用户名和密码调用login for jwt接口。
  2. 验证Token:将返回的Token复制到jwt.io网站,解析查看内容是否包含用户名、用户ID、用户角色和过期时间等信息。

三、过滤器升级

  1. 修改用户过滤器

    • 删除原从Session获取用户信息的代码。
    • 从请求头获取JWT Token(约定Header的Key为jwt token)。
    • 校验Token:
      • 使用Algorithm.HMAC256(Constant.JWT_KEY)生成Algorithm对象。
      • 通过JWT.require(algorithm).build()生成Verifier。
      • 使用Verifier.verify(token)解码Token,获取用户信息并设置到CurrentUser对象中。
    • 处理解码异常:
      • Token过期异常(TokenExpiredException)。
      • Token解码失败异常(JWTDecodeException)。
  2. 完善用户过滤器配置

    • 修改方法名为user filter config。
    • 增加对更新用户信息接口的拦截。

四、总结

完成了JWT Token的生成与校验,以及过滤器的相应升级。用户登录后,通过在请求头中携带JWT Token进行身份校验,取代了原有的Session方式。在实际操作中,要注意处理Maven依赖和IDEA缓存等问题,确保项目顺利运行。

相关推荐
yinuo9 小时前
前端跨页面通讯终极指南⑥:SharedWorker 用法全解析
前端
CoderYanger13 小时前
C.滑动窗口-求子数组个数-越长越合法——2799. 统计完全子数组的数目
java·c语言·开发语言·数据结构·算法·leetcode·职场和发展
廋到被风吹走13 小时前
【数据库】【MySQL】InnoDB外键解析:约束机制、性能影响与最佳实践
android·数据库·mysql
C++业余爱好者13 小时前
Java 提供了8种基本数据类型及封装类型介绍
java·开发语言·python
想用offer打牌13 小时前
RocketMQ如何防止消息丢失?
java·后端·架构·开源·rocketmq
皮卡龙13 小时前
Java常用的JSON
java·开发语言·spring boot·json
掘根14 小时前
【消息队列】交换机数据管理实现
网络·数据库
Logic10114 小时前
《Mysql数据库应用》 第2版 郭文明 实验6 数据库系统维护核心操作与思路解析
数据库·sql·mysql·学习笔记·计算机网络技术·形考作业·国家开放大学
PineappleCoder14 小时前
还在重复下载资源?HTTP 缓存让二次访问 “零请求”,用户体验翻倍
前端·性能优化
拉不动的猪14 小时前
webpack编译中为什么不建议load替换ast中节点删除consolg.log
前端·javascript·webpack