Technology Sharing

In springboot, use threadlocal to store user identity information in the filter

2024-07-12

한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina

This article mainly describes the method of setting jwt information in the filter to save identity information through the filter class of springboot
Process: request->filter->parse request body information->put into threadlocal

Define a filter: A filter that uses the Servlet specification, which is registered through the @WebFilter annotation to intercept all HTTP requests that match the /api path.

The @WebFilter("/api") annotation specifies that the filter will be applied to all requests accessing the /api path.
@Component annotation:

@Component is an annotation of the Spring framework, indicating that JwtFilter is a Spring component that can be managed by the Spring container and supports dependency injection.
doFilter method:
The doFilter method defines how the filter intercepts and processes requests and responses entering the Servlet or Servlet container.
Method signature:

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
    throws IOException, ServletException;
  • 1
  • 2

This method accepts three parameters: ServletRequest request, ServletResponse response and FilterChain chain.
It may throw IOException or ServletException.
Request and Response:

The first two parameters of the doFilter method represent the current request and response objects. You can read request data, modify requests and responses in this method.
Typically, at the end of the doFilter method, you need to call chain.doFilter(request, response) to continue executing the next filter or target resource in the filter chain.

If you want to modify the request content, you can use HttpServletRequestWrapper, which is a wrapper class that extends the HttpServletRequest interface, allowing you to modify or extend the request processing. The purpose of using HttpServletRequestUriWrapper (which may be a custom wrapper class that inherits from HttpServletRequestWrapper) usually includes:

Modify the request URI:

You may want to modify the request URI without changing the original HttpServletRequest object. By using HttpServletRequestUriWrapper, you can wrap the original request and provide a modified URI.
Keep the original request unchanged:

Using a wrapper keeps the original request object intact while allowing you to modify certain aspects of the request at a certain point in the filter chain.
Filtering and pre-processing:

Before calling filterChain.doFilter, you can add any pre-processing logic in the doFilter method, such as modifying request parameters, changing the request path, adding or modifying request headers, etc.

import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.FilterConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;

@WebFilter("/api")
@Component
@Slf4j
public class JwtFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) {
        // noting to do
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
            throws IOException, ServletException {

        var httpRequest = (HttpServletRequest) servletRequest;
        var requestBodyPayload = StreamUtils.copyToString(servletRequest.getInputStream(), StandardCharsets.UTF_8);

        // 解析Body参数,并存入threadLocal管理
        var jwtInfo = JwtUtil.getJwtInfoFromReq(requestBodyPayload);
        JwtUtil.setJwtInfo(jwtInfo);

        // 读取过body,需要重新设置body
        var wrapper = new HttpServletRequestUriWrapper(httpRequest, httpRequest.getRequestURI(), requestBodyPayload);

        // 将请求传递到下一个过滤器(或者最终到达控制器方法)
        filterChain.doFilter(wrapper, servletResponse);
    }

    @Override
    public void destroy() {
        JwtUtil.removeJwtInfo();
        MDC.clear();
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46

jwt information:

@Slf4j
@Component
public class JwtUtil {

    /** 线程jwt信息维护 */
    private static final ThreadLocal<JwtInfo> REQUEST_BASE_INFO_THREAD_LOCAL = new ThreadLocal<>();

    /** 解析jwt信息 */
    public static JwtInfo getJwtInfoFromReq(String requestBodyPayload) {
        var jwtInfo = new JwtInfo();
        try {
            var requestBody = JsonUtil.getJsonNode(requestBodyPayload);
            log.info("[JwtUtil] RequestBody -> {}", requestBody);

            // 解析requestBody,转为jwtInfo对象
          
            jwtInfo.setRequestId(requestBody.get("RequestId") != null ? requestBody.get("RequestId").asText() : "");
            jwtInfo.setRegion(requestBody.get("Region") != null ? requestBody.get("Region").asText() : "");
            log.info("[JwtUtil] JwtInfo -> {}", jwtInfo);
        } catch (Exception e) {
            log.error("[JwtUtil] Parse RequestBodyInfo Error, Error Message -> {}", e.getMessage(), e);
        }
        return jwtInfo;
    }

    /** 获取jwt信息 */
    public static JwtInfo getJwtInfo() {
        var jwtInfo = REQUEST_BASE_INFO_THREAD_LOCAL.get();
        if (Objects.isNull(jwtInfo)) {
            final var requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            if (Objects.nonNull(requestAttributes)) {
                var requestBodyPayload = "";
                try {
                    requestBodyPayload = StreamUtils.copyToString(requestAttributes.getRequest().getInputStream(),
                            StandardCharsets.UTF_8);
                } catch (Exception e) {
                    log.error("[JwtUtil] Parse RequestBodyInfo Error, Error Message -> {}", e.getMessage());
                }
                jwtInfo = getJwtInfoFromReq(requestBodyPayload);
                setJwtInfo(jwtInfo);
            }
        }
        return jwtInfo;
    }

    /** 将jwt信息存入threadLocal中 */
    public static void setJwtInfo(JwtInfo jwtInfo) {
        REQUEST_BASE_INFO_THREAD_LOCAL.set(jwtInfo);
        // 将traceId写入日志变量
        MDC.put("traceId", jwtInfo.getRequestId());
    }

    public static void setJwtInfo(String appId, String ownerUin) {
        var jwtInfo = new JwtUtil.JwtInfo();
        jwtInfo.setRequestId(UUID.randomUUID().toString());

        setJwtInfo(jwtInfo);
    }

    /** 从threadLocal中删除jwt信息 */
    public static void removeJwtInfo() {
        REQUEST_BASE_INFO_THREAD_LOCAL.remove();
    }

    @Data
    public static class JwtInfo {

      
        @JsonPropertyDescription("请求requestId")
        private String requestId;


        @JsonPropertyDescription("请求的Region")
        private String region;
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77

Get the content in jwt and send other http requests:

   public static JsonNode sendHttpRequest(String method, String action, String url, Map<String, Object> body)
            throws IOException, InterruptedException {

        // 设置通用参数
        var jwtInfo = JwtUtil.getJwtInfo();
        if (jwtInfo != null) {
            body.put("RequestId", jwtInfo.getRequestId());
            body.put("AppId", Integer.valueOf(jwtInfo.getAppId()));
            body.put("Uin", jwtInfo.getUin());
            body.put("Region", jwtInfo.getRegion());
        }

        // 设置action
        body.put("Action", action);

        // 发送http请求,拿到请求结果
        HttpConnectUtil.ResponseInfo responseInfo = switch (method) {
            case "GET" -> HttpConnectUtil.sendGetByJson(url, JsonUtil.toJson(body));
            case "POST" -> HttpConnectUtil.sendPost(url, JsonUtil.toJson(body), new HashMap<>(2));
            default -> new HttpConnectUtil.ResponseInfo();
        };

        // 检查Api3格式返回结果,并解析
        var jsonResponse = JsonUtil.getJsonNode(responseInfo.getContent()).get("Response");
        var jsonError = jsonResponse.get("Error");
        if (jsonError != null) {
            var errorCode = jsonError.get("Code").asText();
            var errorMessage = jsonError.get("Message").asText();
            throw new ApiException(ErrorCode.INTERNAL_ERROR,
                    String.format("错误码:[%s],错误信息:[%s]", errorCode, errorMessage));
        }
        return jsonResponse;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33