①实现基于session的登录流程:发送验证码、登录注册、校验登陆状态

在这里插入图片描述

个人简介:Java领域优质创作者;阿里云技术博主、星级博主、专家博主;正在Java学习的路上摸爬滚打,记录学习的过程~
个人主页:.29.的博客
学习社区:进去逛一逛~

在这里插入图片描述


实现基于session的登录流程:发送验证码、登录注册、校验登陆状态


🚀流程介绍

登录流程

  • 发送验证码

    • 用户输入手机号,点击发送按钮进行手机号提交,程序会校验手机号是否合法,不合法时要求用户重新输入手机号,合法则在后台生成对应的验证码并保存至session,之后通过短信方式将验证码发送给用户。

      • 什么是HttpSession?

      • HttpSession是Java Web中的一个接口,它提供了一种在服务器端存储和检索用户相关信息的机制。当用户第一次访问Web应用程序时,服务器会为该用户创建一个唯一的session ID,并将该ID存储在一个名为JSESSIONID的cookie中,然后将该ID与一个新的HttpSession对象相关联。在用户与Web应用程序交互期间,可以使用HttpSession对象来存储和检索与该用户相关的信息。当用户关闭浏览器或超过session超时时间时,session对象将被销毁。

        以下是获取和使用HttpSession对象的常用方法:

        1.获取HttpSession对象:

         HttpSession session = request.getSession();
        

        2.向session中存储数据:

         session.setAttribute("key", value);
        

        3.从session中获取数据:

         Object value = session.getAttribute("key");
        

        4.从session中删除数据:

         session.removeAttribute("key");
        

        5.使session失效:

         session.invalidate();
        

  • 注册、登录
    • 用户将手机号、验证码输入,后台从session中获取验证码与用户输入的验证码进行比对校验,如果不一致则无法通过校验,提示用户验证码错误,验证码一致则后台根据手机号查询用户,若用户不存在,则为用户创建账号信息并保存至数据库中,最后无论用户是否存在,都将用户的信息保存至session中,方便后续业务获取当前用户信息。

  • 校验登陆状态

    • 用户在客户端发起请求时,Cookie会携带用户的 JsessionId 后台,后台根据 JsessionId 从session中获取用户信息,如果没有用户信息就表示未登录,会对请求进行拦截,如果有用户信息,将其存入到本地线程 ThreadLocal 中并放行。

      • 为什么使用ThreadLocal:

        • 每个用户其实对应都是去找tomcat线程池中的一个线程来完成工作的, 使用完成后再进行回收,既然每个请求都是独立的,所以在每个用户去访问我们的工程时,我们可以使用threadlocal来做到线程隔离,每个线程操作自己的一份数据。
      • 什么是 JsessionId ?

        • JSessionId是Java Web应用程序中的一个会话标识符,用于跟踪用户与Web应用程序之间的会话。当用户第一次访问Web应用程序时,服务器会为该用户创建一个唯一的JSessionId,并将其存储在cookie中。在随后的请求中,浏览器会将该cookie发送回服务器,以便服务器可以识别用户并维护会话状态。

          在Java Web应用程序中,可以使用HttpSession对象来访问和管理会话状态。




🚀代码实现

业务逻辑实现


  • 统一返回类型 实体类

    • @Data
      @NoArgsConstructor
      @AllArgsConstructor
      public class Result {
          private Boolean success;
          private String errorMsg;
          private Object data;
          private Long total;
      
          public static Result ok(){
              return new Result(true, null, null, null);
          }
          public static Result ok(Object data){
              return new Result(true, null, data, null);
          }
          public static Result ok(List<?> data, Long total){
              return new Result(true, null, data, total);
          }
          public static Result fail(String errorMsg){
              return new Result(false, errorMsg, null, null);
          }
      }
      
      



  • 校验手机号、邮箱、验证码格式

    • public abstract class RegexPatterns {
          /**
           * 手机号正则
           */
          public static final String PHONE_REGEX = "^1([38][0-9]|4[579]|5[0-3,5-9]|6[6]|7[0135678]|9[89])\\d{8}$";
          /**
           * 邮箱正则
           */
          public static final String EMAIL_REGEX = "^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\\.[a-zA-Z0-9_-]+)+$";
          /**
           * 密码正则。4~32位的字母、数字、下划线
           */
          public static final String PASSWORD_REGEX = "^\\w{4,32}$";
          /**
           * 验证码正则, 6位数字或字母
           */
          public static final String VERIFY_CODE_REGEX = "^[a-zA-Z\\d]{6}$";
      
      }
      
      
    • public class RegexUtils {
          /**
           * 是否是无效手机格式
           * @param phone 要校验的手机号
           * @return true:符合,false:不符合
           */
          public static boolean isPhoneInvalid(String phone){
              return mismatch(phone, RegexPatterns.PHONE_REGEX);
          }
          /**
           * 是否是无效邮箱格式
           * @param email 要校验的邮箱
           * @return true:符合,false:不符合
           */
          public static boolean isEmailInvalid(String email){
              return mismatch(email, RegexPatterns.EMAIL_REGEX);
          }
      
          /**
           * 是否是无效验证码格式
           * @param code 要校验的验证码
           * @return true:符合,false:不符合
           */
          public static boolean isCodeInvalid(String code){
              return mismatch(code, RegexPatterns.VERIFY_CODE_REGEX);
          }
      
          // 校验是否不符合正则格式
          private static boolean mismatch(String str, String regex){
              if (StrUtil.isBlank(str)) { //Hutool工具:StrUtil
                  return true;
              }
              return !str.matches(regex);
          }
      }
      
      



  • 发送短信验证码 业务

    •     @Override
          public Result sendCode(String phone, HttpSession session) {
              //1. 手机号不合法?
              if(RegexUtils.isPhoneInvalid(phone)){
                  //2. 不合法,返回错误信息
                  return Result.fail("手机号格式错误!");
              }
      
              //3. 借助工具类,生成验证码(Hutool工具)
              String code = RandomUtil.randomNumbers(6);
      
              //4. 保存验证码至session域
              session.setAttribute("code",code);
      
              //5. 发送验证码
              log.debug("发送短信验证码成功,验证码: " + code);  //日志、方便控制台查看
              /*
                  调用验证码服务...(具体逻辑参照具体服务供应商的文档)
              */
      
              //6. 返回ok
              return Result.ok();
          }
      



  • 登录、注册 业务

    •     /**
           *   session实现登录功能
           * @param loginForm
           * @param session
           * @return
           */
          @Override
          public Result login(LoginFormDTO loginForm, HttpSession session) {
              //1. 校验手机号
              String phone = loginForm.getPhone();
              if(RegexUtils.isPhoneInvalid(phone)){
                  //2. 返回错误信息
                  return Result.fail("手机号格式错误");
              }
      
              //3. 校验验证码
              Object cacheCode = session.getAttribute("code");
              String code = loginForm.getCode();
              if(code == null || !code.toString().equals(cacheCode)){
                  //不一致,返回错误信息
                  return Result.fail("验证码错误");
              }
      
              // 一致,根据手机号获取用户
              User user = this.query().eq("phone", phone).one(); //(mybatisPlus提供的Service层方法)
      
              //5. 判断用户是否存在
              if(user == null){
                  //6. 不存在,创建新用户
                  user = new User();
                  user.setPhone(phone); //设置phone
                  user.setNickName(USER_NICK_NAME_PREFIX + RandomUtil.randomString(10)); //设置随机昵称
                  this.save(user); // 存入数据库(mybatisPlus提供的Service层方法)
              }
      
              //7. 用户存在,存入session域
              session.setAttribute("user", BeanUtil.copyProperties(user, UserDTO.class));
      
              //返回ok
              return Result.ok();
          }
      



  • 创建并设置ThreadLocal 自定义工具类

    • public class UserHolder {
          private static final ThreadLocal<UserDTO> tl = new ThreadLocal<>();
      
          public static void saveUser(UserDTO user){
              tl.set(user);
          }
      
          public static UserDTO getUser(){
              return tl.get();
          }
      
          public static void removeUser(){
              tl.remove();
          }
      }
      



  • 校验登陆状态 拦截器

    • /**
       * TODO 登录 拦截器
       * @author .29.
       * @create 2023-11-26 16:37
       */
      
      public class LoginInterceptor implements HandlerInterceptor {
          
          @Override
          public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
              // 1.获取session
              HttpSession session = request.getSession();
      
              //2. 获取用户
              Object user = session.getAttribute("user");
      
              //3. 验证用户是否存在
              if(user == null){
                  //4. 不存在,进行拦截,返回401状态码
                  response.setStatus(401);
                  return false;
              }
      
              //5. 存在,存入ThreadLocal(自定义工具类UserHolder,作用:创建并设置ThreadLocal)
              UserHolder.saveUser((UserDTO) user);
      
              //6. 放行
              return true;
          }
          
      }    
      
      • Spring Boot使用Spring MVC拦截器的步骤如下:

        • 1.创建一个拦截器类并实现HandlerInterceptor接口,该接口包含三个方法:preHandle、postHandle和afterCompletion。preHandle方法在请求处理之前调用,postHandle方法在请求处理之后调用,afterCompletion方法在视图渲染之后调用。

        • 2.在拦截器类上使用@Component@Configuration注解将其声明为Spring组件。

        • 3.创建一个配置类并实现WebMvcConfigurer接口,该接口包含一个addInterceptors方法,用于注册拦截器。

        • 4.在addInterceptors方法中使用addInterceptor方法注册拦截器,并使用addPathPatterns方法指定要拦截的请求路径。



  • 使拦截器生效 SpringMvc配置类

    • /**
       * TODO SpringMVC配置类,使拦截器生效
       * @author .29.
       * @create 2023-11-26 16:49
       */
      @Configuration
      public class MvcConfig implements WebMvcConfigurer {
      
          @Resource
          private StringRedisTemplate stringRedisTemplate;
      
          @Override
          public void addInterceptors(InterceptorRegistry registry) {
              //1. 添加登录拦截器、同时设置无需拦截的路径
              registry.addInterceptor(new LoginInterceptor()).excludePathPatterns(
                      "/shop/**",
                      "/voucher/**",
                      "/shop-type/**",
                      "/upload/**",
                      "/blog/hot",
                      "/user/code",
                      "/user/login"
              ).order(0); //order默认0,order值越大拦截器越后执行
          }
          
      }
      
      



🚀存在共享问题

每个tomcat中都有一份属于自己的session,假设用户第一次访问第一台tomcat,并且把自己的信息存放到第一台服务器的session中,但是第二次这个用户访问到了第二台tomcat,那么在第二台服务器上,肯定没有第一台服务器存放的session,所以此时 整个登录拦截功能就会出现问题,我们能如何解决这个问题呢?早期的方案是session拷贝,就是说虽然每个tomcat上都有不同的session,但是每当任意一台服务器的session修改时,都会同步给其他的Tomcat服务器的session,这样的话,就可以实现session的共享了

但是这种方案具有两个大问题

1、每台服务器中都有完整的一份session数据,服务器压力过大。

2、session拷贝数据时,可能会出现延迟。





在这里插入图片描述

  • 58
    点赞
  • 56
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 34
    评论
好的,下面是基于Session技术实现带有验证码功能的注册和登录的代码示例: 1. 注册页面 register.html ``` <!DOCTYPE html> <html> <head> <title>注册页面</title> </head> <body> <h1>注册页面</h1> <form action="register.php" method="post"> <label>用户名:</label> <input type="text" name="username" required><br><br> <label>密码:</label> <input type="password" name="password" required><br><br> <label>验证码:</label> <input type="text" name="captcha" required> <img src="captcha.php" alt="验证码"><br><br> <input type="submit" value="提交"> </form> </body> </html> ``` 2. 注册处理 register.php ``` <?php session_start(); if(isset($_POST['username']) && isset($_POST['password']) && isset($_POST['captcha'])){ // 获取用户输入的用户名、密码和验证码 $username = $_POST['username']; $password = $_POST['password']; $captcha = $_POST['captcha']; // 验证验证码是否正确 if(strtolower($captcha) != strtolower($_SESSION['captcha'])){ echo "<script>alert('验证码错误!');window.location.href='register.html';</script>"; exit; } // 连接数据库,验证用户名是否已经存在 $mysqli = new mysqli('localhost', 'root', 'password', 'test'); $sql = "SELECT * FROM users WHERE username='$username'"; $result = $mysqli->query($sql); if($result->num_rows > 0){ echo "<script>alert('用户名已经存在!');window.location.href='register.html';</script>"; exit; } // 将用户信息保存到数据库中 $sql = "INSERT INTO users (username, password) VALUES ('$username', '$password')"; $mysqli->query($sql); // 将用户信息存储到Session中 $_SESSION['username'] = $username; $_SESSION['password'] = $password; // 跳转到用户主页 header('Location: home.php'); }else{ echo "<script>alert('参数错误!');window.location.href='register.html';</script>"; exit; } ?> ``` 3. 登录页面 login.html ``` <!DOCTYPE html> <html> <head> <title>登录页面</title> </head> <body> <h1>登录页面</h1> <form action="login.php" method="post"> <label>用户名:</label> <input type="text" name="username" required><br><br> <label>密码:</label> <input type="password" name="password" required><br><br> <label>验证码:</label> <input type="text" name="captcha" required> <img src="captcha.php" alt="验证码"><br><br> <input type="submit" value="提交"> </form> </body> </html> ``` 4. 登录处理 login.php ``` <?php session_start(); if(isset($_POST['username']) && isset($_POST['password']) && isset($_POST['captcha'])){ // 获取用户输入的用户名、密码和验证码 $username = $_POST['username']; $password = $_POST['password']; $captcha = $_POST['captcha']; // 验证验证码是否正确 if(strtolower($captcha) != strtolower($_SESSION['captcha'])){ echo "<script>alert('验证码错误!');window.location.href='login.html';</script>"; exit; } // 连接数据库,验证用户名和密码是否正确 $mysqli = new mysqli('localhost', 'root', 'password', 'test'); $sql = "SELECT * FROM users WHERE username='$username' AND password='$password'"; $result = $mysqli->query($sql); if($result->num_rows > 0){ // 将用户信息存储到Session中 $_SESSION['username'] = $username; $_SESSION['password'] = $password; // 跳转到用户主页 header('Location: home.php'); }else{ echo "<script>alert('用户名或密码错误!');window.location.href='login.html';</script>"; exit; } }else{ echo "<script>alert('参数错误!');window.location.href='login.html';</script>"; exit; } ?> ``` 5. 生成验证码 captcha.php ``` <?php session_start(); // 生成验证码 $code = ''; for($i=0; $i<4; $i++){ $code .= chr(rand(97, 122)); } // 将验证码存储到Session中 $_SESSION['captcha'] = $code; // 创建画布 $width = 100; $height = 50; $image = imagecreatetruecolor($width, $height); // 设置背景色和前景色 $bgcolor = imagecolorallocate($image, 255, 255, 255); $fontcolor = imagecolorallocate($image, 0, 0, 0); // 填充背景色 imagefill($image, 0, 0, $bgcolor); // 输出验证码 imagestring($image, 5, 30, 20, $code, $fontcolor); // 输出图像 header('Content-type: image/png'); imagepng($image); // 销毁图像 imagedestroy($image); ?> ``` 6. 用户主页 home.php ``` <?php session_start(); if(isset($_SESSION['username']) && isset($_SESSION['password'])){ // 显示用户信息 echo "<h1>欢迎".$_SESSION['username']."登录!</h1>"; echo "<a href='logout.php'>退出登录</a>"; }else{ header('Location: login.html'); } ?> ``` 7. 退出登录 logout.php ``` <?php session_start(); // 销毁Session session_destroy(); // 跳转到登录页面 header('Location: login.html'); ?> ``` 综上所述,以上代码实现了基于Session技术实现带有验证码功能的注册和登录,可以有效防止机器人恶意注册和登录,提高系统的安全性。
评论 34
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

.29.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值