- 确保URL唯一性
- 修改前端请求参数
- 后端增加验证RequestId
- 增加签名
- 增强读取Body类
- 修改GatewayFilterConfig
- 测试登录
- 测试查询
- 地址
确保URL唯一性
确保URL唯一性,前端请求中增加UUID,后端存入redis,有效时长为5分钟,5分钟重复提交拒绝服务
修改前端请求参数
后端增加验证RequestId
private String getRequestId(HttpHeaders headers) { List<String> list = headers.get("requestId"); if (CollectionUtils.isEmpty(list)) { throw new IllegalArgumentException(ERROR_MESSAGE); } String requestId = list.get(0); //如果requestId存在redis中直接返回 String temp = redisTemplate.opsForValue().get(requestId); if (StringUtils.isNotBlank(temp)) { throw new IllegalArgumentException(ERROR_MESSAGE); } redisTemplate.opsForValue().set(requestId, requestId, 5, TimeUnit.MINUTES); return requestId; }
增加签名
最后一步,添加签名
前端增加签名
跟前端约定好,json数据按照ASCII升序排序。
登录页面:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>登录页面</title> </head> <body> <h1>登录</h1> <from id="from"> 账号:<input id="username" type="text"/> <br/> 密码:<input id="password" type="password"/> <br/> <input id="btn_login" type="button" value="登录"/> </from> <script src="js/jquery.min.js"></script> <script src="js/jsencrypt.js"></script> <script src="js/md5.min.js"></script> <script type="text/javascript"> var encrypt = new JSEncrypt(); encrypt.setPublicKey("MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCFtTlL61IqIGd+fRLUhJ0MjsqFXFJswCohJ45m51WvbxDPRP3gllW0WChk74D5JEOpMDSWo4C7RfoGlBRNW7kQ6qYGukYZ5jgYpzoT0+gp3on96fQXEyQJysv9xiTPIdmSXXVVj1HAOJw29RbzxIVKUSzzPXvEtXRTtCC1+wkAJQIDAQAB"); $("#btn_login").click(function () { //表单 const username = $("#username").val(); const password = $("#password").val(); const form = {}; form.username = username; form.password = password; //生成签名,也可以加盐 const timestamp = Date.parse(new Date()); const data = JSON.stringify(sort_ASCII(form)); const requestId = getUuid(); const sign = MD5(data + requestId + timestamp); $.ajax({ url: "http://localhost:9000/api/user/login", beforeSend: function (XMLHttpRequest) { XMLHttpRequest.setRequestHeader("timestamp", timestamp); XMLHttpRequest.setRequestHeader("requestId", requestId); XMLHttpRequest.setRequestHeader("sign", sign); }, data: encrypt.encrypt(data), type: "POST", dataType: "json", contentType: "application/json;charset=utf-8", success: function (data) { console.log(data); } }); }); function getUuid() { var s = []; var hexDigits = "0123456789abcdef"; for (var i = 0; i < 32; i++) { s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1); } s[14] = "4"; // bits 12-15 of the time_hi_and_version field to 0010 s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1); // bits 6-7 of the clock_seq_hi_and_reserved to 01 s[8] = s[13] = s[18] = s[23]; var uuid = s.join(""); return uuid; } function sort_ASCII(obj) { var arr = new Array(); var num = 0; for (var i in obj) { arr[num] = i; num++; } var sortArr = arr.sort(); var sortObj = {}; for (var i in sortArr) { sortObj[sortArr[i]] = obj[sortArr[i]]; } return sortObj; } </script> </body> </html>
增强读取Body类
/** * @Description: * @Author: Rosh * @Date: 2021/10/27 11:03 */ public class MyCachedBodyOutputMessage extends CachedBodyOutputMessage { private Map<String, Object> paramMap; private Long dateTimestamp; private String requestId; private String sign; public MyCachedBodyOutputMessage(ServerWebExchange exchange, HttpHeaders httpHeaders) { super(exchange, httpHeaders); } public void initial(Map<String, Object> paramMap, String requestId, String sign, Long dateTimestamp) { this.paramMap = paramMap; this.requestId = requestId; this.sign = sign; this.dateTimestamp = dateTimestamp; } public Map<String, Object> getParamMap() { return paramMap; } public Long getDateTimestamp() { return dateTimestamp; } public String getRequestId() { return requestId; } public String getSign() { return sign; } }
修改GatewayFilterConfig
package com.demo.gateway.config; public class GatewayFilterConfig implements GlobalFilter, Ordered { @Autowired private RedisTemplate<String, String> redisTemplate; private static final String ERROR_MESSAGE = "拒绝服务"; @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { //1 获取时间戳 Long dateTimestamp = getDateTimestamp(exchange.getRequest().getHeaders()); //2 获取RequestId String requestId = getRequestId(exchange.getRequest().getHeaders()); //3 获取签名 String sign = getSign(exchange.getRequest().getHeaders()); //4 如果是登录不校验Token String requestUrl = exchange.getRequest().getPath().value(); AntPathMatcher pathMatcher = new AntPathMatcher(); if (!pathMatcher.match("/user/login", requestUrl)) { String token = exchange.getRequest().getHeaders().getFirst(UserConstant.TOKEN); Claims claim = TokenUtils.getClaim(token); if (StringUtils.isBlank(token) || claim == null) { return FilterUtils.invalidToken(exchange); } } //5 修改请求参数,并获取请求参数 Map<String, Object> paramMap; try { paramMap = updateRequestParam(exchange); } catch (Exception e) { return FilterUtils.invalidUrl(exchange); } //6 获取请求体,修改请求体 ServerRequest serverRequest = ServerRequest.create(exchange, HandlerStrategies.withDefaults().messageReaders()); Mono<String> modifiedBody = serverRequest.bodyToMono(String.class).flatMap(body -> { String encrypt = RSAUtils.decrypt(body, RSAConstant.PRIVATE_KEY); JSONObject jsonObject = JSON.parseObject(encrypt); for (Map.Entry<String, Object> entry : jsonObject.entrySet()) { paramMap.put(entry.getKey(), entry.getValue()); } checkSign(sign, dateTimestamp, requestId, paramMap); return Mono.just(encrypt); }); //创建BodyInserter修改请求体 BodyInserter<Mono<String>, ReactiveHttpOutputMessage> bodyInserter = BodyInserters.fromPublisher(modifiedBody, String.class); HttpHeaders headers = new HttpHeaders(); headers.putAll(exchange.getRequest().getHeaders()); headers.remove(HttpHeaders.CONTENT_LENGTH); //创建CachedBodyOutputMessage并且把请求param加入,初始化校验信息 MyCachedBodyOutputMessage outputMessage = new MyCachedBodyOutputMessage(exchange, headers); outputMessage.initial(paramMap, requestId, sign, dateTimestamp); return bodyInserter.insert(outputMessage, new BodyInserterContext()).then(Mono.defer(() -> { ServerHttpRequestDecorator decorator = new ServerHttpRequestDecorator(exchange.getRequest()) { @Override public Flux<DataBuffer> getBody() { Flux<DataBuffer> body = outputMessage.getBody(); if (body.equals(Flux.empty())) { //验证签名 checkSign(outputMessage.getSign(), outputMessage.getDateTimestamp(), outputMessage.getRequestId(), outputMessage.getParamMap()); } return outputMessage.getBody(); } }; return chain.filter(exchange.mutate().request(decorator).build()); })); } public void checkSign(String sign, Long dateTimestamp, String requestId, Map<String, Object> paramMap) { String str = JSON.toJSONString(paramMap) + requestId + dateTimestamp; String tempSign = Md5Utils.getMD5(str.getBytes()); if (!tempSign.equals(sign)) { throw new IllegalArgumentException(ERROR_MESSAGE); } } /** * 修改前端传的参数 */ private Map<String, Object> updateRequestParam(ServerWebExchange exchange) throws NoSuchFieldException, IllegalAccessException { ServerHttpRequest request = exchange.getRequest(); URI uri = request.getURI(); String query = uri.getQuery(); if (StringUtils.isNotBlank(query) && query.contains("param")) { String[] split = query.split("="); String param = RSAUtils.decrypt(split[1], RSAConstant.PRIVATE_KEY); Field targetQuery = uri.getClass().getDeclaredField("query"); targetQuery.setAccessible(true); targetQuery.set(uri, param); return getParamMap(param); } return new TreeMap<>(); } private Map<String, Object> getParamMap(String param) { Map<String, Object> map = new TreeMap<>(); String[] split = param.split("&"); for (String str : split) { String[] params = str.split("="); map.put(params[0], params[1]); } return map; } private String getSign(HttpHeaders headers) { List<String> list = headers.get("sign"); if (CollectionUtils.isEmpty(list)) { throw new IllegalArgumentException(ERROR_MESSAGE); } return list.get(0); } private Long getDateTimestamp(HttpHeaders httpHeaders) { List<String> list = httpHeaders.get("timestamp"); if (CollectionUtils.isEmpty(list)) { throw new IllegalArgumentException(ERROR_MESSAGE); } long timestamp = Long.parseLong(list.get(0)); long currentTimeMillis = System.currentTimeMillis(); //有效时长为5分钟 if (currentTimeMillis - timestamp > 1000 * 60 * 5) { throw new IllegalArgumentException(ERROR_MESSAGE); } return timestamp; } private String getRequestId(HttpHeaders headers) { List<String> list = headers.get("requestId"); if (CollectionUtils.isEmpty(list)) { throw new IllegalArgumentException(ERROR_MESSAGE); } String requestId = list.get(0); //如果requestId存在redis中直接返回 String temp = redisTemplate.opsForValue().get(requestId); if (StringUtils.isNotBlank(temp)) { throw new IllegalArgumentException(ERROR_MESSAGE); } redisTemplate.opsForValue().set(requestId, requestId, 5, TimeUnit.MINUTES); return requestId; } @Override public int getOrder() { return 80; } }
测试登录
发现验签成功