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