How to make the token generated by jwt invalid after the user logs out?

Insert image description here

Question 1: How to make the token generated by jwt invalid after the user logs out?

Since the token generated by jwt is stateless, this is reflected in the fact that our request will create a new session object every time we request:

for example:

@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;}

This will cause us to use the original token to still have a chance to be verified by the system after exiting the system, and have permission to access the system;

So we need to use other methods to invalidate the token (it is not that the token is actually invalid but that it is added to the blacklist or marked in some other way);

1. The method of adding to the blacklist requires additional storage space, and this method has not been considered this time;

2. In addition to addition, we can also use subtraction to achieve:

First, we save the user’s login token in redis.

It should be considered here that users can log in from multiple terminals at the same time (that is, each device will generate a token). If one device logs out and does not want to affect the login of other devices, the logged out token must be processed separately at this time;

A design made here is to "store the login tokens of all users (the same user) in the form of a list in redis". If a user logs out, the user's token will be deleted from the list, and the list will be verified on the next request. Whether the key exists in the database, if so, it is released, otherwise you need to log in again;

Code:

Cache token when logging in:

        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已缓存用户信息");

Remove the token from the list when logging out:

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

The effective ones are handed over to the interceptor/filter:

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


}

Guess you like

Origin blog.csdn.net/weixin_54061333/article/details/132054099