背景

最近有个想法,想把通过 JWT 获取的用户 userId 直接手动塞到 request parameter 中,在 controller 层直接通过 @RequestParam 注解获取,减少调用次数,或者直接读 Redis 把整个用户信息塞进去,于是开始尝试

刚开始,我定位到 @RequestParam注解读取的是 request.getParameterMap() 中的值,所以我进行了以下尝试:

我拦截到 request 并 put 一个值到这个 Map, 然后 Controller 进行调用。

request.getParameterMap().put("name", new String[]{"value1", "value2"});

但是并不行,这个 Map 在返回之前被设置成了不可修改的类型, 所以会报如下错误:

java.lang.IllegalStateException: Cannot find message associated with key parameterMap. 
          locked at org.apache.catalina.util.ParameterMap.put(ParameterMap.java:213)

所以,我们必须绕过这层限制。

如何做

有两个方法可以:

  • 第一个是把这个Map 拿出来,修改不可改变类型并添加一个值,然后想办法塞回去
  • 另一种就是创建一个新的 Request 接收代理,然后修改完后返回新的 Request

第一个觉得太麻烦了,并且绕过他的限制可能会出现安全问题,所以采用第二种。

步骤一:定义新的 Request

首先我们先自定义一个 Request

import java.util.Collections;
import java.util.Enumeration;
import java.util.Map;
import java.util.TreeMap;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;

public class EnhancedHttpRequest extends HttpServletRequestWrapper
{
    private final Map<String, String[]> modifiableParameters;
    private Map<String, String[]> allParameters = null;

    /**
     * Create a new request wrapper that will merge additional parameters into
     * the request object without prematurely reading parameters from the
     * original request.
     * 
     * @param request
     * @param additionalParams
     */
    public EnhancedHttpRequest (final HttpServletRequest request, 
                                                    final Map<String, String[]> additionalParams)
    {
        super(request);
        modifiableParameters = new TreeMap<>();
        modifiableParameters.putAll(additionalParams);
    }

    @Override
    public String getParameter(final String name)
    {
        String[] strings = getParameterMap().get(name);
        if (strings != null)
        {
            return strings[0];
        }
        return super.getParameter(name);
    }

    @Override
    public Map<String, String[]> getParameterMap()
    {
        if (allParameters == null)
        {
            allParameters = new TreeMap<>();
            allParameters.putAll(super.getParameterMap());
            allParameters.putAll(modifiableParameters);
        }
        //Return an unmodifiable collection because we need to uphold the interface contract.
        return Collections.unmodifiableMap(allParameters);
    }

    @Override
    public Enumeration<String> getParameterNames()
    {
        return Collections.enumeration(getParameterMap().keySet());
    }

    @Override
    public String[] getParameterValues(final String name)
    {
        return getParameterMap().get(name);
    }
}

步骤二:新增过滤器

然后,找一个新建一个过滤器,然后加载它

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;

public class AppendParametersFilter implements Filter {
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        Map<String, String[]> additionalParams = new HashMap<>();
        additionalParams.put("user_id", new String[] {"215258456658"});
        additionalParams.put("account_id", new String[] {"1421823141412"});
        EnhancedHttpRequest enhancedHttpRequest = new EnhancedHttpRequest((HttpServletRequest) request, additionalParams);

        // pass the request along the filter chain
        chain.doFilter(enhancedHttpRequest, response);
    }

    @Override
    public void destroy() {	}
    @Override
    public void init(FilterConfig filterConfig) throws ServletException { }
}

步骤三:加载过滤器,使之生效

由于我用的是 Spring Security 所以加载在了 token 验证后

public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    // 忽略其他配置
    /**
     * Configure security settings
     */
    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity
          .addFilterBefore(new AppendParametersFilter(), ChannelProcessingFilter.class));
          // 忽略其他配置
    }

测试

编写 controller 进行测试

    @GetMapping(value = "/test")
    public ResponseEntity<?> test(@RequestParam(name = "user_id") String userId) {
        System.out.println(userId);
        return ResponseEntity.ok(userId);
    }

可以成功拿到数据

小结

最终成功将数据塞入 request 的 parameter 中,但是由于是 Map 如果前端也传递了同名参数,就会产生覆盖,导致问题。

所以建议还是不要简单使用String 类型,而是封装一个对象进行传输,或者采用 自定义注解的方式进行配置,示例如下:

参考链接:Spring Boot自定义注解获取当前登录用户信息

@RequestMapping(value = "/getUser", method = RequestMethod.GET)
public Map<String, Object> queryUser( @CurrentUser UserInfoVO userInfo) {
    System.out.println(userInfo);
}

参考链接

Safely add / modify Servlet request parameter values

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐