黑马点评项目01——短信登录以及登录校验的细节

1.短信登录

1.1 Session方式实现

前端点击发送验证码 ,后端生成验证码后,向session中存放键值对,键是"code",值是验证码;然后,后端生成sessionID以Cookie的方式发给前端,前端拿到后,以后每一次访问都会携带这个Cookie;此时,前端点击登录 的时候,会带着这个cookie进入后端,后端拿到这个sessionID后就知道是哪一个请求端发过来的,找到对应的session 验证码,然后对比是否相同;注意,此时要验证手机号是不是原来那个手机号 ,黑马点评只是验证了它是否满足一个手机号规则,并没有验证是原来那个手机号;根据手机号查询用户,没查到,数据库新建用户信息,查到了,把"user": UserDTO对象(User对象的简化版,不保留敏感信息)放到session中,这是为登录校验 准备的,因为有些功能只有登录的状态才可以实现,我们可以把它放到拦截器中实现,实现HandlerInterceptor 接口的preHandle方法,具体如下:

java 复制代码
@Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 1.获取session
        HttpSession session = request.getSession();
        // 2.获取session 用户
        Object user = session.getAttribute("user");
        // 3.判断用户是否存在
        if (user == null) {
            // 4.不存在
            response.setStatus(401);
            return false;
        }
        // 5.存在,保存信息到本地线程中
        UserHolder.saveUser((UserDTO) user);
        return true;
    }

也就说,一些功能,session中没有user就不会让你过来,有user,就把UserDTO对象存放在本地线程中,如何是实现的呢?

java 复制代码
package com.hmdp.utils;


import com.hmdp.dto.UserDTO;
import com.hmdp.entity.User;

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

缺点:

在单台服务器上,Session 保存在服务器内存中,客户端通过 Cookie 携带 Session ID,服务器根据 Session ID 直接找到对应的会话数据。

但是在多台 服务器 (集群)中(多台tomcat),用户的请求可能每次都被不同的服务器接收,这时如果 Session 没有共享,某台服务器找不到该用户的 Session,导致用户无法保持登录状态。
解决方案:

1、无状态认证方式

使用 Token(如 JWT)登录,服务器不保存 Session,完全无状态,天然适合分布式集群。(学到了再说)

2、redis实现

多台服务器连接同一个redis实例,就避免了这个问题。

1.2 Redis实现

先来看一下RedisTemplate与StringRedisTemplate的区别:

RedisTemplate 可以自定义<K,V>的序列化方式,StringRedisTemplate 默认使用String序列化方式(键和值必须都是String类型),一般来说,用RedisTemplate的时候要自定义序列化方式(默认的JDK序列化方式不好用),键都是String ,值都是Jackson,这样可以将对象放到值里面。加上这种配置:

java 复制代码
@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory){
        // 创建RedisTemplate对象
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        // 设置连接工厂
        template.setConnectionFactory(connectionFactory);
        // 创建JSON序列化工具
        GenericJackson2JsonRedisSerializer jsonRedisSerializer =
                new GenericJackson2JsonRedisSerializer();
        // 设置Key的序列化 string
        template.setKeySerializer(RedisSerializer.string());
        template.setHashKeySerializer(RedisSerializer.string());
        // 设置Value的序列化 json
        template.setValueSerializer(jsonRedisSerializer);
        template.setHashValueSerializer(jsonRedisSerializer);
        // 返回
        return template;
    }
}

配置好之后,可以直接这样使用:

java 复制代码
// 存储对象
User user = new User("Alice", 20);
redisTemplate.opsForValue().set("user:1", user);

// 读取对象
User userFromRedis = (User) redisTemplate.opsForValue().get("user:1");
System.out.println(userFromRedis.getName());  // 输出:Alice

对于之前StringRedisTemplate的一点疑惑的解决:

它也可以操作Hash,Set,只不过是键和值都是String类型的,下面是一些示例

java 复制代码
@Autowired
private StringRedisTemplate stringRedisTemplate;

// 操作 Hash
stringRedisTemplate.opsForHash().put("user:1000", "name", "Alice");
stringRedisTemplate.opsForHash().put("user:1000", "age", "18");

// 操作 Set
stringRedisTemplate.opsForSet().add("onlineUsers", "user1");
stringRedisTemplate.opsForSet().add("onlineUsers", "user2");

回归正题:这里我们使用RedisTemplate ,因为我们要用到一次存放的值是对象

前端点击发送验证码,后端向redis存放键值对<phone,验证码>并设置3min有效期 ,都是String类型哈,前端点击登录时,redis有没有这样一个键值对,返回是一个对象 ,如果不是空,要toString(),然后再equals(),如果比对成功,根据手机号,在数据库查询是否有这么个用户,没有就创建,然后把对象存放到redis中,那么问题来了,键应该是什么,我们这里随机生成全局唯一token, UUID.randomUUID()作为标识符key;在登录校验中,我们需要获取请求头中的token, String token = request.getHeader("authorization");根据这个token在redis查询比对,实现拦截功能。

注意与JWT+Token的区别

相关推荐
forestsea5 分钟前
深入理解Redisson RLocalCachedMap:本地缓存过期策略全解析
redis·缓存·redisson
佛祖让我来巡山25 分钟前
Redis 为什么这么快?——「极速快递站」的故事
redis·redis为什么快?
啦啦啦_99992 小时前
Redis-0-业务逻辑
数据库·redis·缓存
自不量力的A同学3 小时前
Redisson 4.2.0 发布,官方推荐的 Redis 客户端
数据库·redis·缓存
fengxin_rou3 小时前
[Redis从零到精通|第四篇]:缓存穿透、雪崩、击穿
java·redis·缓存·mybatis·idea·多线程
是阿楷啊4 小时前
Java大厂面试场景:音视频场景中的Spring Boot与微服务实战
spring boot·redis·spring cloud·微服务·grafana·prometheus·java面试
笨蛋不要掉眼泪4 小时前
Redis哨兵机制全解析:原理、配置与实战故障转移演示
java·数据库·redis·缓存·bootstrap
ALex_zry16 小时前
Redis Cluster 分布式缓存架构设计与实践
redis·分布式·缓存
乔江seven19 小时前
【Flask 进阶】3 从同步到异步:基于 Redis 任务队列解决 API 高并发与长耗时任务阻塞
redis·python·flask
这周也會开心20 小时前
Redis与MySQL回写中的数据类型存储设计
数据库·redis·mysql