From b5877f13297e15a672d4a11ba4977ba4a0c8884d Mon Sep 17 00:00:00 2001 From: caojianbin <1910336823> Date: Wed, 20 Mar 2024 14:19:02 +0800 Subject: [PATCH] =?UTF-8?q?=E9=87=8D=E6=9E=84=E5=9B=BD=E9=99=85=E5=8C=96,?= =?UTF-8?q?=E6=9D=83=E9=99=90=E5=BC=80=E5=8F=91,=E7=BD=91=E5=85=B3?= =?UTF-8?q?=E5=BC=80=E5=8F=91=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/EcellAuthApplication.java | 4 - .../auth/config/ExceptionAdviceConfig.java | 9 +- .../auth/controller/AuthController.java | 64 ++++++- .../internationalize/auth/utils/AuthLogic.java | 17 ++ .../internationalize/auth/utils/AuthUtil.java | 19 ++ .../common/core/utils/locale/LocaleUtil.java | 13 +- .../ecell-internationalize-gateway/pom.xml | 98 +++++++++- .../gateway/EcellGatewayApplication.java | 14 ++ .../gateway/config/CaptchaConfig.java | 84 ++++++++ .../gateway/config/GatewayConfig.java | 72 +++++++ .../gateway/config/KaptchaTextCreator.java | 73 +++++++ .../config/properties/CaptchaProperties.java | 43 +++++ .../config/properties/IgnoreWhiteProperties.java | 31 +++ .../gateway/constant/MessagesConstant.java | 109 +++++++++++ .../gateway/constant/RateLimitInfo.java | 99 ++++++++++ .../gateway/filter/AuthFilter.java | 211 +++++++++++++++++++++ .../gateway/filter/BlackListUrlFilter.java | 57 ++++++ .../gateway/filter/CacheRequestFilter.java | 86 +++++++++ .../gateway/handler/GatewayExceptionHandler.java | 58 ++++++ .../src/main/resources/application-dev.yml | 74 ++++++++ .../src/main/resources/bootstrap.yml | 40 ++++ .../src/main/resources/lua/rateLimit.lua | 15 ++ 22 files changed, 1260 insertions(+), 30 deletions(-) create mode 100644 ecell-internationalize/ecell-internationalize-auth/src/main/java/com/ecell/internationalize/auth/utils/AuthLogic.java create mode 100644 ecell-internationalize/ecell-internationalize-auth/src/main/java/com/ecell/internationalize/auth/utils/AuthUtil.java create mode 100644 ecell-internationalize/ecell-internationalize-gateway/src/main/java/com/ecell/internationalize/gateway/EcellGatewayApplication.java create mode 100644 ecell-internationalize/ecell-internationalize-gateway/src/main/java/com/ecell/internationalize/gateway/config/CaptchaConfig.java create mode 100644 ecell-internationalize/ecell-internationalize-gateway/src/main/java/com/ecell/internationalize/gateway/config/GatewayConfig.java create mode 100644 ecell-internationalize/ecell-internationalize-gateway/src/main/java/com/ecell/internationalize/gateway/config/KaptchaTextCreator.java create mode 100644 ecell-internationalize/ecell-internationalize-gateway/src/main/java/com/ecell/internationalize/gateway/config/properties/CaptchaProperties.java create mode 100644 ecell-internationalize/ecell-internationalize-gateway/src/main/java/com/ecell/internationalize/gateway/config/properties/IgnoreWhiteProperties.java create mode 100644 ecell-internationalize/ecell-internationalize-gateway/src/main/java/com/ecell/internationalize/gateway/constant/MessagesConstant.java create mode 100644 ecell-internationalize/ecell-internationalize-gateway/src/main/java/com/ecell/internationalize/gateway/constant/RateLimitInfo.java create mode 100644 ecell-internationalize/ecell-internationalize-gateway/src/main/java/com/ecell/internationalize/gateway/filter/AuthFilter.java create mode 100644 ecell-internationalize/ecell-internationalize-gateway/src/main/java/com/ecell/internationalize/gateway/filter/BlackListUrlFilter.java create mode 100644 ecell-internationalize/ecell-internationalize-gateway/src/main/java/com/ecell/internationalize/gateway/filter/CacheRequestFilter.java create mode 100644 ecell-internationalize/ecell-internationalize-gateway/src/main/java/com/ecell/internationalize/gateway/handler/GatewayExceptionHandler.java create mode 100644 ecell-internationalize/ecell-internationalize-gateway/src/main/resources/application-dev.yml create mode 100644 ecell-internationalize/ecell-internationalize-gateway/src/main/resources/bootstrap.yml create mode 100644 ecell-internationalize/ecell-internationalize-gateway/src/main/resources/lua/rateLimit.lua diff --git a/ecell-internationalize/ecell-internationalize-auth/src/main/java/com/ecell/internationalize/auth/EcellAuthApplication.java b/ecell-internationalize/ecell-internationalize-auth/src/main/java/com/ecell/internationalize/auth/EcellAuthApplication.java index dd4dfe7..37f3c68 100644 --- a/ecell-internationalize/ecell-internationalize-auth/src/main/java/com/ecell/internationalize/auth/EcellAuthApplication.java +++ b/ecell-internationalize/ecell-internationalize-auth/src/main/java/com/ecell/internationalize/auth/EcellAuthApplication.java @@ -1,13 +1,9 @@ package com.ecell.internationalize.auth; - -import com.ecell.internationalize.auth.config.ExceptionAdviceConfig; -import com.ecell.internationalize.common.core.utils.SpringUtils; import com.ecell.internationalize.common.swagger.annotation.EnableCustomSwagger2; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.cloud.openfeign.EnableFeignClients; -import org.springframework.context.ConfigurableApplicationContext; /** * @author borui diff --git a/ecell-internationalize/ecell-internationalize-auth/src/main/java/com/ecell/internationalize/auth/config/ExceptionAdviceConfig.java b/ecell-internationalize/ecell-internationalize-auth/src/main/java/com/ecell/internationalize/auth/config/ExceptionAdviceConfig.java index 49dfcc6..13273e6 100644 --- a/ecell-internationalize/ecell-internationalize-auth/src/main/java/com/ecell/internationalize/auth/config/ExceptionAdviceConfig.java +++ b/ecell-internationalize/ecell-internationalize-auth/src/main/java/com/ecell/internationalize/auth/config/ExceptionAdviceConfig.java @@ -28,13 +28,6 @@ public class ExceptionAdviceConfig { @ExceptionHandler(Exception.class) public AjaxResult exceptionHandler(Exception e) { e.printStackTrace(); - -// Locale locale = LocaleUtil.getLocale(); -// -// MessageSource messageSource = SpringUtils.getBean(MessageSource.class); -// String message = messageSource.getMessage("messages.login.empty", null, locale); - String message = LocaleUtil.getMessage("messages.login.empty"); - System.out.println("获取到的国际化语言是:=="+message); - return AjaxResult.error(message); + return AjaxResult.error(LocaleUtil.getMessage("messages.error")); } } diff --git a/ecell-internationalize/ecell-internationalize-auth/src/main/java/com/ecell/internationalize/auth/controller/AuthController.java b/ecell-internationalize/ecell-internationalize-auth/src/main/java/com/ecell/internationalize/auth/controller/AuthController.java index a3d484c..701ba1e 100644 --- a/ecell-internationalize/ecell-internationalize-auth/src/main/java/com/ecell/internationalize/auth/controller/AuthController.java +++ b/ecell-internationalize/ecell-internationalize-auth/src/main/java/com/ecell/internationalize/auth/controller/AuthController.java @@ -1,8 +1,20 @@ package com.ecell.internationalize.auth.controller; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import com.ecell.internationalize.auth.entity.UserType; +import com.ecell.internationalize.auth.feign.SysUserFeignClient; +import com.ecell.internationalize.auth.service.SysUserLoginService; +import com.ecell.internationalize.auth.utils.AuthUtil; +import com.ecell.internationalize.common.core.domain.UserLogin; +import com.ecell.internationalize.common.core.utils.StringUtils; +import com.ecell.internationalize.common.core.utils.locale.LocaleUtil; +import com.ecell.internationalize.common.core.web.domain.AjaxResult; +import com.ecell.internationalize.common.core.web.domain.R; +import com.ecell.internationalize.common.security.service.TokenService; +import com.ecell.internationalize.common.security.utils.SecurityUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletRequest; /** * @author borui @@ -10,11 +22,49 @@ import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/auth") public class AuthController { + private final Integer FAIL_CODE = 500; + @Autowired + private TokenService tokenServiceImpl; + + @Autowired + private SysUserLoginService sysUserLoginService; + @Autowired + private SysUserFeignClient sysUserFeignClient; + + @PostMapping("login") + public AjaxResult login(@RequestBody UserType userType) { + // 用户登录校验 + R userLogin = sysUserLoginService.login(userType.getUsername(), userType.getPassword()); + if (FAIL_CODE == userLogin.getCode()) { + return AjaxResult.error(userLogin.getMsg()); + } + // 获取登录token + return AjaxResult.success(LocaleUtil.getMessage("messages.success"), tokenServiceImpl.createToken(userLogin.getData())); + } + + @DeleteMapping("logout") + public AjaxResult logout(HttpServletRequest request) { + String token = SecurityUtils.getToken(request); + if (StringUtils.isNotEmpty(token)) { + // 删除用户缓存记录 + AuthUtil.logoutByToken(token); + } + return AjaxResult.success(LocaleUtil.getMessage("messages.success")); + } + + @PostMapping("refresh") + public AjaxResult refresh(HttpServletRequest request) { + UserLogin userLogin = tokenServiceImpl.getUserLogin(request); + if (StringUtils.isNotNull(userLogin)) { + // 刷新令牌有效期 + tokenServiceImpl.refreshToken(userLogin); + return AjaxResult.success(LocaleUtil.getMessage("messages.success")); + } + return AjaxResult.success(LocaleUtil.getMessage("messages.success")); + } @GetMapping("test") - public String test( ){ - int a=1; - int b= a/0; - return "111"; + public UserLogin test(String userName) { + return sysUserFeignClient.queryByUser(userName); } } diff --git a/ecell-internationalize/ecell-internationalize-auth/src/main/java/com/ecell/internationalize/auth/utils/AuthLogic.java b/ecell-internationalize/ecell-internationalize-auth/src/main/java/com/ecell/internationalize/auth/utils/AuthLogic.java new file mode 100644 index 0000000..4f8db46 --- /dev/null +++ b/ecell-internationalize/ecell-internationalize-auth/src/main/java/com/ecell/internationalize/auth/utils/AuthLogic.java @@ -0,0 +1,17 @@ +package com.ecell.internationalize.auth.utils; + +import com.ecell.internationalize.common.core.utils.SpringUtils; +import com.ecell.internationalize.common.security.service.TokenService; + +/** + * @author borui + */ +public class AuthLogic { + public TokenService tokenServiceImpl = SpringUtils.getBean(TokenService.class); + /** + * 会话注销,根据指定Token + */ + public void logoutByToken(String token) { + tokenServiceImpl.delLoginUser(token); + } +} diff --git a/ecell-internationalize/ecell-internationalize-auth/src/main/java/com/ecell/internationalize/auth/utils/AuthUtil.java b/ecell-internationalize/ecell-internationalize-auth/src/main/java/com/ecell/internationalize/auth/utils/AuthUtil.java new file mode 100644 index 0000000..3d19276 --- /dev/null +++ b/ecell-internationalize/ecell-internationalize-auth/src/main/java/com/ecell/internationalize/auth/utils/AuthUtil.java @@ -0,0 +1,19 @@ +package com.ecell.internationalize.auth.utils; + +/** + * @author borui + */ +public class AuthUtil { + /** + * 底层的 AuthLogic 对象 + */ + public static AuthLogic authLogic = new AuthLogic(); + /** + * 会话注销,根据指定Token + * + * @param token 指定token + */ + public static void logoutByToken(String token) { + authLogic.logoutByToken(token); + } +} diff --git a/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-core/src/main/java/com/ecell/internationalize/common/core/utils/locale/LocaleUtil.java b/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-core/src/main/java/com/ecell/internationalize/common/core/utils/locale/LocaleUtil.java index 3a76b94..856c419 100644 --- a/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-core/src/main/java/com/ecell/internationalize/common/core/utils/locale/LocaleUtil.java +++ b/ecell-internationalize/ecell-internationalize-common/ecell-internationalize-core/src/main/java/com/ecell/internationalize/common/core/utils/locale/LocaleUtil.java @@ -13,7 +13,7 @@ import java.util.Locale; */ public class LocaleUtil { - public static Locale getLocale(){ + public static Locale getLocale() { String language = ServletUtils.getHeader(ServletUtils.getRequest(), "content-language"); Locale locale; @@ -21,19 +21,18 @@ public class LocaleUtil { locale = Locale.US; } else { try { - String [] split = language.split("_"); + String[] split = language.split("_"); locale = new Locale(split[0], split[1]); } catch (Exception e) { locale = Locale.US; } } - return locale; + return locale; } - public static String getMessage(String code) { - Locale locale = getLocale(); - return SpringUtils.getBean(MessageSource.class).getMessage(code, null, locale); + public static String getMessage(String code) { + return SpringUtils.getBean(MessageSource.class).getMessage(code, null, getLocale()); - } + } } diff --git a/ecell-internationalize/ecell-internationalize-gateway/pom.xml b/ecell-internationalize/ecell-internationalize-gateway/pom.xml index 737adf9..0a3e1d1 100644 --- a/ecell-internationalize/ecell-internationalize-gateway/pom.xml +++ b/ecell-internationalize/ecell-internationalize-gateway/pom.xml @@ -12,9 +12,99 @@ com.ecell.internationalize.gateway ecell-internationalize-gateway - - 8 - 8 - + + + + + org.springframework.cloud + spring-cloud-starter-gateway + + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-discovery + + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-config + + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-sentinel + + + + + com.alibaba.cloud + spring-cloud-alibaba-sentinel-gateway + + + + + com.alibaba.csp + sentinel-datasource-nacos + + + + + org.springframework.boot + spring-boot-starter-actuator + + + + + org.springframework.cloud + spring-cloud-loadbalancer + + + + + com.github.penggle + kaptcha + + + + + com.ecell.internationalize.common.redis + ecell-internationalize-redis + 1.0-SNAPSHOT + + + + + io.springfox + springfox-swagger-ui + ${swagger.fox.version} + + + io.springfox + springfox-swagger2 + ${swagger.fox.version} + + + + + +${project.artifactId} + + + org.springframework.boot + spring-boot-maven-plugin + ${spring-boot.version} + + + + repackage + + + + + + \ No newline at end of file diff --git a/ecell-internationalize/ecell-internationalize-gateway/src/main/java/com/ecell/internationalize/gateway/EcellGatewayApplication.java b/ecell-internationalize/ecell-internationalize-gateway/src/main/java/com/ecell/internationalize/gateway/EcellGatewayApplication.java new file mode 100644 index 0000000..276fff0 --- /dev/null +++ b/ecell-internationalize/ecell-internationalize-gateway/src/main/java/com/ecell/internationalize/gateway/EcellGatewayApplication.java @@ -0,0 +1,14 @@ +package com.ecell.internationalize.gateway; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; + +@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class }) +public class EcellGatewayApplication { + public static void main(String[] args) + { + SpringApplication.run(EcellGatewayApplication.class, args); + System.out.println("(♥◠‿◠)ノ゙ 易赛网关启动成功 ლ(´ڡ`ლ)゙"); + } +} diff --git a/ecell-internationalize/ecell-internationalize-gateway/src/main/java/com/ecell/internationalize/gateway/config/CaptchaConfig.java b/ecell-internationalize/ecell-internationalize-gateway/src/main/java/com/ecell/internationalize/gateway/config/CaptchaConfig.java new file mode 100644 index 0000000..797555a --- /dev/null +++ b/ecell-internationalize/ecell-internationalize-gateway/src/main/java/com/ecell/internationalize/gateway/config/CaptchaConfig.java @@ -0,0 +1,84 @@ +package com.ecell.internationalize.gateway.config; + +import com.google.code.kaptcha.Constants; +import com.google.code.kaptcha.impl.DefaultKaptcha; +import com.google.code.kaptcha.util.Config; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.util.Properties; + + +/** + * @author borui + */ +@Configuration +public class CaptchaConfig { + + @Bean(name = "captchaProducer") + public DefaultKaptcha getKaptchaBean() + { + DefaultKaptcha defaultKaptcha = new DefaultKaptcha(); + Properties properties = new Properties(); + // 是否有边框 默认为true 我们可以自己设置yes,no + properties.setProperty(Constants.KAPTCHA_BORDER, "yes"); + // 验证码文本字符颜色 默认为Color.BLACK + properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_FONT_COLOR, "black"); + // 验证码图片宽度 默认为200 + properties.setProperty(Constants.KAPTCHA_IMAGE_WIDTH, "160"); + // 验证码图片高度 默认为50 + properties.setProperty(Constants.KAPTCHA_IMAGE_HEIGHT, "60"); + // 验证码文本字符大小 默认为40 + properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_FONT_SIZE, "38"); + // KAPTCHA_SESSION_KEY + properties.setProperty(Constants.KAPTCHA_SESSION_CONFIG_KEY, "kaptchaCode"); + // 验证码文本字符长度 默认为5 + properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "4"); + // 验证码文本字体样式 默认为new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize) + properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_FONT_NAMES, "Arial,Courier"); + // 图片样式 水纹com.google.code.kaptcha.impl.WaterRipple 鱼眼com.google.code.kaptcha.impl.FishEyeGimpy 阴影com.google.code.kaptcha.impl.ShadowGimpy + properties.setProperty(Constants.KAPTCHA_OBSCURIFICATOR_IMPL, "com.google.code.kaptcha.impl.ShadowGimpy"); + Config config = new Config(properties); + defaultKaptcha.setConfig(config); + return defaultKaptcha; + } + + @Bean(name = "captchaProducerMath") + public DefaultKaptcha getKaptchaBeanMath() + { + DefaultKaptcha defaultKaptcha = new DefaultKaptcha(); + Properties properties = new Properties(); + // 是否有边框 默认为true 我们可以自己设置yes,no + properties.setProperty(Constants.KAPTCHA_BORDER, "yes"); + // 边框颜色 默认为Color.BLACK + properties.setProperty(Constants.KAPTCHA_BORDER_COLOR, "105,179,90"); + // 验证码文本字符颜色 默认为Color.BLACK + properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_FONT_COLOR, "blue"); + // 验证码图片宽度 默认为200 + properties.setProperty(Constants.KAPTCHA_IMAGE_WIDTH, "160"); + // 验证码图片高度 默认为50 + properties.setProperty(Constants.KAPTCHA_IMAGE_HEIGHT, "60"); + // 验证码文本字符大小 默认为40 + properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_FONT_SIZE, "35"); + // KAPTCHA_SESSION_KEY + properties.setProperty(Constants.KAPTCHA_SESSION_CONFIG_KEY, "kaptchaCodeMath"); + // 验证码文本生成器 + properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_IMPL, "com.ruoyi.gateway.config.KaptchaTextCreator"); + // 验证码文本字符间距 默认为2 + properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_CHAR_SPACE, "3"); + // 验证码文本字符长度 默认为5 + properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "6"); + // 验证码文本字体样式 默认为new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize) + properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_FONT_NAMES, "Arial,Courier"); + // 验证码噪点颜色 默认为Color.BLACK + properties.setProperty(Constants.KAPTCHA_NOISE_COLOR, "white"); + // 干扰实现类 + properties.setProperty(Constants.KAPTCHA_NOISE_IMPL, "com.google.code.kaptcha.impl.NoNoise"); + // 图片样式 水纹com.google.code.kaptcha.impl.WaterRipple 鱼眼com.google.code.kaptcha.impl.FishEyeGimpy 阴影com.google.code.kaptcha.impl.ShadowGimpy + properties.setProperty(Constants.KAPTCHA_OBSCURIFICATOR_IMPL, "com.google.code.kaptcha.impl.ShadowGimpy"); + Config config = new Config(properties); + defaultKaptcha.setConfig(config); + return defaultKaptcha; + } + +} diff --git a/ecell-internationalize/ecell-internationalize-gateway/src/main/java/com/ecell/internationalize/gateway/config/GatewayConfig.java b/ecell-internationalize/ecell-internationalize-gateway/src/main/java/com/ecell/internationalize/gateway/config/GatewayConfig.java new file mode 100644 index 0000000..1c5ca48 --- /dev/null +++ b/ecell-internationalize/ecell-internationalize-gateway/src/main/java/com/ecell/internationalize/gateway/config/GatewayConfig.java @@ -0,0 +1,72 @@ +package com.ecell.internationalize.gateway.config; + +import com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants; +import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule; +import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayParamFlowItem; +import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager; +import com.alibaba.csp.sentinel.slots.block.RuleConstant; +import com.ecell.internationalize.gateway.handler.SentinelFallbackHandler; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import org.springframework.core.io.ClassPathResource; +import org.springframework.data.redis.core.script.DefaultRedisScript; +import org.springframework.scripting.support.ResourceScriptSource; + +import java.util.HashSet; +import java.util.Set; + +/** + * @author borui + */ +@Configuration +public class GatewayConfig { + @Bean + @Order(Ordered.HIGHEST_PRECEDENCE) + public SentinelFallbackHandler sentinelGatewayExceptionHandler() + { + return new SentinelFallbackHandler(); + } +/** + * + * 初始化限流规则 + * */ + // @PostConstruct + public void doInit(){ + initGatewayRules(); + + } + /** + * 硬编码网关限流规则 + */ + private void initGatewayRules() { + Set rules = new HashSet<>(); + + GatewayFlowRule rule = new GatewayFlowRule(); + // 指定限流模式, 根据 route_id 做限流, 默认的模式 + rule.setResourceMode(SentinelGatewayConstants.RESOURCE_MODE_ROUTE_ID); + // 指定 route_id -> service id + rule.setResource("yisai-system"); + GatewayParamFlowItem paramItem = new GatewayParamFlowItem(); + paramItem.setFieldName("appId"); + rule.setParamItem(paramItem); + // 按照 QPS 限流 + rule.setGrade(RuleConstant.FLOW_GRADE_QPS); + // 统计窗口和限流阈值 + rule.setIntervalSec(60); + rule.setCount(3); + + rules.add(rule); + + // 加载到网关中 + GatewayRuleManager.loadRules(rules); + } + @Bean + public DefaultRedisScript limitScript(){ + DefaultRedisScript script = new DefaultRedisScript<>(); + script.setResultType(Long.class); + script.setScriptSource(new ResourceScriptSource(new ClassPathResource("lua\\rateLimit.lua"))); + return script; + } +} diff --git a/ecell-internationalize/ecell-internationalize-gateway/src/main/java/com/ecell/internationalize/gateway/config/KaptchaTextCreator.java b/ecell-internationalize/ecell-internationalize-gateway/src/main/java/com/ecell/internationalize/gateway/config/KaptchaTextCreator.java new file mode 100644 index 0000000..57a7763 --- /dev/null +++ b/ecell-internationalize/ecell-internationalize-gateway/src/main/java/com/ecell/internationalize/gateway/config/KaptchaTextCreator.java @@ -0,0 +1,73 @@ +package com.ecell.internationalize.gateway.config; + +import com.google.code.kaptcha.text.impl.DefaultTextCreator; + +import java.util.Random; + +/** + * @author borui + */ +public class KaptchaTextCreator extends DefaultTextCreator { + private static final String[] CNUMBERS = "0,1,2,3,4,5,6,7,8,9,10".split(","); + + @Override + public String getText() + { + Integer result = 0; + Random random = new Random(); + int x = random.nextInt(10); + int y = random.nextInt(10); + StringBuilder suChinese = new StringBuilder(); + int randomoperands = (int) Math.round(Math.random() * 2); + if (randomoperands == 0) + { + result = x * y; + suChinese.append(CNUMBERS[x]); + suChinese.append("*"); + suChinese.append(CNUMBERS[y]); + } + else if (randomoperands == 1) + { + if (!(x == 0) && y % x == 0) + { + result = y / x; + suChinese.append(CNUMBERS[y]); + suChinese.append("/"); + suChinese.append(CNUMBERS[x]); + } + else + { + result = x + y; + suChinese.append(CNUMBERS[x]); + suChinese.append("+"); + suChinese.append(CNUMBERS[y]); + } + } + else if (randomoperands == 2) + { + if (x >= y) + { + result = x - y; + suChinese.append(CNUMBERS[x]); + suChinese.append("-"); + suChinese.append(CNUMBERS[y]); + } + else + { + result = y - x; + suChinese.append(CNUMBERS[y]); + suChinese.append("-"); + suChinese.append(CNUMBERS[x]); + } + } + else + { + result = x + y; + suChinese.append(CNUMBERS[x]); + suChinese.append("+"); + suChinese.append(CNUMBERS[y]); + } + suChinese.append("=?@" + result); + return suChinese.toString(); + } +} diff --git a/ecell-internationalize/ecell-internationalize-gateway/src/main/java/com/ecell/internationalize/gateway/config/properties/CaptchaProperties.java b/ecell-internationalize/ecell-internationalize-gateway/src/main/java/com/ecell/internationalize/gateway/config/properties/CaptchaProperties.java new file mode 100644 index 0000000..f89255b --- /dev/null +++ b/ecell-internationalize/ecell-internationalize-gateway/src/main/java/com/ecell/internationalize/gateway/config/properties/CaptchaProperties.java @@ -0,0 +1,43 @@ +package com.ecell.internationalize.gateway.config.properties; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.cloud.context.config.annotation.RefreshScope; +import org.springframework.context.annotation.Configuration; + +/** + * @author borui + */ +@Configuration +@RefreshScope +@ConfigurationProperties(prefix = "security.captcha") +public class CaptchaProperties { + /** + * 验证码开关 + */ + private Boolean enabled; + + /** + * 验证码类型(math 数组计算 char 字符) + */ + private String type; + + public Boolean getEnabled() + { + return enabled; + } + + public void setEnabled(Boolean enabled) + { + this.enabled = enabled; + } + + public String getType() + { + return type; + } + + public void setType(String type) + { + this.type = type; + } +} diff --git a/ecell-internationalize/ecell-internationalize-gateway/src/main/java/com/ecell/internationalize/gateway/config/properties/IgnoreWhiteProperties.java b/ecell-internationalize/ecell-internationalize-gateway/src/main/java/com/ecell/internationalize/gateway/config/properties/IgnoreWhiteProperties.java new file mode 100644 index 0000000..754bcf3 --- /dev/null +++ b/ecell-internationalize/ecell-internationalize-gateway/src/main/java/com/ecell/internationalize/gateway/config/properties/IgnoreWhiteProperties.java @@ -0,0 +1,31 @@ +package com.ecell.internationalize.gateway.config.properties; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.cloud.context.config.annotation.RefreshScope; +import org.springframework.context.annotation.Configuration; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author borui + */ +@Configuration +@RefreshScope +@ConfigurationProperties(prefix = "security.ignore") +public class IgnoreWhiteProperties { + /** + * 放行白名单配置,网关不校验此处的白名单 + */ + private List whites = new ArrayList<>(); + + public List getWhites() + { + return whites; + } + + public void setWhites(List whites) + { + this.whites = whites; + } +} diff --git a/ecell-internationalize/ecell-internationalize-gateway/src/main/java/com/ecell/internationalize/gateway/constant/MessagesConstant.java b/ecell-internationalize/ecell-internationalize-gateway/src/main/java/com/ecell/internationalize/gateway/constant/MessagesConstant.java new file mode 100644 index 0000000..99f5762 --- /dev/null +++ b/ecell-internationalize/ecell-internationalize-gateway/src/main/java/com/ecell/internationalize/gateway/constant/MessagesConstant.java @@ -0,0 +1,109 @@ +package com.ecell.internationalize.gateway.constant; + +import com.ecell.internationalize.common.core.utils.StringUtils; +import org.slf4j.LoggerFactory; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.web.server.ServerWebExchange; + +import java.util.List; + +/** + * @author borui + */ +public class MessagesConstant { + private static final org.slf4j.Logger log = LoggerFactory.getLogger(MessagesConstant.class); + /**令牌不能为空*/ + public static final String MESSAGES_TOKE_EMPTY_EN="Token cannot be empty"; + public static final String MESSAGES_TOKE_EMPTY_ZH="令牌不能为空"; + /**令牌已过期或验证不正确*/ + public static final String MESSAGES_TOKE_EMPTY_OR_ERROR_EN="Token expired or incorrectly verified"; + public static final String MESSAGES_TOKE_EMPTY_OR_ERROR_ZH="令牌已过期或验证不正确"; + /**登录状态已过期*/ + public static final String MESSAGES_LOGIN_STATUS_EXPIRED_EN="Login status has expired"; + public static final String MESSAGES_LOGIN_STATUS_EXPIRED_ZH="登录状态已过期"; + /**令牌验证失败*/ + public static final String MESSAGES_TOKE_VERIFICATION_FAILED_EN="Token verification failed"; + public static final String MESSAGES_TOKE_VERIFICATION_FAILED_ZH="令牌验证失败"; + /**appId 不能为空*/ + public static final String MESSAGES_APPID_ISEMPTY_FAILED_EN="appId cannot be empty"; + public static final String MESSAGES_APPID_ISEMPTY_FAILED_ZH="appId 不能为空"; + /**appKey 不能为空*/ + public static final String MESSAGES_APPkEY_ISEMPTY_FAILED_EN="appKey cannot be empty"; + public static final String MESSAGES_APPkEY_ISEMPTY_FAILED_ZH="appKey 不能为空"; + /**请求地址不允许访问*/ + public static final String MESSAGES_REQUEST_ADDRESS_EN="The requested address does not allow access"; + public static final String MESSAGES_REQUEST_ADDRESS_ZH="请求地址不允许访问"; + /**服务未找到*/ + public static final String MESSAGES_SERVICE_NOT_FOUND_EN="Service not found"; + public static final String MESSAGES_SERVICE_NOT_FOUND_ZH="服务未找到"; + /**内部服务器错误*/ + public static final String MESSAGES_INTERNAL_SERVER_ERROR_EN="Internal server error"; + public static final String MESSAGES_INTERNAL_SERVER_ERROR_ZH="内部服务器错误"; + + /**内部服务器错误*/ + public static final String MESSAGES_API_STATE_ERROR_EN="This account has been disabled"; + public static final String MESSAGES_API_STATE_ERROR_ZH="该账户已被禁用"; + + public static final String MESSAGES_API_LIMIT_MAX_ERROR_ZH="当前接口达到最大限流次数"; + public static final String MESSAGES_API_LIMIT_MAX_ERROR_EN="The current interface has reached the maximum current limit times"; + + + public static final Integer APP_ID_CODE=406; + public static final Integer APP_KEY_CODE=407; + public static final Integer STATE_CODE=408; + public static final Integer LIMIT_CODE=409; + + public static final String STRING_ZERO="0"; + public static final String STRING_ONE="1"; + + + /**语言类型 中文*/ + public static final String Language_ZH="zh"; + /**语言类型 英文*/ + public static final String Language_EN="en"; + /**语言类型 key*/ + public static final String HEADER_KEY="yisailanguage"; + + + /** + * 国际化处理 + * @param exchange + * @return + */ + public static String getMessages(ServerWebExchange exchange, String zh_msg, String en_msg) { + //给一个默认值 + String msg = en_msg; + ServerHttpRequest request = exchange.getRequest(); + if (StringUtils.isNotNull(request)) { + if (request.getHeaders().containsKey(MessagesConstant.HEADER_KEY)) { + String zh = exchange.getRequest().getHeaders().getFirst(MessagesConstant.HEADER_KEY); + if (MessagesConstant.Language_ZH.equals(zh)) { + msg = zh_msg; + } else { + msg = en_msg; + } + } else { + List list = request.getHeaders().get("accept-language"); + if (StringUtils.isNotEmpty(list) && list.size() > 0) { + String s = list.get(0).split(",")[0].split("-")[0]; + log.info("获取头部的accept-language值:", request.getHeaders().get("accept-language")); + log.info("获取头部的语言:{}", s); + if (MessagesConstant.Language_ZH.equals(s)) { + msg = zh_msg; + } else { + msg = en_msg; + } + } + + + } + } else { + msg = en_msg; + } + + + + + return msg; + } +} diff --git a/ecell-internationalize/ecell-internationalize-gateway/src/main/java/com/ecell/internationalize/gateway/constant/RateLimitInfo.java b/ecell-internationalize/ecell-internationalize-gateway/src/main/java/com/ecell/internationalize/gateway/constant/RateLimitInfo.java new file mode 100644 index 0000000..6237d33 --- /dev/null +++ b/ecell-internationalize/ecell-internationalize-gateway/src/main/java/com/ecell/internationalize/gateway/constant/RateLimitInfo.java @@ -0,0 +1,99 @@ +package com.ecell.internationalize.gateway.constant; + +import io.swagger.annotations.ApiModelProperty; + +import java.io.Serializable; + +/** + * @author borui + */ +public class RateLimitInfo implements Serializable { + private static final long serialVersionUID=1L; + @ApiModelProperty(example = "APPID") + private Integer appId; + + @ApiModelProperty(example = "APPKEY") + private String appKey; + + @ApiModelProperty(example = "状态(0:禁用 , 1:启用)") + private String status; + + @ApiModelProperty(example = "厂商Id") + private String firmId; + + @ApiModelProperty(example = "标识(1:厂商:2:代理商)") + private String firmFlag; + + @ApiModelProperty(example = "代理商Id") + private String secondFirmId; + + @ApiModelProperty(example = "发起请求秒数(如一分钟多少次,单位为秒)") + private Integer second; + + @ApiModelProperty(example = "发起请求次数(如多少分钟100次)") + private Integer frequency; + + public Integer getAppId() { + return appId; + } + + public void setAppId(Integer appId) { + this.appId = appId; + } + + public String getAppKey() { + return appKey; + } + + public void setAppKey(String appKey) { + this.appKey = appKey; + } + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + + public String getFirmId() { + return firmId; + } + + public void setFirmId(String firmId) { + this.firmId = firmId; + } + + public String getFirmFlag() { + return firmFlag; + } + + public void setFirmFlag(String firmFlag) { + this.firmFlag = firmFlag; + } + + public String getSecondFirmId() { + return secondFirmId; + } + + public void setSecondFirmId(String secondFirmId) { + this.secondFirmId = secondFirmId; + } + + public Integer getSecond() { + return second; + } + + public void setSecond(Integer second) { + this.second = second; + } + + public Integer getFrequency() { + return frequency; + } + + public void setFrequency(Integer frequency) { + this.frequency = frequency; + } +} diff --git a/ecell-internationalize/ecell-internationalize-gateway/src/main/java/com/ecell/internationalize/gateway/filter/AuthFilter.java b/ecell-internationalize/ecell-internationalize-gateway/src/main/java/com/ecell/internationalize/gateway/filter/AuthFilter.java new file mode 100644 index 0000000..330b241 --- /dev/null +++ b/ecell-internationalize/ecell-internationalize-gateway/src/main/java/com/ecell/internationalize/gateway/filter/AuthFilter.java @@ -0,0 +1,211 @@ +package com.ecell.internationalize.gateway.filter; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; +import com.ecell.internationalize.common.core.constant.CacheConstants; +import com.ecell.internationalize.common.core.constant.HttpStatus; +import com.ecell.internationalize.common.core.constant.SecurityConstants; +import com.ecell.internationalize.common.core.constant.TokenConstants; +import com.ecell.internationalize.common.core.utils.JwtUtils; +import com.ecell.internationalize.common.core.utils.ServletUtils; +import com.ecell.internationalize.common.core.utils.StringUtils; +import com.ecell.internationalize.common.redis.service.RedisService; +import com.ecell.internationalize.gateway.config.properties.IgnoreWhiteProperties; +import com.ecell.internationalize.gateway.constant.MessagesConstant; +import com.ecell.internationalize.gateway.constant.RateLimitInfo; +import io.jsonwebtoken.Claims; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cloud.gateway.filter.GatewayFilterChain; +import org.springframework.cloud.gateway.filter.GlobalFilter; +import org.springframework.core.Ordered; +import org.springframework.data.redis.core.script.RedisScript; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.stereotype.Component; +import org.springframework.web.server.ServerWebExchange; +import reactor.core.publisher.Mono; + +import java.util.Collections; + +/** + * @author borui + */ +@Component +public class AuthFilter implements GlobalFilter, Ordered { + private static final Logger log = LoggerFactory.getLogger(AuthFilter.class); + private static final String API_URL = "/yisai-system/api/**"; + private static final String RATE_LIMIT = "rate_limit:"; + private static final String HEADER_APPID = "appid"; + private static final String HEADER_APPKEY = "appkey"; + + // 排除过滤的 uri 地址,nacos自行添加 + @Autowired + private IgnoreWhiteProperties ignoreWhite; + + @Autowired + private RedisService redisService; + + @Autowired + RedisScript redisScript; + + + @Override + public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { + ServerHttpRequest request = exchange.getRequest(); + ServerHttpRequest.Builder mutate = request.mutate(); + + String url = request.getURI().getPath(); + if (StringUtils.isMatch(API_URL, url)) { + return getVoidMono(exchange, chain, request, url); + // return chain.filter(exchange); + } + // 跳过不需要验证的路径 + if (StringUtils.matches(url, ignoreWhite.getWhites())) { + return chain.filter(exchange); + } + String token = getToken(request); + if (StringUtils.isEmpty(token)) { + + return unauthorizedResponse(exchange, MessagesConstant.getMessages(exchange, MessagesConstant.MESSAGES_TOKE_EMPTY_ZH, MessagesConstant.MESSAGES_TOKE_EMPTY_EN)); + } + Claims claims = JwtUtils.parseToken(token); + if (claims == null) { + return unauthorizedResponse(exchange, MessagesConstant.getMessages(exchange, MessagesConstant.MESSAGES_TOKE_EMPTY_OR_ERROR_ZH, MessagesConstant.MESSAGES_TOKE_EMPTY_OR_ERROR_EN)); + } + String userkey = JwtUtils.getUserKey(claims); + boolean islogin = redisService.hasKey(getTokenKey(userkey)); + if (!islogin) { + return unauthorizedResponse(exchange, MessagesConstant.getMessages(exchange, MessagesConstant.MESSAGES_LOGIN_STATUS_EXPIRED_ZH, MessagesConstant.MESSAGES_LOGIN_STATUS_EXPIRED_EN)); + } + String userid = JwtUtils.getUserId(claims); + String username = JwtUtils.getUserName(claims); + if (StringUtils.isEmpty(userid) || StringUtils.isEmpty(username)) { + return unauthorizedResponse(exchange, MessagesConstant.getMessages(exchange, MessagesConstant.MESSAGES_TOKE_VERIFICATION_FAILED_ZH, MessagesConstant.MESSAGES_TOKE_VERIFICATION_FAILED_EN)); + } + + // 设置用户信息到请求 + addHeader(mutate, SecurityConstants.USER_KEY, userkey); + addHeader(mutate, SecurityConstants.DETAILS_USER_ID, userid); + addHeader(mutate, SecurityConstants.DETAILS_USERNAME, username); + // 内部请求来源参数清除 + removeHeader(mutate, SecurityConstants.FROM_SOURCE); + return chain.filter(exchange.mutate().request(mutate.build()).build()); + } + + + private void addHeader(ServerHttpRequest.Builder mutate, String name, Object value) { + if (value == null) { + return; + } + String valueStr = value.toString(); + String valueEncode = ServletUtils.urlEncode(valueStr); + mutate.header(name, valueEncode); + } + + private void removeHeader(ServerHttpRequest.Builder mutate, String name) { + mutate.headers(httpHeaders -> httpHeaders.remove(name)).build(); + } + + private Mono unauthorizedResponse(ServerWebExchange exchange, String msg) { + log.error("[鉴权异常处理]请求路径:{}", exchange.getRequest().getPath()); + + return ServletUtils.webFluxResponseWriter(exchange.getResponse(), msg, HttpStatus.UNAUTHORIZED); + } + + private Mono unauthorizedAPIResponse(ServerWebExchange exchange, String msg, int code) { + log.error("[对外接口鉴权异常处理]请求路径:{}", exchange.getRequest().getPath()); + + return ServletUtils.webFluxResponseWriter(exchange.getResponse(), msg, code); + } + + /** + * 获取缓存key + */ + private String getTokenKey(String token) { + return CacheConstants.LOGIN_TOKEN_KEY + token; + } + + /** + * 获取请求token + */ + private String getToken(ServerHttpRequest request) { + String token = request.getHeaders().getFirst(TokenConstants.AUTHENTICATION); + // 如果前端设置了令牌前缀,则裁剪掉前缀 + if (StringUtils.isNotEmpty(token) && token.startsWith(TokenConstants.PREFIX)) { + token = token.replaceFirst(TokenConstants.PREFIX, StringUtils.EMPTY); + } + return token; + } + + private Mono getVoidMono(ServerWebExchange exchange, GatewayFilterChain chain, ServerHttpRequest request, String url) { + Long apiCount; + String appId = request.getHeaders().getFirst(HEADER_APPID); + String appKey = request.getHeaders().getFirst(HEADER_APPKEY); + if (StringUtils.isEmpty(appId)) { + return unauthorizedAPIResponse(exchange, MessagesConstant.getMessages(exchange, MessagesConstant.MESSAGES_APPID_ISEMPTY_FAILED_ZH, MessagesConstant.MESSAGES_APPID_ISEMPTY_FAILED_EN), MessagesConstant.APP_ID_CODE); + } + if (StringUtils.isEmpty(appId)) { + return unauthorizedAPIResponse(exchange, MessagesConstant.getMessages(exchange, MessagesConstant.MESSAGES_APPkEY_ISEMPTY_FAILED_ZH, MessagesConstant.MESSAGES_APPkEY_ISEMPTY_FAILED_EN), MessagesConstant.APP_KEY_CODE); + } + String redisKey = appId + "_" + appKey; + + if (!redisService.hasKey(redisKey)) { + return unauthorizedAPIResponse(exchange, MessagesConstant.getMessages(exchange, MessagesConstant.MESSAGES_INTERNAL_SERVER_ERROR_ZH, MessagesConstant.MESSAGES_INTERNAL_SERVER_ERROR_EN), 500); + } + try { + Object cacheObject = redisService.getCacheObject(redisKey); + String s = JSON.toJSONString(cacheObject); + RateLimitInfo rateLimitInfo = JSONObject.parseObject(s, RateLimitInfo.class); + if (StringUtils.isNotNull(rateLimitInfo)) { + String status = rateLimitInfo.getStatus(); + if (StringUtils.isNotEmpty(status)) { + if (MessagesConstant.STRING_ZERO.equals(status)) { + return unauthorizedAPIResponse(exchange, MessagesConstant.getMessages(exchange, MessagesConstant.MESSAGES_API_STATE_ERROR_ZH, MessagesConstant.MESSAGES_API_STATE_ERROR_EN), MessagesConstant.STATE_CODE); + } + + apiCount = getApiCount(rateLimitInfo, url); + if (apiCount > rateLimitInfo.getFrequency()) { + return unauthorizedAPIResponse(exchange, MessagesConstant.getMessages(exchange, MessagesConstant.MESSAGES_API_LIMIT_MAX_ERROR_ZH, MessagesConstant.MESSAGES_API_LIMIT_MAX_ERROR_EN), MessagesConstant.LIMIT_CODE); + } + } + + } + + } catch (Exception e) { + return unauthorizedAPIResponse(exchange, MessagesConstant.getMessages(exchange, MessagesConstant.MESSAGES_INTERNAL_SERVER_ERROR_ZH, MessagesConstant.MESSAGES_INTERNAL_SERVER_ERROR_EN), 500); + } + + return chain.filter(exchange); + } + + private Long getApiCount(RateLimitInfo info, String url) { + Long current; + int time = 0, count = 0; + String fid; + StringBuffer buffer = new StringBuffer(RATE_LIMIT); + if (MessagesConstant.STRING_ONE.equals(info.getFirmFlag())) { + fid = info.getFirmId(); + } else { + fid = info.getSecondFirmId(); + } + String rateKey = buffer.append(fid).append(url).toString(); + time = info.getSecond(); + if (time <= 0) { + time = 60; + } + count = info.getFrequency(); + if (count <= 0) { + count = 10; + } + + current = (Long) redisService.redisTemplate.execute(redisScript, Collections.singletonList(rateKey), time, count); + + return current; + } + + @Override + public int getOrder() { + return -200; + } +} diff --git a/ecell-internationalize/ecell-internationalize-gateway/src/main/java/com/ecell/internationalize/gateway/filter/BlackListUrlFilter.java b/ecell-internationalize/ecell-internationalize-gateway/src/main/java/com/ecell/internationalize/gateway/filter/BlackListUrlFilter.java new file mode 100644 index 0000000..0b4fa05 --- /dev/null +++ b/ecell-internationalize/ecell-internationalize-gateway/src/main/java/com/ecell/internationalize/gateway/filter/BlackListUrlFilter.java @@ -0,0 +1,57 @@ +package com.ecell.internationalize.gateway.filter; + +import com.ecell.internationalize.common.core.utils.ServletUtils; +import com.ecell.internationalize.gateway.constant.MessagesConstant; +import org.springframework.cloud.gateway.filter.GatewayFilter; +import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Pattern; + +/** + * @author borui + */ +@Component +public class BlackListUrlFilter extends AbstractGatewayFilterFactory { + @Override + public GatewayFilter apply(Config config) { + return (exchange, chain) -> { + + String url = exchange.getRequest().getURI().getPath(); + if (config.matchBlacklist(url)) { + return ServletUtils.webFluxResponseWriter(exchange.getResponse(), MessagesConstant.getMessages(exchange, MessagesConstant.MESSAGES_REQUEST_ADDRESS_ZH, MessagesConstant.MESSAGES_REQUEST_ADDRESS_EN)); + } + + return chain.filter(exchange); + }; + } + + public BlackListUrlFilter() { + super(Config.class); + } + + public static class Config { + private List blacklistUrl; + + private List blacklistUrlPattern = new ArrayList<>(); + + public boolean matchBlacklist(String url) { + return !blacklistUrlPattern.isEmpty() && blacklistUrlPattern.stream().anyMatch(p -> p.matcher(url).find()); + } + + public List getBlacklistUrl() { + return blacklistUrl; + } + + public void setBlacklistUrl(List blacklistUrl) { + this.blacklistUrl = blacklistUrl; + this.blacklistUrlPattern.clear(); + this.blacklistUrl.forEach(url -> { + this.blacklistUrlPattern.add(Pattern.compile(url.replaceAll("\\*\\*", "(.*?)"), Pattern.CASE_INSENSITIVE)); + }); + } + } + +} diff --git a/ecell-internationalize/ecell-internationalize-gateway/src/main/java/com/ecell/internationalize/gateway/filter/CacheRequestFilter.java b/ecell-internationalize/ecell-internationalize-gateway/src/main/java/com/ecell/internationalize/gateway/filter/CacheRequestFilter.java new file mode 100644 index 0000000..72a574c --- /dev/null +++ b/ecell-internationalize/ecell-internationalize-gateway/src/main/java/com/ecell/internationalize/gateway/filter/CacheRequestFilter.java @@ -0,0 +1,86 @@ +package com.ecell.internationalize.gateway.filter; + +import com.alibaba.nacos.shaded.org.checkerframework.checker.units.qual.C; +import org.springframework.cloud.gateway.filter.GatewayFilter; +import org.springframework.cloud.gateway.filter.GatewayFilterChain; +import org.springframework.cloud.gateway.filter.OrderedGatewayFilter; +import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory; +import org.springframework.cloud.gateway.support.ServerWebExchangeUtils; +import org.springframework.http.HttpMethod; +import org.springframework.stereotype.Component; +import org.springframework.web.server.ServerWebExchange; +import reactor.core.publisher.Mono; + +import java.util.Collections; +import java.util.List; + +/** + * @author borui + */ +@Component +public class CacheRequestFilter extends AbstractGatewayFilterFactory { + public CacheRequestFilter() + { + super(Config.class); + } + + @Override + public String name() + { + return "CacheRequestFilter"; + } + + @Override + public GatewayFilter apply(Config config) + { + CacheRequestGatewayFilter cacheRequestGatewayFilter = new CacheRequestGatewayFilter(); + Integer order = config.getOrder(); + if (order == null) + { + return cacheRequestGatewayFilter; + } + return new OrderedGatewayFilter(cacheRequestGatewayFilter, order); + } + + public static class CacheRequestGatewayFilter implements GatewayFilter + { + @Override + public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) + { + // GET DELETE 不过滤 + HttpMethod method = exchange.getRequest().getMethod(); + if (method == null || method == HttpMethod.GET || method == HttpMethod.DELETE) + { + return chain.filter(exchange); + } + return ServerWebExchangeUtils.cacheRequestBodyAndRequest(exchange, (serverHttpRequest) -> { + if (serverHttpRequest == exchange.getRequest()) + { + return chain.filter(exchange); + } + return chain.filter(exchange.mutate().request(serverHttpRequest).build()); + }); + } + } + + @Override + public List shortcutFieldOrder() + { + return Collections.singletonList("order"); + } + + static class Config + { + private Integer order; + + public Integer getOrder() + { + return order; + } + + public void setOrder(Integer order) + { + this.order = order; + } + } +} diff --git a/ecell-internationalize/ecell-internationalize-gateway/src/main/java/com/ecell/internationalize/gateway/handler/GatewayExceptionHandler.java b/ecell-internationalize/ecell-internationalize-gateway/src/main/java/com/ecell/internationalize/gateway/handler/GatewayExceptionHandler.java new file mode 100644 index 0000000..4c99a60 --- /dev/null +++ b/ecell-internationalize/ecell-internationalize-gateway/src/main/java/com/ecell/internationalize/gateway/handler/GatewayExceptionHandler.java @@ -0,0 +1,58 @@ +package com.ecell.internationalize.gateway.handler; + +import com.ecell.internationalize.common.core.utils.ServletUtils; +import com.ecell.internationalize.gateway.constant.MessagesConstant; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler; +import org.springframework.cloud.gateway.support.NotFoundException; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.Order; +import org.springframework.http.server.reactive.ServerHttpResponse; +import org.springframework.web.server.ResponseStatusException; +import org.springframework.web.server.ServerWebExchange; +import reactor.core.publisher.Mono; + +/** + * @author borui + */ +@Order(-1) +@Configuration +public class GatewayExceptionHandler implements ErrorWebExceptionHandler { + private static final Logger log = LoggerFactory.getLogger(GatewayExceptionHandler.class); + + @Override + public Mono handle(ServerWebExchange exchange, Throwable ex) + { + ServerHttpResponse response = exchange.getResponse(); + + if (exchange.getResponse().isCommitted()) + { + return Mono.error(ex); + } + + String msg; + + if (ex instanceof NotFoundException) + { + /**服务未找到*/ + msg = MessagesConstant.getMessages(exchange,MessagesConstant.MESSAGES_SERVICE_NOT_FOUND_ZH,MessagesConstant.MESSAGES_SERVICE_NOT_FOUND_EN); + + } + else if (ex instanceof ResponseStatusException) + { + ResponseStatusException responseStatusException = (ResponseStatusException) ex; + msg = responseStatusException.getMessage(); + } + else + { + /**内部服务器错误*/ + //msg="内部服务器错误"; + msg =MessagesConstant.getMessages(exchange,MessagesConstant.MESSAGES_INTERNAL_SERVER_ERROR_ZH,MessagesConstant.MESSAGES_INTERNAL_SERVER_ERROR_EN); + } + + log.error("[网关异常处理]请求路径:{},异常信息:{}", exchange.getRequest().getPath(), ex.getMessage()); + + return ServletUtils.webFluxResponseWriter(response, msg); + } +} diff --git a/ecell-internationalize/ecell-internationalize-gateway/src/main/resources/application-dev.yml b/ecell-internationalize/ecell-internationalize-gateway/src/main/resources/application-dev.yml new file mode 100644 index 0000000..3ffd078 --- /dev/null +++ b/ecell-internationalize/ecell-internationalize-gateway/src/main/resources/application-dev.yml @@ -0,0 +1,74 @@ +spring: + redis: + host: 120.77.209.176 + port: 6379 + database: 0 + password: Ecell...20201001 + cloud: + gateway: + discovery: + locator: + lowerCaseServiceId: true + enabled: true + routes: + # 认证中心 + - id: ecell-internationalize-auth + uri: lb://ecell-internationalize-auth + predicates: + - Path=/auth/** + filters: + # 验证码处理 + - CacheRequestFilter + - ValidateCodeFilter + - StripPrefix=1 + #易赛系统服务 + - id: yisai-system + uri: lb://yisai-system + predicates: + - Path=/yisai_system/** + filters: + - StripPrefix=1 + + #易赛授权服务 + - id: yisai-system-security + uri: lb://yisai-system-security + predicates: + - Path=/yisai_security/** + filters: + - StripPrefix=1 + #易赛APP服务 + - id: yisai-app + uri: lb://yisai-app + predicates: + - Path=/yisai_app/** + filters: + - StripPrefix=1 + + +# 安全配置 +security: + # 验证码 + captcha: + enabled: true + type: math + # 防止XSS攻击 + xss: + enabled: true + excludeUrls: + - /system/notice + # 不校验白名单 + ignore: + whites: + - /auth/logout + - /auth/login + - /auth/register + - /*/v2/api-docs + - /csrf + - /yisai_auth/*/login + - /yisai_auth/*/logout + - /yisai_api/api/** + - /yisai_system/api/** +cors: + enabled: true + + diff --git a/ecell-internationalize/ecell-internationalize-gateway/src/main/resources/bootstrap.yml b/ecell-internationalize/ecell-internationalize-gateway/src/main/resources/bootstrap.yml new file mode 100644 index 0000000..3861405 --- /dev/null +++ b/ecell-internationalize/ecell-internationalize-gateway/src/main/resources/bootstrap.yml @@ -0,0 +1,40 @@ +# Tomcat +server: + port: 9997 + +# Spring +spring: + application: + # 应用名称 + name: ecell-internationalize-gateway + profiles: + # 环境配置 + active: dev + cloud: + nacos: + discovery: + # 服务注册地址 + server-addr: 127.0.0.1:8848 + config: + # 配置中心地址 + server-addr: 127.0.0.1:8848 + # 配置文件格式 + file-extension: yml + # 共享配置 + shared-configs: + - application-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension} +# sentinel: +# # 取消控制台懒加载 +# eager: true +# transport: +# # 控制台地址 +# dashboard: 127.0.0.1:8718 +# # nacos配置持久化 +# datasource: +# ds1: +# nacos: +# server-addr: 127.0.0.1:8848 +# dataId: sentinel-ruoyi-gateway +# groupId: DEFAULT_GROUP +# data-type: json +# rule-type: flow diff --git a/ecell-internationalize/ecell-internationalize-gateway/src/main/resources/lua/rateLimit.lua b/ecell-internationalize/ecell-internationalize-gateway/src/main/resources/lua/rateLimit.lua new file mode 100644 index 0000000..75f3b49 --- /dev/null +++ b/ecell-internationalize/ecell-internationalize-gateway/src/main/resources/lua/rateLimit.lua @@ -0,0 +1,15 @@ +local key = KEYS[1] +local time = tonumber(ARGV[1]) +local count = tonumber(ARGV[2]) +local current = redis.call('get',key) +if current and tonumber(current) > count then + return tonumber(current) +end + current = redis.call('incr',key) +if tonumber(current)==1 then + redis.call('expire',key,time) +end + return tonumber(current) + + +