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

如何使jwt生成的 token在用户登出之后失效?_第1张图片

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

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

举个例子:

@PostMapping(value = "/authentication/logout")
public ResponseEntity 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 list = redisRWService.takeTokenStorage(userName + Const.TOKEN_PREFIX);
        //如果集合中包含这个token,则可以正常登出,否则就是非法请求
        if (list.contains(token)) {
            //在集合中删除该token后重新存入redis
            List 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 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);
    }


}

你可能感兴趣的:(项目,java,框架,java,开发语言,spring,boot)