SpringBoot中通过HttpServletRequestWrapper修改request

728 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第3天,点击查看活动详情

背景

    最近公司开始搞产品整合,目标是把每个产品当成一个服务接入,成为一个产品体系。经过初步了解,我了解了公司的微服务体系的架构大致如下图:

arch.png

    前端登录之后通过token与后端进行交互,在网关出调用授权服务进行鉴权,如果鉴权通过则在请求头上加上用户信息,并由网关将请求转发到对应的后台服务。

    这样原本是正常的逻辑,但是真正接入的时候发现如果我需要在接口中获取用户信息就必须要传入HttpServletRequest才能取到request的请求头以获取当前登录的用户信息,而这种需要获取用户信息的接口又非常多,所以需要改造适配的范围就十分大了,这改起来可真是项体力活。

    有没有全局统一处理的方法呢?经过一番百度谷歌,终于了解到可以拦截request并通过HttpServletRequestWrapper修改request信息的,那我不是可以通过这个思路将request中的header头取出来,然后放到request的body中就可以了。正好我的服务中的所有requestbody入参都是继承了一个baseparam的,我只需要在baseparam中加入一个user属性,然后将请求头中的user取出来并设置到这个baseparam中就可以将用户信息传到所有接口中了。话不多说,开干!

gan.png

方案

  1. 继承HttpServletRequestWrapper实现request的读取和修改
@Slf4j
public class UserHttpServletRequestWrapper extends HttpServletRequestWrapper {

    private byte[] body;

    public UserHttpServletRequestWrapper(HttpServletRequest request) {
        super(request);
        String bodyStr = getBodyString(request);
        body = bodyStr.getBytes(Charset.defaultCharset());
    }

    /**
     * 获取请求Body
     * @param request request
     * @return String
     */
    public String getBodyString(final HttpServletRequest request) {
        try {
            String baseUserId = "";
            String userInfoStr = request.getHeader("user");
            if (StringUtils.isNotBlank(userInfoStr)) {
                JSONObject user = JSONObject.parseObject(userInfoStr);
                if (Objects.nonNull(user)) {
                    baseUserId = user.getString("id");
                }
            }
            return inputStream2String(request.getInputStream(), baseUserId);
        } catch (IOException e) {
            log.error("", e);
            throw new RuntimeException(e);
        }
    }

    /**
     * 将inputStream里的数据读取出来并转换成字符串
     *
     * @param inputStream inputStream
     * @return String
     */
    private String inputStream2String(InputStream inputStream, String baseUserId) {
        StringBuilder sb = new StringBuilder();
        BufferedReader reader = null;

        try {
            reader = new BufferedReader(new InputStreamReader(inputStream, Charset.defaultCharset()));
            String line;
            while ((line = reader.readLine()) != null) {
                sb.append(line);
            }
        } catch (IOException e) {
            log.error("", e);
            throw new RuntimeException(e);
        } finally {
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException e) {
                    log.error("", e);
                }
            }
        }
        JSONObject jsonObject = JSONObject.parseObject(sb.toString());
        if (jsonObject != null) {
            UserBaseDto userBaseDto = new UserBaseDto();
            userBaseDto.setUserId(Integer.valueOf(baseUserId));
            jsonObject.put("userBaseDto", userBaseDto);
            return jsonObject.toJSONString();
        }
        return sb.toString();
    }

    @Override
    public BufferedReader getReader() throws IOException {
        return new BufferedReader(new InputStreamReader(getInputStream()));
    }

    @Override
    public ServletInputStream getInputStream() {
        final ByteArrayInputStream inputStream = new ByteArrayInputStream(body);
        return new ServletInputStream() {
            @Override
            public int read() throws IOException {
                return inputStream.read();
            }
            @Override
            public boolean isFinished() {
                return false;
            }
            @Override
            public boolean isReady() {
                return false;
            }
            @Override
            public void setReadListener(ReadListener readListener) {
            }
        };
    }
}
  1. 增加filter用于调用HttpServletRequestWrapper来修改request
@Slf4j
public class AddUserInfoFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        log.info("AddUserInfoFilter init...");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        ServletRequest requestWrapper = null;
        //获取请求中的流,将取出来的字符串,再次转换成流,然后把它放入到新request对象中,
        if (request instanceof HttpServletRequest) {
            requestWrapper = new UserHttpServletRequestWrapper((HttpServletRequest) request);
        }
        //在chain.doFiler方法中传递新的request对象
        if (requestWrapper == null) {
            chain.doFilter(request, response);
        } else {
            chain.doFilter(requestWrapper, response);
        }
    }

    @Override
    public void destroy() {
        log.info("AddUserInfoFilter destroy...");
    }

}

3.注册filter

@Configuration
public class UserFilterConfig {

    /**
     * 注册filter
     * @return
     */
    @Bean
    public FilterRegistrationBean filterRegistration() {
        FilterRegistrationBean<Filter> registration = new FilterRegistrationBean<>();
        registration.setFilter(addUserInfoFilter());
        registration.addUrlPatterns("/*");
        registration.setName("addUserInfoFilter");
        return registration;
    }

    /**
     * addUserInfoFilter
     * @return Filter
     */
    @Bean
    public Filter addUserInfoFilter() {
        return new AddUserInfoFilter();
    }
}

4.通过接口测试即可以发现,所有接口的requestbody中均有了子属性userBaseDto,其中可包含当前登录用户的信息,我们即可在业务层进行使用了。

思考

1、遇到问题不能简单直线地想问题,面对这个问题如果简单粗暴地去每个接口中加request.getheader那不知道要浪费多少时间,有时候想方设法偷懒也是一种优秀的品质。

2、做设计时一定要多思考多积累,在此场景中如果当初没有让所有的request均继承baseparam,那么后续要统一拓展参数就要一个个加,那是一项不小的工作。