zuul是netflix开源的一个API Gateway 服务器, 本质上是一个web servlet应用,Zuul 在云平台上提供动态路由,监控,弹性,安全等边缘服务的框架,Zuul 相当于是设备和 Netflix 流应用的 Web 网站后端所有请求的前门。zuul的核心是一系列的filters, 其作用可以类比Servlet框架的Filter,或者AOP。
在基于 springcloud 构建的微服务系统中,通常使用网关zuul来进行一些用户验证等过滤的操作,比如 用户在 header 或者 url 参数中存放了 token ,网关层需要 用该 token 查出用户 的 userId ,并存放于 request 中,以便后续微服务可以直接使用而避免再去用 token 查询。
在这里,使用zuul的过滤器对请求参数验签(解密),然后发给后续的微服务。
共三个服务:注册中心,zuul服务,通过zuul能访问到的服务。
流程:zuul服务和另一个服务注册到注册中心上,带有加密过得参数的请求url经过zuul处理参数解密之后发给后续微服务。
首先获取到request,但是在request中只有getParameter()而没有setParameter()方法,所以直接修改url参数不可行,另外在request中虽然可以setAttribute(),但是可能由于作用域(request)的不同,一台服务器才能getAttribute()出来,在这里设置的Attribute在后续的微服务中是获取不到的,因此必须考虑另外的方式:get方法和其他方法处理方式不同,post和put需重写HttpServletRequestWrapper,即获取请求的输入流,重写json参数,传入重写构造上下文中的request中。
zuul中的filter代码
import com.example.zuuldemo.util.AESUtil; import com.netflix.zuul.ZuulFilter; import com.netflix.zuul.context.RequestContext; import com.netflix.zuul.http.ServletInputStreamWrapper; import net.sf.json.JSONObject; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import org.springframework.util.StreamUtils; import javax.servlet.ServletInputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import java.io.IOException; import java.io.InputStream; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * 处理请求参数filter * * @author :liuqi * @date :2018-08-29 14:11. */ @Component public class SignFilter extends ZuulFilter { private static Logger log = LoggerFactory.getLogger(SignFilter.class); /** * pre:路由之前 * routing:路由之时 * post: 路由之后 * error:发送错误调用 * * @return */ @Override public String filterType() { return "pre"; } /** * filterOrder:过滤的顺序 * * @return */ @Override public int filterOrder() { return 0; } /** * shouldFilter:这里可以写逻辑判断,是否要过滤,本文true,永远过滤 * * @return */ @Override public boolean shouldFilter() { return true; } /** * run:过滤器的具体逻辑。 * 要把请求参数进行验签(解密)之后传给后续的微服务,首先获取到request,但是在request中只有getParameter()而没有setParameter()方法 * 所以直接修改url参数不可行,另外在reqeust中虽然可以使用setAttribute(),但是可能由于作用域(request)的不同,一台服务器中才能getAttribute * 在这里设置的attribute在后续的微服务中是获取不到的,因此必须考虑另外的方式:即获取请求的输入流,并重写,即重写json参数, * ctx.setRequest(new HttpServletRequestWrapper(request) {}),这种方式可重新构造上下文中的request * * @return */ @Override public Object run() { // 获取到request RequestContext ctx = RequestContext.getCurrentContext(); HttpServletRequest request = ctx.getRequest(); // 获取请求参数name String name = ""; try { // 请求方法 String method = request.getMethod(); log.info(String.format("%s >>> %s", method, request.getRequestURL().toString())); // 获取请求的输入流 InputStream in = request.getInputStream(); String body = StreamUtils.copyToString(in, Charset.forName("UTF-8")); // 如果body为空初始化为空json if (StringUtils.isBlank(body)) { body = "{}"; } log.info("body" + body); // 转化成json JSONObject json = JSONObject.fromObject(body); // get方法和post、put方法处理方式不同 if ("GET".equals(method)) { // 获取请求参数name name = request.getParameter("name"); if (name != null) { // 关键步骤,一定要get一下,下面才能取到值requestQueryParams request.getParameterMap(); Map<String, List<String>> requestQueryParams = ctx.getRequestQueryParams(); if (requestQueryParams == null) { requestQueryParams = new HashMap<>(); } List<String> arrayList = new ArrayList<>(); String key = "key"; String aes_decodedStr = AESUtil.getInstance().decode(name, key); arrayList.add(aes_decodedStr + ""); requestQueryParams.put("decodename", arrayList); ctx.setRequestQueryParams(requestQueryParams); } }// post和put需重写HttpServletRequestWrapper else if ("POST".equals(method) || "PUT".equals(method)) { // 获取请求参数name name = json.getString("name"); if (name != null) { String key = "key"; // String aes_encodedStr = AESUtil.getInstance().encode(name, key); // log.info("加密:" + aes_encodedStr); // json.put("decodename", aes_decodedStr); String aes_decodedStr = AESUtil.getInstance().decode(name, key); log.info("解密:" + aes_decodedStr); // 把解密之后的参数放到json里 json.put("decodename", aes_decodedStr); String newBody = json.toString(); log.info("newBody" + newBody); final byte[] reqBodyBytes = newBody.getBytes(); // 重写上下文的HttpServletRequestWrapper ctx.setRequest(new HttpServletRequestWrapper(request) { @Override public ServletInputStream getInputStream() throws IOException { return new ServletInputStreamWrapper(reqBodyBytes); } @Override public int getContentLength() { return reqBodyBytes.length; } @Override public long getContentLengthLong() { return reqBodyBytes.length; } }); } } } catch (IOException e) { e.printStackTrace(); } return null; } }
后续服务获取到解密后的参数
import net.sf.json.JSONObject; import org.apache.commons.lang.StringUtils; import org.springframework.util.StreamUtils; import org.springframework.web.bind.annotation.*; import javax.servlet.http.HttpServletRequest; import java.io.IOException; import java.io.InputStream; import java.nio.charset.Charset; import java.util.Map; /** * 接收经过zuul处理(解密)的参数,并返回 * * @author :liuqi * @date :2018-08-29 12:12. */ @RestController public class HiController { /** * get方式 * @RequestParam注解方式 * * @param decodename * @return */ @GetMapping("/hi") public String getName(@RequestParam("decodename") String decodename){ return decodename; } /** * post方式 * @RequestBody注解方式获取 * * @param param * @return */ @PostMapping("/hello") public String postName(@RequestBody Map<String,String> param){ String name = param.get("decodename"); return name; } /** * post方式 * 获取请求的输入流,并转化成json * * @param request * @return */ @PostMapping("/hello1") public String postName1(HttpServletRequest request){ String name = ""; try { InputStream in = request.getInputStream(); String body = StreamUtils.copyToString(in, Charset.forName("UTF-8")); if(StringUtils.isNotBlank(body)){ JSONObject jsonObject = JSONObject.fromObject(body); name = (String)jsonObject.get("decodename"); } } catch (IOException e) { e.printStackTrace(); } return name; } /** * post方式 * @RequestBody注解方式获取 * * @param param * @return */ @PutMapping("/howareyou") public String putName(@RequestBody Map<String,String> param){ String name = param.get("decodename"); return name; }
get请求测试
地址:http://localhost:1112/api-a/hi?name=5A0B6501B76A82FCAE5FC26DB2583B0D
post请求测试
http://localhost:1112/api-a/hello
put请求测试
地址:http://localhost:1112/api-a/howareyou
代码地址:https://github.com/yuki9467/zuul-handlerequest-demo