Redis的共享session应用实现短信登录

目录
  • 1. 基于 session 实现短信登录
    • 1.1 短信登录流程图
    • 1.2 实现发送短信验证码
    • 1.3 实现短信验证码登录、注册
    • 1.4 实现登录校验拦截器
  • 2. 集群的 session 共享问题
  • 3. 基于 Redis 实现共享 session 登录
    • 3.1 Redis 实现共享 session 登录流程图
    • 3.2 实现发送短信验证码
    • 3.3 实现短信验证码登录、注册
    • 3.4 实现登录校验拦截器

1. 基于 session 实现短信登录

1.1 短信登录流程图

1.2 实现发送短信验证码

前端请求说明:

  说明
请求方式 POST
请求路径 /user/code
请求参数 phone(电话号码)
返回值

后端接口实现:

@Slf4j
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {

    @Override
    public Result sendCode(String phone, HttpSession session) {
        // 1. 校验手机号
        if(RegexUtils.isPhoneInvalid(phone)){
            // 2. 如果不符合,返回错误信息
            return Result.fail("手机号格式错误!");
        }
        // 3. 符合,生成验证码(设置生成6位)
        String code = RandomUtil.randomNumbers(6);
        // 4. 保存验证码到 session
        session.setAttribute("code", code);
        // 5. 发送验证码(这里并未实现,通过日志记录)
        log.debug("发送短信验证码成功,验证码:{}", code);
        // 返回 ok
        return Result.ok();
    }
}

1.3 实现短信验证码登录、注册

前端请求说明

  说明
请求方式 POST
请求路径 /user/login
请求参数 phone(电话号码);code(验证码)
返回值

后端接口实现:

@Slf4j
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {

    @Override
    public Result login(LoginFormDTO loginForm, HttpSession session) {
        // 1. 校验手机号
        String phone = loginForm.getPhone();
        if(RegexUtils.isPhoneInvalid(phone)){
            // 不一致,返回错误信息
            return Result.fail("手机号格式错误!");
        }
        // 2. 校验验证码
        String cacheCode = (String) session.getAttribute("code");
        String code = loginForm.getCode();
        if(cacheCode == null || !cacheCode.equals(cacheCode)){
            // 不一致,返回错误信息
            return Result.fail("验证码错误!");
        }
        // 4. 一致,根据手机号查询用户(这里使用的 mybatis-plus)
        User user = query().eq("phone", phone).one();
        // 5. 判断用户是否存在
        if(user == null){
            // 6. 不存在,创建新用户并保存
            user = createUserWithPhone(phone);
        }
        	// 7. 保存用户信息到 session 中(通过 BeanUtil.copyProperties 方法将 user 中的信息过滤到 UserDTO 上,即用来隐藏部分信息)
        session.setAttribute("user", BeanUtil.copyProperties(user, UserDTO.class));
        return Result.ok();
    }

    private User createUserWithPhone(String phone) {
        // 1. 创建用户
        User user = new User();
        user.setPhone(phone);
        user.setNickName("user_" + RandomUtil.randomString(10));
        // 2. 保存用户(这里使用 mybatis-plus)
        save(user);
        return user;
    }
}

1.4 实现登录校验拦截器

登录校验拦截器实现:

public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 1. 获取 session
        HttpSession session = request.getSession();
        // 2. 获取 session 中的用户
        UserDTO user = (UserDTO) session.getAttribute("user");
        // 3. 判断用户是否存在
        if(user == null){
            // 4. 不存在,拦截,返回 401 未授权
            response.setStatus(401);
            return false;
        }
        // 5. 存在,保存用户信息到 ThreadLocal
        UserHolder.saveUser(user);
        // 6. 放行
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        // 移除用户,避免内存泄露
        UserHolder.removeUser();
    }
}

UserHolder 类的实现: 该类中定义了一个静态的 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();
    }
}

配置拦截器:

@Configuration
public class MvcConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor())
                .excludePathPatterns(
                        "/user/login",
                        "/user/code"
                );
    }
}

前端请求说明:

  说明
请求方式 POST
请求路径 /user/me
请求参数
返回值

后端接口实现:

@Slf4j
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {

    @Override
    public Result me() {
        UserDTO user = UserHolder.getUser();
        return Result.ok(user);
    }
}

2. 集群的 session 共享问题

session 共享问题:

多台 tomcat 并不共享 session 存储空间,当请求切换到不同 tomcat 服务时会导致数据丢失的问题。

session 的替代方案应该满足以下条件:

  • 数据共享(不同的 tomcat 都能够访问 Redis 中的数据)
  • 内存存储(Redis 通过内存存储)
  • key、value 结构(Redis 是 key-value 结构)

3. 基于 Redis 实现共享 session 登录

3.1 Redis 实现共享 session 登录流程图

3.2 实现发送短信验证码

前端请求说明:

  说明
请求方式 POST
请求路径 /user/code
请求参数 phone(电话号码)
返回值

后端接口实现:

@Slf4j
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Override
    public Result sendCode(String phone, HttpSession session) {
        // 1. 校验手机号
        if (RegexUtils.isPhoneInvalid(phone)) {
            // 2. 如果不符合,返回错误信息
            return Result.fail("手机号格式错误!");
        }
        // 3. 符合,生成验证码(设置生成6位)
        String code = RandomUtil.randomNumbers(6);
        // 4. 保存验证码到 Redis(以手机号为 key,设置有效期为 2min)
        stringRedisTemplate.opsForValue().set("login:code:" + phone, code, 2, TimeUnit.MINUTES);
        // 5. 发送验证码(这里并未实现,通过日志记录)
        log.debug("发送短信验证码成功,验证码:{}", code);
        // 返回 ok
        return Result.ok();
    }
}

3.3 实现短信验证码登录、注册

前端请求说明:

  说明
请求方式 POST
请求路径 /user/login
请求参数 phone(电话号码);code(验证码)
返回值

后端接口实现:

@Slf4j
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {

    @Override
    public Result login(LoginFormDTO loginForm, HttpSession session) {
        // 1. 校验手机号
        String phone = loginForm.getPhone();
        if(RegexUtils.isPhoneInvalid(phone)){
            // 不一致,返回错误信息
            return Result.fail("手机号格式错误!");
        }
        // 2. 校验验证码
        String cacheCode = (String) session.getAttribute("code");
        String code = loginForm.getCode();
        if(cacheCode == null || !cacheCode.equals(cacheCode)){
            // 不一致,返回错误信息
            return Result.fail("验证码错误!");
        }
        // 4. 一致,根据手机号查询用户(这里使用的 mybatis-plus)
        User user = query().eq("phone", phone).one();
        // 5. 判断用户是否存在
        if(user == null){
            // 6. 不存在,创建新用户并保存
            user = createUserWithPhone(phone);
        }
        	// 7. 保存用户信息到 session 中(通过 BeanUtil.copyProperties 方法将 user 中的信息过滤到 UserDTO 上,即用来隐藏部分信息)
        session.setAttribute("user", BeanUtil.copyProperties(user, UserDTO.class));
        return Result.ok();
    }

    private User createUserWithPhone(String phone) {
        // 1. 创建用户
        User user = new User();
        user.setPhone(phone);
        user.setNickName("user_" + RandomUtil.randomString(10));
        // 2. 保存用户(这里使用 mybatis-plus)
        save(user);
        return user;
    }
}

3.4 实现登录校验拦截器

这里将原有的一个拦截器分成两个拦截器,第一个拦截器对所有的请求进行拦截,每次拦截刷新 token 的有效期,并将能查询到的用户信息保存到 ThreadLocal 中。第二个拦截器则进行拦截功能,对需要登录的路径进行拦截。

刷新 token 拦截器实现:

public class RefreshTokenInterceptor implements HandlerInterceptor {

    private StringRedisTemplate stringRedisTemplate;

    public RefreshTokenInterceptor(StringRedisTemplate stringRedisTemplate){
        this.stringRedisTemplate = stringRedisTemplate;
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 1. 获取请求头中的 token
        String token = request.getHeader("authorization");
        if (StrUtil.isBlank(token)) {
            return true;
        }
        // 2. 基于 token 获取 redis 中的用户
        String tokenKey = "login:token:" + token;
        Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(tokenKey);
        // 3. 判断用户是否存在
        if (userMap.isEmpty()) {
            return true;
        }
        // 5. 将查询到的 Hash 数据转为 UserDTO 对象
        UserDTO user = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);
        // 6. 存在,保存用户信息到 ThreadLocal
        UserHolder.saveUser(user);
        // 7. 刷新 token 有效期 30 min
        stringRedisTemplate.expire(tokenKey, 30, TimeUnit.MINUTES);
        // 8. 放行
        return true;
    }
}

登录校验拦截器实现:

public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 1. 获取 session
        HttpSession session = request.getSession();
        // 2. 获取 session 中的用户
        UserDTO user = (UserDTO) session.getAttribute("user");
        // 3. 判断用户是否存在
        if(user == null){
            // 4. 不存在,拦截,返回 401 未授权
            response.setStatus(401);
            return false;
        }
        // 5. 存在,保存用户信息到 ThreadLocal
        UserHolder.saveUser(user);
        // 6. 放行
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        // 移除用户,避免内存泄露
        UserHolder.removeUser();
    }
}

UserHolder 类的实现: 该类中定义了一个静态的 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();
    }
}

配置拦截器:

@Configuration
public class MvcConfig implements WebMvcConfigurer {

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new RefreshTokenInterceptor(stringRedisTemplate))
                .addPathPatterns("/**").order(0);
        registry.addInterceptor(new LoginInterceptor())
                .excludePathPatterns(
                        "/user/login",
                        "/user/code"
                ).order(1);
    }
}

前端请求说明:

  说明
请求方式 POST
请求路径 /user/me
请求参数
返回值

后端接口实现:

@Slf4j
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {

    @Override
    public Result me() {
        UserDTO user = UserHolder.getUser();
        return Result.ok(user);
    }
}

到此这篇关于Redis的共享session应用实现短信登录的文章就介绍到这了,更多相关Redis session 短信登录内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 详解springboot中redis的使用和分布式session共享问题

    对于分布式使用Nginx+Tomcat实现负载均衡,最常用的均衡算法有IP_Hash.轮训.根据权重.随机等.不管对于哪一种负载均衡算法,由于Nginx对不同的请求分发到某一个Tomcat,Tomcat在运行的时候分别是不同的容器里,因此会出现session不同步或者丢失的问题. 实际上实现Session共享的方案很多,其中一种常用的就是使用Tomcat.Jetty等服务器提供的Session共享功能,将Session的内容统一存储在一个数据库(如MySQL)或缓存(如Redis)中. 本文旨在

  • Redis解决Session共享问题的方法详解

    企业项目中,一般都是将项目部署到多台服务器上,用nginx做负载均衡.这样可以减轻单台服务器的压力,不过这样也带来一些问题,例如之前单机部署的话,session存取都是直接了当的,因为请求就只到这一台服务器上,不需要考虑数据共享.接下来分别用8000和8001端口启动同一个项目,做一个简单演示: 测试接口代码: package com.wl.standard.controller; import cn.hutool.core.util.StrUtil; import com.wl.standar

  • spring boot整合redis实现shiro的分布式session共享的方法

    我们知道,shiro是通过SessionManager来管理Session的,而对于Session的操作则是通过SessionDao来实现的,默认的情况下,shiro实现了两种SessionDao,分别为CachingSessionDAO和MemorySessionDAO,当我们使用EhCache缓存时,则是使用的CachingSessionDAO,不适用缓存的情况下,就会选择基于内存的SessionDao.所以,如果我们想实现基于Redis的分布式Session共享,重点在于重写Session

  • Redis实现Session共享与单点登录

    首先,导包. 在pom.xml文件里面加入以下: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.springframework.session</g

  • 学习Spring-Session+Redis实现session共享的方法

    1.添加依赖 <dependency> <groupId>org.springframework.session</groupId> <artifactId>spring-session-data-redis</artifactId> <version>1.2.1.RELEASE</version> </dependency> <dependency> <groupId>redis.cl

  • Redis的共享session应用实现短信登录

    目录 1. 基于 session 实现短信登录 1.1 短信登录流程图 1.2 实现发送短信验证码 1.3 实现短信验证码登录.注册 1.4 实现登录校验拦截器 2. 集群的 session 共享问题 3. 基于 Redis 实现共享 session 登录 3.1 Redis 实现共享 session 登录流程图 3.2 实现发送短信验证码 3.3 实现短信验证码登录.注册 3.4 实现登录校验拦截器 1. 基于 session 实现短信登录 1.1 短信登录流程图 1.2 实现发送短信验证码

  • Redis实现短信登录的企业实战

    目录 一.导入黑马点评项目 1. 导入SQL 2. 前后端分离 3. 导入后端项目 4. 导入前端项目 二.基于Session实现登录流程 1. 发送短信验证码 2. 短信验证码登录.注册 3. 登录验证功能 三.集群的session共享问题 四.基于Redis实现共享session的登录功能 1. 选择合适的数据结构存入Redis 2. 发送短信验证码 3. 短信验证码登录.注册 4. 解决token刷新问题 一.导入黑马点评项目 黑马点评项目主要包括以下功能: 这一章主要介绍短信登录功能,短

  • 如何基于Session实现短信登录功能

    目录 一.基于Session实现登录 1.1 业务流程图 二.发送短信验证码 2.1 发送短信请求方式及参数说明 三.登录功能 3.1  短信验证的请求方式及路径 3.2  业务层代码实现用户登录 3.3 拦截器——登录验证功能 三.隐藏用户敏感信息 四.session共享问题 总结 一.基于Session实现登录 1.1 业务流程图 二.发送短信验证码 2.1 发送短信请求方式及参数说明 这个地方为什么需要session?  因为我们需要把验证码保存在session当中 /** * 发送手机验

  • 基于Redis zSet实现滑动窗口对短信进行防刷限流的问题

    前言 主要针对目前线上短信被脚本恶意盗刷的情况,用Redis实现滑动窗口限流 public void checkCurrentWindowValue(String telNum) { String windowKey = CommonConstant.getNnSmsWindowKey(telNum); //获取当前时间戳 long currentTime = System.currentTimeMillis(); //1小时,默认只能发5次,参数smsWindowMax做成可配置项,配置到Na

  • javaweb项目如何实现手机短信登录

    手机号登录在现在的项目中用的场景非常多,实现起来也不难,今天我们就一起来通过演示实现登录过程.   一.首先需要注册个第三方的账户,比如秒嘀科技等,然后拿到三个参数值:QUERAY_PATH   ACCOUNT_SID       AUTH_TOKEN 二.编写获取验证码类getMessage.java private static final String QUERAY_PATH="xxxx"; private static final String ACCOUNT_SID=&quo

  • Spring Security 自定义短信登录认证的实现

    自定义登录filter 上篇文章我们说到,对于用户的登录,security通过定义一个filter拦截login路径来实现的,所以我们要实现自定义登录,需要自己定义一个filter,继承AbstractAuthenticationProcessingFilter,从request中提取到手机号和验证码,然后提交给AuthenticationManager: public class SmsAuthenticationFilter extends AbstractAuthenticationPro

  • 小程序实现短信登录倒计时

    在平时的短信登录中,当发送短信验证码后会显示倒计时,那么这个倒计时如何实现呢? wxml文件 <view class='Form'> <form bindsubmit="formSubmit" bindreset="formReset" class='forms'> <view class="fidpas"> <input type="number" class="phone

  • mpvue网易云短信接口实现小程序短信登录的示例代码

    上一篇简单介绍了mpvue实现快递单号查询,慢慢发现mpvue真的和vue很像,但它有几乎十分的吻合小程序的语法规范,刚开始用起来会觉得特点的爽,但涉及到细节却是有很多采坑的地方.今天利用网上的网易云接口,再结合mpvue简单写一写小程序短信验证登录. 简单封装的一个网络请求文件,网易云接口网上大佬们GitHub上还是比较的多而且开源 const baseURL = "https://*****:1717"; //基路径 exports.http = function({url,met

  • 微信小程序实现短信登录的实战

    目录 1.界面效果预览 2.uView安装 3.uView配置 3.1 main.js中引入 3.2 uni.scss中引入 3.3 App.vue中引入 3.4 pages.json中配置 4.短信登录界面 5.点击获取验证码接口 项目要求增加短信登录及人脸识别登录功能,下面我们来实现下短信登录功能 1.界面效果预览 2.uView安装 uView官网:https://www.uviewui.com 以npm安装为例,执行:npm install uview-ui即可 3.uView配置 3.

  • 基于Redis实现短信验证码登录项目示例(附源码)

    目录 Redis短信登录流程描述 短信验证码的发送 短信验证码的验证 是否登录的验证 源码分析 模拟发送短信验证码 短信验证码的验证 校验是否登录 登录验证优化 Redis短信登录流程描述 短信验证码的发送 用户提交手机号,系统验证手机号是否有效,毕竟无效手机号会消耗你的短信验证次数还会导致系统的性能下降.如果手机号为无效的话就让用户重新提交手机号,如果有效就生成验证码并将该验证码作为value保存到redis中对应的key是手机号,之所以这么做的原因是保证key的唯一性,如果使用固定字符串作为

随机推荐