引言
本文代码已提交至Github(版本号:
a27a585f2c3e71c17d59da1e1ea1480da1e90ba5
),有兴趣的同学可以下载来看看:https://github.com/ylw-github/taodong-shop
在之前博客《淘东电商项目(67) -互联网安全架构设计(方法论)》,主要讲解了互联网安全架构设计的方法,主要介绍了如下几种:
- 基于网关实现IP黑名单与名单拦截
- API接口实现Token授权认证
- 使用MD5实现API接口验证签名,防止抓包篡改数据
- 实现API接口安全加密传输(公钥和私钥互换机制)
- 基于Oauth2.0 实现API接口开放平台
- 接口参数使用网关实现防止XSS、SQL注入
- 定期工具实现代码健康扫描
上面的打勾代码实现在前面博客已经讲解完,本文主要讲解网关代码的逻辑重构。
本文目录结构:
l____引言
l____ 1.建造者模式的缺陷
l____ 2.责任链模式重构代码
l________ 2.1 责任链模式原理
l________ 2.2 代码实现
l____ 3.测试
1.建造者模式的缺陷
在前面的几篇博客,可以知道网络的流程处理使用了建造者模式,代码结构如下,有建造者接口(GatewayBuild
)、建造者实现类(VerificationBuild
)、建造者管理类(GatewayDirector
):
其中建造者实现类的代码如下:
/**
* description: 参数验证
* create by: YangLinWei
* create time: 2020/5/20 9:09 上午
*/
@Slf4j
@Component
public class VerificationBuild implements GatewayBuild {
@Autowired
private BlacklistMapper blacklistMapper;
@Autowired
private AuthorizationServiceFeign verificaCodeServiceFeign;
@Override
public Boolean blackBlock(RequestContext ctx, String ipAddres, HttpServletResponse response) {
// 2.查询数据库黑名单
Blacklist blacklist = blacklistMapper.findBlacklist(ipAddres);
if (blacklist != null) {
resultError(ctx, "ip:" + ipAddres + ",Insufficient access rights");
return false;
}
log.info(">>>>>>ip:{},验证通过>>>>>>>", ipAddres);
// 3.将ip地址传递到转发服务中
response.addHeader("ipAddres", ipAddres);
log.info(">>>>>>ip:{},验证通过>>>>>>>", ipAddres);
return true;
}
@Override
public Boolean toVerifyMap(RequestContext ctx, String ipAddres, HttpServletRequest request) {
// 4.外网传递参数验证
Map<String, String> verifyMap = SignUtil.toVerifyMap(request.getParameterMap(), false);
if (!SignUtil.verify(verifyMap)) {
resultError(ctx, "ip:" + ipAddres + ",Sign fail");
return false;
}
return true;
}
@Override
public Map<String, List<String>> filterParameters(HttpServletRequest request, RequestContext ctx) {
Map<String, List<String>> requestQueryParams = ctx.getRequestQueryParams();
if (requestQueryParams == null) {
requestQueryParams = new HashMap<>();
}
Enumeration em = request.getParameterNames();
while (em.hasMoreElements()) {
String name = (String) em.nextElement();
String value = request.getParameter(name);
ArrayList<String> arrayList = new ArrayList<>();
// 将参数转化为html参数 防止xss攻击 < 转为<
arrayList.add(StringEscapeUtils.escapeHtml(value));
requestQueryParams.put(name, arrayList);
log.info(">>>>>>过滤参数name:{},arrayList:{}>>>>>>>", name, arrayList);
}
return requestQueryParams;
}
@Override
public Boolean apiAuthority(RequestContext ctx, HttpServletRequest request) {
String servletPath = request.getServletPath();
log.info(">>>>>servletPath:" + servletPath + ",servletPath.substring(0, 5):" + servletPath.substring(0, 5));
if (!servletPath.substring(0, 7).equals("/public")) {
return true;
}
String accessToken = request.getParameter("accessToken");
log.info(">>>>>accessToken验证:" + accessToken);
if (StringUtils.isEmpty(accessToken)) {
resultError(ctx, "AccessToken cannot be empty");
return false;
}
// 调用接口验证accessToken是否失效
BaseResponse<JSONObject> appInfo = verificaCodeServiceFeign.getAppInfo(accessToken);
log.info(">>>>>>data:" + appInfo.toString());
if (!isSuccess(appInfo)) {
resultError(ctx, appInfo.getMsg());
return false;
}
return true;
}
private void resultError(RequestContext ctx, String errorMsg) {
ctx.setResponseStatusCode(401);
ctx.setSendZuulResponse(false);
ctx.setResponseBody(errorMsg);
}
// 接口直接返回true 或者false
public Boolean isSuccess(BaseResponse<?> baseResp) {
if (baseResp == null) {
return false;
}
if (!baseResp.getCode().equals(Constants.HTTP_RES_CODE_200)) {
return false;
}
return true;
}
}
可以看到,随着业务的需求增加,如果还要加其它的过滤条件,将会都在这个类里编写,是相当冗余的,因此建造者模式不再适用于当前的网关过滤功能,所以本文将使用“责任链模式”来替代“建造者模式”。
2.责任链模式重构代码
2.1 责任链模式原理
之前有写过过责任链模式的博客,有兴趣的同学可以参阅下:《设计模式17 - 责任链模式【Chain of Responsibility Pattern】》。
对于网关过滤功能,我画出了一张关于责任链模式的原理图:
上图流程描述:首先责任链调用方调用责任链工厂去获取第一个Handler,然后执行黑名单Handler,执行完后,黑名单handler会执行加签验证handler,加签Handler执行完后,加签Handler会执行AccessToken验证handler,依次类推。只要其中的一个handler不同过,其关联的下一个handler都不能执行。
好了,下面直接讲解代码实现。
2.2 代码实现
首先看看代码结构:
①handler接口(GateWayHandler
):
/**
* description: 网关Handler接口
* create by: YangLinWei
* create time: 2020/5/21 3:33 下午
*/
public interface GatewayHandler {
/**
* 网关拦截处理请求
*/
Boolean service(RequestContext ctx, String ipAddres, HttpServletRequest request, HttpServletResponse response);
/**
* 设置下一个
*/
void setNextHandler(GatewayHandler gatewayHandler);
/**
* 获取下一个Handler
*
* @return
*/
public GatewayHandler getNextHandler();
}
②handler实现接口的父接口(BaseHandler
公用方法抽取):
/**
* description: handler基类
* create by: YangLinWei
* create time: 2020/5/21 3:38 下午
*/
public class BaseHandler {
protected GatewayHandler gatewayHandler;
public void setNextHandler(GatewayHandler gatewayHandler) {
this.gatewayHandler = gatewayHandler;
}
public GatewayHandler getNextHandler() {
return gatewayHandler;
}
protected void resultError(RequestContext ctx, String errorMsg) {
ctx.setResponseStatusCode(401);
// 网关响应为false 不会转发服务
ctx.setSendZuulResponse(false);
ctx.setResponseBody(errorMsg);
}
// 接口直接返回true 或者false
public Boolean isSuccess(BaseResponse<?> baseResp) {
if (baseResp == null) {
return false;
}
if (!baseResp.getCode().equals(Constants.HTTP_RES_CODE_200)) {
return false;
}
return true;
}
}
③handler子类实现(以BlackListHandler为例子):
/**
* description: 黑名单处理handler
* create by: YangLinWei
* create time: 2020/5/21 3:37 下午
*/
@Component
@Slf4j
public class BlackListHandler extends BaseHandler implements GatewayHandler {
@Autowired
private BlacklistMapper blacklistMapper;
@Override
public Boolean service(RequestContext ctx, String ipAddres, HttpServletRequest request, HttpServletResponse response) {
// 2.查询数据库黑名单
Blacklist blacklist = blacklistMapper.findBlacklist(ipAddres);
if (blacklist != null) {
resultError(ctx, "ip:" + ipAddres + ",Insufficient access rights");
return Boolean.FALSE;
}
log.info(">>>>>>ip:{},验证通过>>>>>>>", ipAddres);
// 3.将ip地址传递到转发服务中
response.addHeader("ipAddres", ipAddres);
log.info(">>>>>>ip:{},验证通过>>>>>>>", ipAddres);
if (gatewayHandler != null) {
gatewayHandler.service(ctx, ipAddres, request, response);
}
return Boolean.TRUE;
}
}
④责任链链工厂(FactoryHandler
):
public class FactoryHandler {
public static GatewayHandler getHandler() {
// 1.黑名单拦截
GatewayHandler handler1 = (GatewayHandler) SpringContextUtil.getBean("blackListHandler");
// 2.API接口参数接口验签
GatewayHandler handler2 = (GatewayHandler) SpringContextUtil.getBean("verifySignHandler");
handler1.setNextHandler(handler2);
// 3.参数过滤
GatewayHandler handler3 = (GatewayHandler) SpringContextUtil.getBean("filterParamHandler");
handler1.setNextHandler(handler3);
//4.验证accessToken
GatewayHandler handler4 = (GatewayHandler) SpringContextUtil.getBean("apiAuthorityHandler");
handler3.setNextHandler(handler4);
return handler1;
}
}
⑤责任链调用者(ResponsibilityClient
):
@Component
public class ResponsibilityClient {
public void responsibility(RequestContext ctx, String ipAddres, HttpServletRequest request,
HttpServletResponse response) {
GatewayHandler handler = FactoryHandler.getHandler();
handler.service(ctx, ipAddres, request, response);
}
}
3.测试
仿照上一篇博客的测试流程,浏览器先输入错误的accessToken:http://127.0.0.1/public/api-pay/cratePayToken?payAmount=9999&orderId=20200513141452&userId=27&productName=apple&accessToken=11111:
可以看到拒绝访问,浏览器输入正确的token,访问:http://127.0.0.1/public/api-pay/cratePayToken?payAmount=9999&orderId=20200513141452&userId=27&productName=apple&accessToken=authfdc563ec2ec049ea8fc66ab777215bb5,可以看到访问成功。
本文完!