基于SpringBoot + Vue的个人博客系统14——退出登录功能

简介

退出登录的一种比较简单的实现是直接在客户端删除token,但是这存在一个问题,就是被删除的 token 依然有效,按理说注销登录之后 token 应该也是失效的。这一篇文章就主要介绍怎么让 token 失效

网上方法有很多,可以参考这篇文章:JWT 身份认证优缺点分析以及常见问题解决方案

这里我们使用 Redis 黑名单的方式

安装Redis

1、安装 docker (这里使用阿里云,系统 CentOS 7)

# 安装需要的工具包
sudo yum install -y yum-utils

# 设置镜像仓库 (这里使用阿里云的)
sudo yum-config-manager \
    --add-repo \
    http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo

# 安装docker
sudo yum install docker-ce docker-ce-cli containerd.io

# 启动 docker
sudo systemctl start docker

说明:

  • 如果下载镜像太慢的话可以使用阿里云的镜像加速服务

阿里云镜像加速

2、安装 Portainer 可视化面板(这一步根据个人习惯,个人喜欢界面操作)

# 创建容器数据卷
docker volume create portainer_data
# 安装 Portainer
docker run -d -p 9000:9000 -p 8000:8000 --name portainer --restart always -v /var/run/docker.sock:/var/run/docker.sock -v portainer_data:/data portainer/portainer

3、安装完成之后,需要在阿里云开启安全组才能在本地访问,这里为了省事,笔者直接将8000~9000的全添加了进去

阿里云安全组

4、这个时候就可以在本机访问服务器的 9000 端口,来管理 docker 了

portainer界面

5、新建 stack

新建stack

这里启动容器是基于docker compose ,注意版本为 2

docker-compose.yml 文件内容如下

version: '2'
services:
  redis:
    image: redis
    container_name: redis
    hostname: redis
    restart: always
    ports:
      - 8379:6379
    volumes:
      - ./conf/redis.conf:/etc/redis/redis.conf:rw
      - ./data:/data/redis_data:rw
    command:
      redis-server /etc/redis/redis.conf --appendonly yes

然后点击 “Deployment” 进行发布,redis就安装好了

Redis安装成功

6、安装好 redis 之后我们需要一个链接工具,这种类型的工具很多,这里使用的是一个叫 redis-commander 的工具,是使用node写的

github: https://github.com/joeferner/redis-commander

安装并启动:

# 全局安装
npm install -g redis-commander
# 指定服务器地址,端口
redis-commander --redis-host 47.94.128.215 --redis-port 8379

然后在浏览器访问http://localhost:8081即可

redis客户端

SpringBoot 集成 Redis

1、引入依赖

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

2、新建 Redis 配置类

package pers.qianyucc.qblog.config;

import org.springframework.boot.autoconfigure.*;
import org.springframework.boot.autoconfigure.data.redis.*;
import org.springframework.context.annotation.*;
import org.springframework.data.redis.connection.lettuce.*;
import org.springframework.data.redis.core.*;
import org.springframework.data.redis.serializer.*;

import java.io.*;
import java.nio.charset.*;

@Configuration
@AutoConfigureAfter(RedisAutoConfiguration.class)
public class RedisConfig {
    @Bean
    public RedisTemplate<String, Serializable> redisCacheTemplate(LettuceConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Serializable> template = new RedisTemplate<>();
        // key的序列化器设置成StringRedisSerializer
        template.setKeySerializer(new StringRedisSerializer());
        // 解决中文乱码问题
        template.setValueSerializer(new GenericToStringSerializer<>(String.class, StandardCharsets.UTF_8));
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }
}

3、编写 logout 的api接口,因为 Redis 中集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1),所以使用 set 存储 已经过期的 JWT

UserController

@PostMapping("/auth/logout")
@ApiOperation("用户注销登录")
public Results<Object> logout(@RequestAttribute("token") String token) {
    redisTemplate.opsForSet().add(JwtConstants.REDIS_KEY, token);
    return Results.ok("退出登录成功", null);
}

4、修改拦截器,增加对 Redis 里面是否存在token的判断

package pers.qianyucc.qblog.interceptor;

//......
@Slf4j
@Component
public class JwtTokenInterceptor implements HandlerInterceptor {
    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @Override
    public boolean preHandle(HttpServletRequest req, HttpServletResponse res, Object handler) throws Exception {
        String token = req.getHeader(JwtConstants.TOKEN_HEADER);
        // 判断 token 是否非空,判断 token 的前缀
        if (Objects.isNull(token) || !token.startsWith(JwtConstants.TOKEN_PREFIX)) {
            throw new BlogException(ErrorInfoEnum.NOT_LOGIN);
        }
        // 判断token是否过期
        token = token.replace(JwtConstants.TOKEN_PREFIX, StrUtil.EMPTY);
        if (JwtUtils.isTokenExpired(token)) {
            throw new BlogException(ErrorInfoEnum.TOKEN_EXPIRED);
        }
        // 判断 token 是否失效
        Boolean isMember = redisTemplate.opsForSet().isMember(JwtConstants.REDIS_KEY, token);
        if (isMember) {
            throw new BlogException(ErrorInfoEnum.TOKEN_INVALID);
        }
        Claims claims = JwtUtils.getTokenBody(token);
        String[] roles = Optional.ofNullable(claims.get(JwtConstants.ROLE_CLAIMS))
                .map(r -> r.toString().split(","))
                .orElse(new String[0]);
        // 判断角色是否正确
        if (!ArrayUtil.contains(roles, UserRoleEnum.ADMIN.getValue())) {
            throw new BlogException(ErrorInfoEnum.NO_AUTHORITY);
        }
        req.setAttribute("token", token);
        return true;
    }
}

前端设计

同样,vue-template-admin 已经帮我们完成了大部分内容,我们只需要在他的基础上进行修改就行了

1、修改@/api/user.js的logout方法

export function logout() {
  return request({
    url: '/auth/logout',
    method: 'post'
  })
}

2、下面分析一下退出登录的执行流程

@/layout/components/NavBar.vue中的logout函数,实际上执行了 vuex 中的logout方法

    async logout() {
      await this.$store.dispatch("user/logout");
      this.$router.push(`/login?redirect=${this.$route.fullPath}`);
    },

@/store/modules/user.js的logout方法中可以看到,调用logout api 成功之后,主要执行了三个方法

  • removeToken:使用js-cookie插件,删除cookie中的token
  • resetRouter:重置路由
  • commit(‘RESET_STATE’):将 vuex 中的值恢复到默认值
  logout({ commit, state }) {
    return new Promise((resolve, reject) => {
      logout(state.token).then(() => {
        removeToken() // must remove  token  first
        resetRouter()
        commit('RESET_STATE')
        resolve()
      }).catch(error => {
        reject(error)
      })
    })
  },

@/utils/auth.js

export function removeToken() {
  return Cookies.remove(TokenKey)
}

@/router/index.js

export function resetRouter() {
  const newRouter = createRouter()
  router.matcher = newRouter.matcher // reset router
}

@/store/modules/user.js

  RESET_STATE: (state) => {
    Object.assign(state, getDefaultState())
  },
const getDefaultState = () => {
  return {
    token: getToken(),
    name: '',
    avatar: ''
  }
}
测试效果

1、然后我们登录之后再退出,可以在 Redis 中看到已经存入了一个过期的token

token已经存入Redis

2、然后我们再使用这个 token 去进行操作,发现已经提示过期

token已过期

参考代码:https://gitee.com/qianyucc/QBlog2/tree/v-10.0

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值