Redis实现单点登录(并且只能在一台设备登录)

Redis实现单点登录(并且只能在一台设备登录)

1、Redis配置

依赖注入

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

全局配置

spring:
  redis:
    host: 127.0.0.1
    port: 6379
    timeout: 30000
    jedis:
      pool:
        max-active: 8
        max-wait: -1
        max-idle: 500
        min-idle: 0
      lettuce:
        shutdown-timeout: 0

Redis工具配置

因为官方给的工具类太难用了,所以我们选择自己写一个工具

RedisConfig

@Configuration
@EnableCaching
public class RedisConfig {
    @Bean
    @SuppressWarnings("all")
    public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory factory){
        RedisTemplate<String,Object> template = new RedisTemplate<String,Object>();
        template.setConnectionFactory(factory);
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL,JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        // key采用String的序列化方式
        template.setKeySerializer(stringRedisSerializer);
// hash的key也采用String的序列化方式
        template.setHashKeySerializer(stringRedisSerializer);
// value序列化方式采用jackson
        template.setValueSerializer(jackson2JsonRedisSerializer);
// hash的value序列化方式采用jackson
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        template.afterPropertiesSet();
        return template;

    }

RedisUtils

以下举几个例子,更多方法请在Redis的文档里查看

@Component
public class RedisUtils {
    @Autowired
    RedisTemplate<String,Object> redisTemplate;
    /**
     * 普通缓存放入
     * @param key 键
     * @param value 值
     * @return true成功 false失败
     */

    public boolean set(String key, Object value) {
        try {
            redisTemplate.opsForValue().set(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 普通缓存放入并设置时间
     * @param key 键
     * @param value 值
     * @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
     * @return true成功 false 失败
     */
    public boolean set(String key, Object value, long time) {
        try {
            if (time > 0) {
                redisTemplate.opsForValue().set(key, value, time,
                        TimeUnit.SECONDS);
            } else {
                set(key, value);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    public boolean exist(String key){
        return redisTemplate.hasKey(key);
    }

    public void remove(String key){
        System.out.println(key);
            redisTemplate.delete(key);
    }



    /**
     * 读取缓存
     *
     * @param key
     * @return
     */

    public String  get(final String key) {
        return String.valueOf(redisTemplate.opsForValue().get(key));
    }

}

2、拦截器配置

实现只有登录过的session才能访问某些接口

1、编写redis拦截器

public class RedisSessionInterceptor implements HandlerInterceptor {
    @Autowired
    private RedisUtils redisUtils;
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response,Object handler)throws Exception{
        String sid = request.getSession().getId();
        if(redisUtils.exist(sid))
            return true;
        response401(response);
        return false;
    }
    private void response401(HttpServletResponse response){
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json; charset=utf-8");
        try
        {
            response.setStatus(StatusCode.NEED_LOGIN);
            response.getWriter().print(JSON.toJSONString(new Result<String>(StatusCode.NEED_LOGIN,"","用户未登录"))) ;
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }
    }
}

2、将拦截器注册到拦截器配置类中

@Configuration
public class WebSecurityConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry){
        //添加拦截器,并设置拦截的接口、忽略的接口
        registry.addInterceptor(redisSessionInterceptor())
                .addPathPatterns("/api/user/**")
                .excludePathPatterns("/api/user/login")
                .excludePathPatterns("/swagger-ui.html/**")
                .excludePathPatterns("/api/user/register");
    }
    @Bean
    public RedisSessionInterceptor redisSessionInterceptor(){
        return new RedisSessionInterceptor();
    }
}

此处注意,拦截器一定要在配置文件中注入,如此处在配置类中使用@Bean注解注入(这样才能保证先执行拦截器中Autowired自动注入的对象,否则Utils会注入失败)

3、登录逻辑编写

检查sessionid逻辑

前端每次打开网页时,应调用此接口,检查session是否存在于redis中,如果存在,则可直接跳过登录的步骤

public Result<String> setRedisResult(HttpServletRequest request){

    //第一次登录
    //1. 取出当期客户端的sessionId
    String sId=request.getSession().getId();
    //2. 查询该sessionId 是否存在于redis
    boolean exists = redisUtils.exist(sId);

    if (!exists){
        return new Result<String>(StatusCode.NEED_LOGIN,"","用户需登录");
    }else {
        //2.2 已经登录过,则存入redis中刷新过期时间,再直接返回成功页面
        redisUtils.set(sId,"login success",1000);
        return new Result<String>(StatusCode.SUCCESS,"","sid验证通过");
    }

}

登录逻辑

此处省略了用户User类与Mybatis-plus mapper层

@Autowired
RedisUtils redisUtils;
@Autowired
UserMapper userMapper;
public Result<String> login(HttpServletRequest request, Long id, String password){
    User user = userMapper.selectById(id);
    if(user==null){

        return new Result<String>(StatusCode.LOGIN_MATCH_ERROR,"","用户不存在");
    }else if(!user.getPassword().equals(password)){
        return new Result<String>(StatusCode.LOGIN_MATCH_ERROR,"","密码错误");
    }else {
        if(redisUtils.exist(id.toString())){
            redisUtils.remove(redisUtils.get(id.toString()));
            redisUtils.remove(id.toString());
        }
        redisUtils.set(request.getSession().getId(),"login success",1000);//有效期为1000s
        redisUtils.set(id.toString(),request.getSession().getId(),1000);
        //如果通过后,写入session域进行共享,即使是负载不同端口,sessionId不会发生变化
        return new Result<String>(StatusCode.SUCCESS,getToken(user),"登录成功!");
    }
}

因为要实现只有一个设备能同时登录本账号,所以登录验证通过后,第一步是检查该id是否最近登录过。如果登录过则将Redis中存的两条记录先删除

if(redisUtils.exist(id.toString())){
            redisUtils.remove(redisUtils.get(id.toString()));
            redisUtils.remove(id.toString());
        }

然后再向redis中存入两条新的缓存数据

一条key:sessionid value:login success

另一条 key: id(账号) value: sessionid

通过这两条记录,就可以判断当前设备是否登录,当前账号是否登录,并可通过登录账号的id查到登录设备的sessionid

退出登录

public Result<String> userLoginOut(HttpServletRequest request){

    String sId=request.getSession().getId();
    redisUtils.remove(sId);
    return new Result<String>(StatusCode.SUCCESS,"","已退出登录!");
}

退出登录时,获取下sid删了就行

此处虽然redis中 key为id的记录并未删除,但是并未影响功能

如果想实现把key为id的记录也删了,可以通过登录时获取token,在退出登录时通过header中获取的token解析出id再删除

  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值