如何使jwt生成的 token在用户登出之后失效?

13 篇文章 0 订阅
4 篇文章 0 订阅

在这里插入图片描述

问题1:如何使jwt生成的 token在用户登出之后失效?

由于jwt生成的token是无状态的,这体现在我们在每一次请求时 request都会新建一个session对象:

举个例子:

@PostMapping(value = "/authentication/logout")
public ResponseEntity<BaseResult> logOut(HttpServeletRequest request ,@RequestHeader(name = "Authorization") String authHeader) throws AuthenticationException, IOException {
    HttpSession session =request.getSession();
    session.isNew();//true
    return null;}

这就会导致我们在退出系统之后,使用原token依然有机会被系统检验通过,而有权限访问系统;

所以我们需要借助别的方式使得该token失效(并不是token真正失效而是将其加入黑名单或者使用别的什么方式标记它);

1,加入黑名单的方式需要额外的占用存储空间,本次暂未考虑该方式;

2,除了加,我们还可以使用减法的方式来实现:

首先我们在redis中将用户登陆的token存起来,

这里要考虑用户可以多端同时登陆(即每个设备都会产生一个token),如果一个设备登出而不想影响其他设备的登录,此时就要将登出的那个token单独处理;

这里做了一个设计是"将所有用户(同一用户)登录token已list的形式存在redis中",如果一个用户登出,则将该用户的token从list中删除,下次请求时会校验list中是否有该key,如果有放行,否则就要重新登录;

代码实现:

登陆时缓存token:

        HttpStatus httpStatus;
        BaseResult baseResult;
        HttpHeaders httpHeaders = new HttpHeaders();
        String password = RSAUtils.privateDecrypt(user.getPassword(), RSAUtils.getPrivateKey(Const.PRIVATE_KEY));
        if (!HmrsUtils.isValidPassword(password)) {
            log.error("密码不匹配:{}", user.getUsername());
            throw new PasswordLengthException();
        }
        //登陆的时候考虑浏览器的因素,可以多个浏览器同时登录一个账号
        String token = authService.login(user.getUsername(), password);
        if (StrUtil.isEmpty(token)) {
            httpHeaders.add("Authorization", null);
            httpStatus = HttpStatus.BAD_REQUEST;
            baseResult = HmrsUtils.setHttpBaseResult(httpStatus.value(), "登录失败", "用户名/密码错误");
        } else {
            httpHeaders.add("Authorization", token);
            httpStatus = HttpStatus.OK;
            baseResult = HmrsUtils.setHttpBaseResult(httpStatus.value(), "登录成功", null);
            log.info("{}用户登录", user.getUsername());
            User userInfo = userService.queryUserInfoWithOutPwd(user.getUsername());
            baseResult.setUserInfo(userInfo);
            //登录成功后缓存用户信息 --key -->username value  ---缓存用户信息作为热信息
            redisRWService.saveJsonObject(user.getUsername(), userInfo);//存入的是一个对象
            //缓存token 一个集合 就存字符串
            redisRWService.saveTokenStorage(user.getUsername() + Const.TOKEN_PREFIX, token);
//            redisRWService.saveJsonObject(user.getUsername() + Const.TOKEN_PREFIX, token);
            redisRWService.setKeyExpireTime(user.getUsername() + Const.TOKEN_PREFIX, Const.REDIS_TOKEN_EXPIRE);
            log.info("redis已缓存用户信息");

登出时list中移除该token:

HttpStatus httpStatus;
    BaseResult baseResult;
    HttpHeaders httpHeaders = new HttpHeaders();
    String userName = JwtTokenUtil.getUserName(authHeader);
    String token = authHeader.substring(Const.TOKEN_PREFIX.length() + 1);
    //如果有key,说明有用户登录
    if (redisRWService.hasKey(userName + Const.TOKEN_PREFIX)) {
        //取出这个list
        List<String> list = redisRWService.takeTokenStorage(userName + Const.TOKEN_PREFIX);
        //如果集合中包含这个token,则可以正常登出,否则就是非法请求
        if (list.contains(token)) {
            //在集合中删除该token后重新存入redis
            List<String> collect = list.stream().filter(str -> !str.equals(token)).collect(Collectors.toList());
            redisRWService.deleteKey(userName + Const.TOKEN_PREFIX);
            redisRWService.saveAllTokenStorage(userName + Const.TOKEN_PREFIX, collect);
            httpStatus = HttpStatus.OK;
            baseResult = HmrsUtils.setHttpBaseResult(httpStatus.value(), "登出成功", null);
            log.info("{}登出", userName);
            httpHeaders.add("Authorization", null);
            return new ResponseEntity<>(baseResult, httpHeaders, httpStatus);
        } else {
            httpStatus = HttpStatus.FORBIDDEN;
            baseResult = HmrsUtils.setHttpBaseResult(httpStatus.value(), "登出失败", "请重新登录");
            log.error("{}登出失败", userName);
            return new ResponseEntity<>(baseResult, null, httpStatus);
        }
        //否则就是该账号用户长时间未登录
    } else {
        httpStatus = HttpStatus.FORBIDDEN;
        baseResult = HmrsUtils.setHttpBaseResult(httpStatus.value(), "登出失败", "长时间未登录,请登陆后重试");
        log.error("{}登出失败", userName);
        return new ResponseEntity<>(baseResult, null, httpStatus);
    }
}

生效的交给拦截器/过滤器:

@Slf4j
@Component
public class JwtTokenInterceptor implements HandlerInterceptor {

    @Autowired
    private RedisRWService redisRWService;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {

        String authHeader = request.getHeader(Const.HEADER_STRING);
        if (StrUtil.isNotEmpty(authHeader)) {
            String token = authHeader.substring(Const.TOKEN_PREFIX.length() + 1);
            //从token中取出用户名
            String userName = JwtTokenUtil.getUsernameFromToken(token);
            if (StrUtil.isNotEmpty(userName)) {
                //能解析出用户名 判断登录状态
                if (redisRWService.hasKey(userName + Const.TOKEN_PREFIX)) {
                    List<String> list = redisRWService.takeTokenStorage(userName + Const.TOKEN_PREFIX);
                    //如果集合中有这个token
                    if (list.contains(token)) {
                        log.info("{}用户登录", userName);
                        return true;
                    } else {
                        String jsonString = JSONObject.toJSONString(HmrsUtils.setHttpBaseResult(Const.TOKEN_EXPIRE, "请登录再试", "登录已过期,请重新登录"));
                        HmrsUtils.returnJson(response, jsonString);
                        return false;
                    }

                } else {
                    String jsonString = JSONObject.toJSONString(HmrsUtils.setHttpBaseResult(Const.TOKEN_EXPIRE, "长时间未登录", "长时间未登录,请登录后重试"));
                    HmrsUtils.returnJson(response, jsonString);
                    return false;
                }
            } else {
                String jsonString = JSONObject.toJSONString(HmrsUtils.setHttpBaseResult(400, "登陆失败", "非法请求"));
                HmrsUtils.returnJson(response, jsonString);
                return false;
            }
        } else {
            String jsonString = JSONObject.toJSONString(HmrsUtils.setHttpBaseResult(400, "登陆失败", "用户名/密码不正确"));
            HmrsUtils.returnJson(response, jsonString);
            return false;
        }
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {

    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {

    }

package com.hmrs.filter;

import com.alibaba.fastjson2.JSONObject;
import com.hmrs.comm.Const;
import com.hmrs.util.HmrsUtils;
import com.hmrs.util.JwtTokenUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Slf4j
@Component
public class JwtTokenFilter extends OncePerRequestFilter {

    @Autowired
    private UserDetailsService userDetailsService;


    @Override
    public FilterConfig getFilterConfig() {
        return super.getFilterConfig();
    }


    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain
            chain) throws ServletException, IOException {
        String authHeader = request.getHeader(Const.HEADER_STRING);
        if (authHeader != null && authHeader.startsWith(Const.TOKEN_PREFIX)) {
            final String authToken = authHeader.substring(Const.TOKEN_PREFIX.length() + 1);
            String username = JwtTokenUtil.getUsernameFromToken(authToken);
            if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
                UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
                if (JwtTokenUtil.validateToken(authToken, userDetails)) {
                    UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
                            userDetails, null, userDetails.getAuthorities());
                    authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(
                            request));
                    SecurityContextHolder.getContext().setAuthentication(authentication);
                }
            } else if (username == null) {
                log.error("登录已过期");
                String result = JSONObject.toJSONString(HmrsUtils.setHttpBaseResult(1002, "failed", "登陆已过期"));
                HmrsUtils.returnJson(response, result);
                return;
            }
        }
        chain.doFilter(request, response);
    }


}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
JWT生成token是一个字符串,由三部分组成:头部、载荷和签名。头部指定了Token类型和所使用的加密算法,载荷存放的是自定义的数据,一般包括用户ID、过期间等信息,签名需要使用编码后的头部和载荷以及一个秘钥进行加密。最后将头部、载荷和签名用点号连接起来,生成最终的JWT token。\[1\]\[3\] 这个token可以用于用户身份认证,服务端可以根据这个字符串来认定用户身份,并且可以验证token是否过期。JWT生成token具有较高的安全性,因为它可以加密并支持多种算法,而且携带的信息是自定义的。此外,JWT的使用还可以减少后端的内存消耗,因为验证信息可以由前端保存。\[2\] #### 引用[.reference_title] - *1* *2* [利用JWT生成Token](https://blog.csdn.net/GG_and_DD/article/details/84314872)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [JWT-生成token](https://blog.csdn.net/m0_69128892/article/details/124714234)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

CodeMartain

祝:生活蒸蒸日上!

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

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

打赏作者

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

抵扣说明:

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

余额充值