package com.insight.gateway.filter; import com.insight.gateway.common.Verify; import com.insight.utils.*; import com.insight.utils.pojo.InterfaceDto; import com.insight.utils.pojo.LoginInfo; import com.insight.utils.pojo.Reply; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.core.Ordered; import org.springframework.core.io.buffer.DataBuffer; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; import java.time.LocalDateTime; import java.util.*; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; /** * @author 宣炳刚 * @date 2019-08-29 * @remark 身份验证及鉴权过滤器 */ @Component public class AuthFilter implements GlobalFilter, Ordered { private LocalDateTime flagTime = LocalDateTime.now(); private List<InterfaceDto> regConfigs = new ArrayList<>(); private Map<String, InterfaceDto> hashConfigs = new HashMap<>(); private final Logger logger = LoggerFactory.getLogger(this.getClass()); private static final String COUNT_START_TIME = "StartTime"; /** * 令牌持有人信息 */ private LoginInfo loginInfo; /** * 验证结果 */ private Reply reply; /** * 身份验证及鉴权过滤器 * * @param exchange ServerWebExchange * @param chain GatewayFilterChain * @return Mono */ @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { ServerHttpRequest request = exchange.getRequest(); HttpMethod method = request.getMethod(); String path = request.getPath().value(); InterfaceDto config = getConfig(method, path); if (config == null) { reply = ReplyHelper.fail("请求的URL不存在"); return initResponse(exchange); } // 验证及鉴权 String key = method + ":" + path; HttpHeaders headers = request.getHeaders(); String fingerprint = headers.getFirst("fingerprint"); Boolean isLogResult = config.getLogResult(); exchange.getAttributes().put("logResult", isLogResult == null ? false : isLogResult); if (!config.getVerify()) { return isLimited(config, fingerprint, key) ? initResponse(exchange) : chain.filter(exchange); } String token = headers.getFirst("Authorization"); boolean isVerified = verify(token, fingerprint, config.getAuthCode()); if (!isVerified || isLimited(config, fingerprint, key)) { return initResponse(exchange); } // 验证提交数据临时Token if (config.getNeedToken()) { String redisKey = "SubmitToken:" + Util.md5(loginInfo.getUserId() + ":" + key); String submitToken = headers.getFirst("SubmitToken"); String id = Redis.get(redisKey); if (id == null || id.isEmpty() || !id.equals(submitToken)) { reply = ReplyHelper.fail("SubmitToken不存在"); return initResponse(exchange); } else { Redis.deleteKey(redisKey); } } request.mutate().header("loginInfo", loginInfo.toString()).build(); return chain.filter(exchange); } /** * 获取过滤器序号 * * @return 过滤器序号 */ @Override public int getOrder() { return 1; } /** * 验证用户令牌并鉴权 * * @param token 令牌 * @param fingerprint 用户特征串 * @param authCode 接口授权码 * @return 是否通过验证 */ private boolean verify(String token, String fingerprint, String authCode) { if (token == null || token.isEmpty()) { reply = ReplyHelper.invalidToken(); return false; } Verify verify = new Verify(token, fingerprint); reply = verify.compare(authCode); if (!reply.getSuccess()) { return false; } loginInfo = Json.clone(verify, LoginInfo.class); return true; } /** * 是否被限流 * * @param config 接口配置表 * @param fingerprint 用户特征串 * @param key 键值 * @return 是否被限流 */ private boolean isLimited(InterfaceDto config, String fingerprint, String key) { if (!config.getLimit() || key == null || key.isEmpty()) { return false; } Integer gap = config.getLimitGap(); if (gap == null || gap.equals(0)) { return false; } String limitKey = Util.md5(fingerprint + "|" + key); if (isLimited(limitKey, gap)) { return true; } Integer cycle = config.getLimitCycle(); if (cycle == null || cycle.equals(0)) { return false; } Integer max = config.getLimitMax(); if (max == null || max.equals(0)) { return false; } return isLimited(limitKey, cycle, max, config.getMessage()); } /** * 是否被限流(访问间隔小于最小时间间隔) * * @param key 键值 * @param gap 访问最小时间间隔 * @return 是否限制访问 */ private boolean isLimited(String key, Integer gap) { key = "Surplus:" + key; String val = Redis.get(key); if (val == null || val.isEmpty()) { Redis.set(key, DateHelper.getDateTime(), gap, TimeUnit.SECONDS); return false; } Date time = DateHelper.parseDateTime(val); long bypass = System.currentTimeMillis() - Objects.requireNonNull(time).getTime(); // 调用时间间隔低于1秒时,重置调用时间为当前时间作为惩罚 if (bypass < 1000) { Redis.set(key, DateHelper.getDateTime(), gap, TimeUnit.SECONDS); } reply = ReplyHelper.tooOften(); return true; } /** * 是否被限流(限流计时周期内超过最大访问次数) * * @param key 键值 * @param cycle 限流计时周期(秒) * @param max 限制次数/限流周期 * @param msg 消息 * @return 是否限制访问 */ private Boolean isLimited(String key, Integer cycle, Integer max, String msg) { key = "Limit:" + key; String val = Redis.get(key); if (val == null || val.isEmpty()) { Redis.set(key, "1", cycle, TimeUnit.SECONDS); return false; } // 读取访问次数,如次数超过限制,返回true,否则访问次数增加1次 int count = Integer.parseInt(val); if (count > max) { reply = ReplyHelper.tooOften(msg); return true; } count++; long expire = Redis.getExpire(key, TimeUnit.MILLISECONDS); Redis.set(key, Integer.toString(count), expire, TimeUnit.MILLISECONDS); return false; } /** * 生成返回数据 * * @param exchange ServerWebExchange * @return Mono */ private Mono<Void> initResponse(ServerWebExchange exchange) { //设置headers ServerHttpResponse response = exchange.getResponse(); HttpHeaders httpHeaders = response.getHeaders(); httpHeaders.add("Content-Type", "application/json; charset=UTF-8"); httpHeaders.add("Cache-Control", "no-store, no-cache, must-revalidate, max-age=0"); //设置body String json = Json.toJson(reply); logger.warn("返回数据: {}", json); DataBuffer body = response.bufferFactory().wrap(json.getBytes()); Long startTime = exchange.getAttribute(COUNT_START_TIME); if (startTime != null) { long duration = (System.currentTimeMillis() - startTime); logger.info("处理时间: {} ms", duration); } return response.writeWith(Mono.just(body)); } /** * 通过匹配URL获取接口配置 * * @param method 请求方法 * @param url 请求URL * @return 接口配置 */ private InterfaceDto getConfig(HttpMethod method, String url) { // 刷新缓存 LocalDateTime now = LocalDateTime.now(); if (now.isAfter(flagTime.plusSeconds(60))){ flagTime = now; hashConfigs = getHashConfigs(); regConfigs = getRegularConfigs(); } // 先进行哈希匹配 String hash = Util.md5(method.name() + ":" + url); if (hashConfigs.containsKey(hash)) { return hashConfigs.get(hash); } // 哈希匹配失败后进行正则匹配 String path = method + ":" + url; for (InterfaceDto config : regConfigs) { String regular = config.getRegular(); if (path.matches(regular)) { return config; } } // 重载配置进行哈希匹配 hashConfigs = getHashConfigs(); if (hashConfigs.containsKey(hash)) { return hashConfigs.get(hash); } // 重载配置进行正则匹配 regConfigs = getRegularConfigs(); for (InterfaceDto config : regConfigs) { String regular = config.getRegular(); if (path.matches(regular)) { return config; } } return null; } /** * 获取接口配置哈希表 * * @return 接口配置表 */ private Map<String, InterfaceDto> getHashConfigs() { String json = Redis.get("Config:Interface"); List<InterfaceDto> list = Json.toList(json, InterfaceDto.class); Map<String, InterfaceDto> map = new HashMap<>(list.size()); for (InterfaceDto config : list) { String url = config.getUrl(); if (!url.contains("{")) { String hash = Util.md5(config.getMethod() + ":" + config.getUrl()); map.put(hash, config); } } return map; } /** * 获取接口配置正则表 * * @return 接口配置表 */ private List<InterfaceDto> getRegularConfigs() { String json = Redis.get("Config:Interface"); List<InterfaceDto> list = Json.toList(json, InterfaceDto.class); for (InterfaceDto config : list) { String method = config.getMethod(); String url = config.getUrl(); if (url.contains("{")) { // 此正则表达式仅支持UUID作为路径参数,如使用其他类型的参数.请修改正则表达式以匹配参数类型 String regular = method + ":" + url.replaceAll("/\\{[a-zA-Z]+}", "/[0-9a-f]{32}"); config.setRegular(regular); } } return list.stream().filter(i -> i.getRegular() != null).collect(Collectors.toList()); } }