web项目中一般用户信息是通过加密处理放在cookie中的,如果每个需要用户信息的接口都要去cookie中获取然后解密得到用户信息的话就比较麻烦,这里介绍的就是如何避免这种麻烦。
在过滤器中包装httpServletRequest:
public class CsrfFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpServletRequest = (HttpServletRequest) request; Cookie[] cookies = httpServletRequest.getCookies(); Long userId = JwtUtils.getUserIdFromCookie(cookies); HttpServletResponse httpServletResponse = (HttpServletResponse) response; chain.doFilter(new MyHttpServletRequest(httpServletRequest,userId), httpServletResponse); }}
对应的MyHttpServletRequest中的定义如下:
public class MyHttpServletRequestWrapper extends HttpServletRequestWrapper { private Long userId; public MyHttpServletRequestWrapper(HttpServletRequest request, Long userId) { super(request); this.userId = userId; } public MyHttpServletRequestWrapper(HttpServletRequest request) { super(request); } @Override public HttpSession getSession() { return super.getSession(false); } /** * 通过request.getHeader获取 * @param name * @return */ @Override public String getHeader(String name) { if (name.equals("userId")){ return userId.toString(); } return super.getHeader(name); } /** * 与getParameterNames处理后可以直接在controller层直接通过@RequestParam可直接写在接口参数列表中注入。 * @param name * @return */ @Override public String[] getParameterValues(String name) { if (name.equals("userId")){ return new String[]{userId.toString()}; } return super.getParameterValues(name); } @Override public Enumeration<String> getParameterNames() { Set<String> params = new LinkedHashSet<>(); params.add("userId"); Enumeration<String> names = super.getParameterNames(); while (names.hasMoreElements()){ params.add(names.nextElement()); } return Collections.enumeration(params); } @Override public ServletInputStream getInputStream() throws IOException { final ByteArrayInputStream bais = new ByteArrayInputStream( inputHandlers(super.getInputStream()).getBytes()); return new ServletInputStream() { @Override public int read() throws IOException { return bais.read(); } @Override public boolean isFinished() { return false; } @Override public boolean isReady() { return true; } @Override public void setReadListener(ReadListener readListener) { } }; } /** * 针对json请求 * @param servletInputStream * @return */ public String inputHandlers(ServletInputStream servletInputStream) { StringBuilder sb = new StringBuilder(); BufferedReader reader = null; try { reader = new BufferedReader(new InputStreamReader(servletInputStream, Charset.forName("UTF-8"))); String line = ""; while ((line = reader.readLine()) != null) { sb.append(line); } } catch (IOException e) { e.printStackTrace(); } finally { if (servletInputStream != null) { try { servletInputStream.close(); } catch (IOException e) { e.printStackTrace(); } } if (reader != null) { try { reader.close(); } catch (IOException e) { e.printStackTrace(); } } } JSONObject jsonObject = JSONObject.parseObject(sb.toString()); jsonObject.put("userId",userId); return jsonObject.toJSONString(); }}
这样无论get还是post还是json请求,在controller层就能通过接口中参数注入的方式都能直接获取用户信息了。
//需要注入用户信息的参数级别的注解@Target(ElementType.PARAMETER) // 可用在方法的参数上@Retention(RetentionPolicy.RUNTIME) // 运行时有效public @interface CurrentUser {}//需要用户登录的方法级别的注解@Documented@Inherited@Target({ElementType.METHOD,ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)public @interface AuthPassport { boolean validate() default true; /** * true时,代表接口可登录可不登录 * @return */ boolean noNeed() default false;}
public class CurrentUserMethodArgumentResolver implements HandlerMethodArgumentResolver { @Override public boolean supportsParameter(MethodParameter parameter) { return parameter.getParameterType().isAssignableFrom(UserBaseTo.class) && parameter.hasParameterAnnotation(CurrentUser.class); } @Override public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) { UserBaseTo user = null; if (parameter.getParameterType().isAssignableFrom(UserBaseTo.class) && parameter.hasParameterAnnotation(CurrentUser.class)) { user = (UserBaseTo) webRequest.getAttribute(BaseGlobalConstants.CURRENT_USER, RequestAttributes.SCOPE_REQUEST); } return user; }}
这样在controller中就能以下面这种方式获取用户信息了:
@RequestMapping(value = "/query", method = RequestMethod.GET) @BrowseRecord() @AuthPassport(noNeed = true) public ResultVo<Object> get(@PathVariable String origin, @ApiIgnore @CurrentUser UserBaseTo userBaseTo, Long contentId,Integer isFree) { }
这种方式就不需要多讲了,主要通过在请求进入时将用户信息放入当前io线程的threadLocal中携带过来,然后在下层就能从threadLocal中去获取了。在一些使用了线程池隔离技术来处理请求的场景,这种方式是行不通的, 比如使用了hystrix。在使用线程池的场景中一个要求就是,每个线程处理完之后,需要清空threadLocal中的信息以便线程复用。
以前在做dubbo + hystrix + zipkin时,配置dubbo的filter时,如果将hystrixFilter配置在zipkinFilter前面就会有问题,因为zipkin中的统计信息是通过threadLocal来传递的。
如果想解决这个问题,可以看下阿里开源的一个组件:https://github.com/alibaba/transmittable-thread-local。