From c80c93bdc2ff3dc0647c3c4272e0aad7d07ef077 Mon Sep 17 00:00:00 2001 From: fst1996 <2411194573@qq.com> Date: Sun, 17 Sep 2023 10:16:56 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=9D=E5=A7=8B=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 47 ++++++ pom.xml | 129 +++++++++++++++++ .../gateway/BaWeiGatewayApplication.java | 20 +++ .../bawei/gateway/config/CaptchaConfig.java | 83 +++++++++++ .../bawei/gateway/config/GatewayConfig.java | 23 +++ .../gateway/config/KaptchaTextCreator.java | 75 ++++++++++ .../config/RouterFunctionConfiguration.java | 31 ++++ .../bawei/gateway/config/SwaggerProvider.java | 79 ++++++++++ .../config/properties/CaptchaProperties.java | 46 ++++++ .../properties/IgnoreWhiteProperties.java | 33 +++++ .../config/properties/XssProperties.java | 48 +++++++ .../com/bawei/gateway/filter/AuthFilter.java | 135 ++++++++++++++++++ .../gateway/filter/BlackListUrlFilter.java | 65 +++++++++ .../gateway/filter/CacheRequestFilter.java | 87 +++++++++++ .../gateway/filter/ValidateCodeFilter.java | 79 ++++++++++ .../com/bawei/gateway/filter/XssFilter.java | 124 ++++++++++++++++ .../handler/GatewayExceptionHandler.java | 56 ++++++++ .../handler/SentinelFallbackHandler.java | 41 ++++++ .../bawei/gateway/handler/SwaggerHandler.java | 56 ++++++++ .../gateway/handler/ValidateCodeHandler.java | 41 ++++++ .../gateway/service/ValidateCodeService.java | 23 +++ .../service/impl/ValidateCodeServiceImpl.java | 119 +++++++++++++++ src/main/resources/banner.txt | 10 ++ src/main/resources/bootstrap.yml | 40 ++++++ src/main/resources/logback.xml | 74 ++++++++++ 25 files changed, 1564 insertions(+) create mode 100644 .gitignore create mode 100644 pom.xml create mode 100644 src/main/java/com/bawei/gateway/BaWeiGatewayApplication.java create mode 100644 src/main/java/com/bawei/gateway/config/CaptchaConfig.java create mode 100644 src/main/java/com/bawei/gateway/config/GatewayConfig.java create mode 100644 src/main/java/com/bawei/gateway/config/KaptchaTextCreator.java create mode 100644 src/main/java/com/bawei/gateway/config/RouterFunctionConfiguration.java create mode 100644 src/main/java/com/bawei/gateway/config/SwaggerProvider.java create mode 100644 src/main/java/com/bawei/gateway/config/properties/CaptchaProperties.java create mode 100644 src/main/java/com/bawei/gateway/config/properties/IgnoreWhiteProperties.java create mode 100644 src/main/java/com/bawei/gateway/config/properties/XssProperties.java create mode 100644 src/main/java/com/bawei/gateway/filter/AuthFilter.java create mode 100644 src/main/java/com/bawei/gateway/filter/BlackListUrlFilter.java create mode 100644 src/main/java/com/bawei/gateway/filter/CacheRequestFilter.java create mode 100644 src/main/java/com/bawei/gateway/filter/ValidateCodeFilter.java create mode 100644 src/main/java/com/bawei/gateway/filter/XssFilter.java create mode 100644 src/main/java/com/bawei/gateway/handler/GatewayExceptionHandler.java create mode 100644 src/main/java/com/bawei/gateway/handler/SentinelFallbackHandler.java create mode 100644 src/main/java/com/bawei/gateway/handler/SwaggerHandler.java create mode 100644 src/main/java/com/bawei/gateway/handler/ValidateCodeHandler.java create mode 100644 src/main/java/com/bawei/gateway/service/ValidateCodeService.java create mode 100644 src/main/java/com/bawei/gateway/service/impl/ValidateCodeServiceImpl.java create mode 100644 src/main/resources/banner.txt create mode 100644 src/main/resources/bootstrap.yml create mode 100644 src/main/resources/logback.xml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..05d7c76 --- /dev/null +++ b/.gitignore @@ -0,0 +1,47 @@ +###################################################################### +# Build Tools + +.gradle +/build/ +!gradle/wrapper/gradle-wrapper.jar + +target/ +!.mvn/wrapper/maven-wrapper.jar + +###################################################################### +# IDE + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### JRebel ### +rebel.xml +### NetBeans ### +nbproject/private/ +build/* +nbbuild/ +dist/ +nbdist/ +.nb-gradle/ +target/ + +###################################################################### +# Others +*.log +*.xml.versionsBackup +*.swp + +!*/build/*.java +!*/build/*.html +!*/build/*.xml diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..3896536 --- /dev/null +++ b/pom.xml @@ -0,0 +1,129 @@ + + + com.bawei + parent + 3.6.0 + + 4.0.0 + + 3.6.0 + menghang-gateway + + + menghang-gateway网关模块 + + + + + menghang-public + 梦航-public + http://192.168.111.130:8081/repository/maven-public/ + + + + + + menghang-releases + 梦航-releases + http://192.168.111.130:8081/repository/maven-releases/ + + + + + + + + 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.bawei + menghang-common-redis + 3.6.0 + + + + + io.springfox + springfox-swagger-ui + ${swagger.fox.version} + + + io.springfox + springfox-swagger2 + ${swagger.fox.version} + + + + + + ${project.artifactId} + + + org.springframework.boot + spring-boot-maven-plugin + 2.7.2 + + + + repackage + + + + + + + + diff --git a/src/main/java/com/bawei/gateway/BaWeiGatewayApplication.java b/src/main/java/com/bawei/gateway/BaWeiGatewayApplication.java new file mode 100644 index 0000000..5598141 --- /dev/null +++ b/src/main/java/com/bawei/gateway/BaWeiGatewayApplication.java @@ -0,0 +1,20 @@ +package com.bawei.gateway; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; + +/** + * 网关启动程序 + * + * @author bawei + */ +@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class }) +public class BaWeiGatewayApplication +{ + public static void main(String[] args) + { + SpringApplication.run(BaWeiGatewayApplication.class, args); + System.out.println("(♥◠‿◠)ノ゙ 若依网关启动成功 ლ(´ڡ`ლ)゙ "); + } +} diff --git a/src/main/java/com/bawei/gateway/config/CaptchaConfig.java b/src/main/java/com/bawei/gateway/config/CaptchaConfig.java new file mode 100644 index 0000000..4e864fb --- /dev/null +++ b/src/main/java/com/bawei/gateway/config/CaptchaConfig.java @@ -0,0 +1,83 @@ +package com.bawei.gateway.config; + +import java.util.Properties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import com.google.code.kaptcha.impl.DefaultKaptcha; +import com.google.code.kaptcha.util.Config; +import static com.google.code.kaptcha.Constants.*; + +/** + * 验证码配置 + * + * @author bawei + */ +@Configuration +public class CaptchaConfig +{ + @Bean(name = "captchaProducer") + public DefaultKaptcha getKaptchaBean() + { + DefaultKaptcha defaultKaptcha = new DefaultKaptcha(); + Properties properties = new Properties(); + // 是否有边框 默认为true 我们可以自己设置yes,no + properties.setProperty(KAPTCHA_BORDER, "yes"); + // 验证码文本字符颜色 默认为Color.BLACK + properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_COLOR, "black"); + // 验证码图片宽度 默认为200 + properties.setProperty(KAPTCHA_IMAGE_WIDTH, "160"); + // 验证码图片高度 默认为50 + properties.setProperty(KAPTCHA_IMAGE_HEIGHT, "60"); + // 验证码文本字符大小 默认为40 + properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_SIZE, "38"); + // KAPTCHA_SESSION_KEY + properties.setProperty(KAPTCHA_SESSION_CONFIG_KEY, "kaptchaCode"); + // 验证码文本字符长度 默认为5 + properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "4"); + // 验证码文本字体样式 默认为new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize) + properties.setProperty(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(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(KAPTCHA_BORDER, "yes"); + // 边框颜色 默认为Color.BLACK + properties.setProperty(KAPTCHA_BORDER_COLOR, "105,179,90"); + // 验证码文本字符颜色 默认为Color.BLACK + properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_COLOR, "blue"); + // 验证码图片宽度 默认为200 + properties.setProperty(KAPTCHA_IMAGE_WIDTH, "160"); + // 验证码图片高度 默认为50 + properties.setProperty(KAPTCHA_IMAGE_HEIGHT, "60"); + // 验证码文本字符大小 默认为40 + properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_SIZE, "35"); + // KAPTCHA_SESSION_KEY + properties.setProperty(KAPTCHA_SESSION_CONFIG_KEY, "kaptchaCodeMath"); + // 验证码文本生成器 + properties.setProperty(KAPTCHA_TEXTPRODUCER_IMPL, "com.bawei.gateway.config.KaptchaTextCreator"); + // 验证码文本字符间距 默认为2 + properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_SPACE, "3"); + // 验证码文本字符长度 默认为5 + properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "6"); + // 验证码文本字体样式 默认为new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize) + properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_NAMES, "Arial,Courier"); + // 验证码噪点颜色 默认为Color.BLACK + properties.setProperty(KAPTCHA_NOISE_COLOR, "white"); + // 干扰实现类 + properties.setProperty(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(KAPTCHA_OBSCURIFICATOR_IMPL, "com.google.code.kaptcha.impl.ShadowGimpy"); + Config config = new Config(properties); + defaultKaptcha.setConfig(config); + return defaultKaptcha; + } +} diff --git a/src/main/java/com/bawei/gateway/config/GatewayConfig.java b/src/main/java/com/bawei/gateway/config/GatewayConfig.java new file mode 100644 index 0000000..08b7994 --- /dev/null +++ b/src/main/java/com/bawei/gateway/config/GatewayConfig.java @@ -0,0 +1,23 @@ +package com.bawei.gateway.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import com.bawei.gateway.handler.SentinelFallbackHandler; + +/** + * 网关限流配置 + * + * @author bawei + */ +@Configuration +public class GatewayConfig +{ + @Bean + @Order(Ordered.HIGHEST_PRECEDENCE) + public SentinelFallbackHandler sentinelGatewayExceptionHandler() + { + return new SentinelFallbackHandler(); + } +} diff --git a/src/main/java/com/bawei/gateway/config/KaptchaTextCreator.java b/src/main/java/com/bawei/gateway/config/KaptchaTextCreator.java new file mode 100644 index 0000000..6b62190 --- /dev/null +++ b/src/main/java/com/bawei/gateway/config/KaptchaTextCreator.java @@ -0,0 +1,75 @@ +package com.bawei.gateway.config; + +import java.util.Random; +import com.google.code.kaptcha.text.impl.DefaultTextCreator; + +/** + * 验证码文本生成器 + * + * @author bawei + */ +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 = random.nextInt(3); + 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/src/main/java/com/bawei/gateway/config/RouterFunctionConfiguration.java b/src/main/java/com/bawei/gateway/config/RouterFunctionConfiguration.java new file mode 100644 index 0000000..156b8da --- /dev/null +++ b/src/main/java/com/bawei/gateway/config/RouterFunctionConfiguration.java @@ -0,0 +1,31 @@ +package com.bawei.gateway.config; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.MediaType; +import org.springframework.web.reactive.function.server.RequestPredicates; +import org.springframework.web.reactive.function.server.RouterFunction; +import org.springframework.web.reactive.function.server.RouterFunctions; +import com.bawei.gateway.handler.ValidateCodeHandler; + +/** + * 路由配置信息 + * + * @author bawei + */ +@Configuration +public class RouterFunctionConfiguration +{ + @Autowired + private ValidateCodeHandler validateCodeHandler; + + @SuppressWarnings("rawtypes") + @Bean + public RouterFunction routerFunction() + { + return RouterFunctions.route( + RequestPredicates.GET("/code").and(RequestPredicates.accept(MediaType.TEXT_PLAIN)), + validateCodeHandler); + } +} diff --git a/src/main/java/com/bawei/gateway/config/SwaggerProvider.java b/src/main/java/com/bawei/gateway/config/SwaggerProvider.java new file mode 100644 index 0000000..51476a1 --- /dev/null +++ b/src/main/java/com/bawei/gateway/config/SwaggerProvider.java @@ -0,0 +1,79 @@ +package com.bawei.gateway.config; + +import java.util.ArrayList; +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cloud.gateway.config.GatewayProperties; +import org.springframework.cloud.gateway.route.RouteLocator; +import org.springframework.cloud.gateway.support.NameUtils; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Component; +import org.springframework.web.reactive.config.ResourceHandlerRegistry; +import org.springframework.web.reactive.config.WebFluxConfigurer; +import springfox.documentation.swagger.web.SwaggerResource; +import springfox.documentation.swagger.web.SwaggerResourcesProvider; + +/** + * 聚合系统接口 + * + * @author bawei + */ +@Component +public class SwaggerProvider implements SwaggerResourcesProvider, WebFluxConfigurer +{ + /** + * Swagger2默认的url后缀 + */ + public static final String SWAGGER2URL = "/v2/api-docs"; + + /** + * 网关路由 + */ + @Lazy + @Autowired + private RouteLocator routeLocator; + + @Autowired + private GatewayProperties gatewayProperties; + + /** + * 聚合其他服务接口 + * + * @return + */ + @Override + public List get() + { + List resourceList = new ArrayList<>(); + List routes = new ArrayList<>(); + // 获取网关中配置的route + routeLocator.getRoutes().subscribe(route -> routes.add(route.getId())); + gatewayProperties.getRoutes().stream() + .filter(routeDefinition -> routes + .contains(routeDefinition.getId())) + .forEach(routeDefinition -> routeDefinition.getPredicates().stream() + .filter(predicateDefinition -> "Path".equalsIgnoreCase(predicateDefinition.getName())) + .filter(predicateDefinition -> !"bawei-auth".equalsIgnoreCase(routeDefinition.getId())) + .forEach(predicateDefinition -> resourceList + .add(swaggerResource(routeDefinition.getId(), predicateDefinition.getArgs() + .get(NameUtils.GENERATED_NAME_PREFIX + "0").replace("/**", SWAGGER2URL))))); + return resourceList; + } + + private SwaggerResource swaggerResource(String name, String location) + { + SwaggerResource swaggerResource = new SwaggerResource(); + swaggerResource.setName(name); + swaggerResource.setLocation(location); + swaggerResource.setSwaggerVersion("2.0"); + return swaggerResource; + } + + @Override + public void addResourceHandlers(ResourceHandlerRegistry registry) + { + /** swagger-ui 地址 */ + registry.addResourceHandler("/swagger-ui/**") + .addResourceLocations("classpath:/META-INF/resources/webjars/springfox-swagger-ui/"); + } +} diff --git a/src/main/java/com/bawei/gateway/config/properties/CaptchaProperties.java b/src/main/java/com/bawei/gateway/config/properties/CaptchaProperties.java new file mode 100644 index 0000000..b9fbf42 --- /dev/null +++ b/src/main/java/com/bawei/gateway/config/properties/CaptchaProperties.java @@ -0,0 +1,46 @@ +package com.bawei.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 bawei + */ +@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/src/main/java/com/bawei/gateway/config/properties/IgnoreWhiteProperties.java b/src/main/java/com/bawei/gateway/config/properties/IgnoreWhiteProperties.java new file mode 100644 index 0000000..73e0691 --- /dev/null +++ b/src/main/java/com/bawei/gateway/config/properties/IgnoreWhiteProperties.java @@ -0,0 +1,33 @@ +package com.bawei.gateway.config.properties; + +import java.util.ArrayList; +import java.util.List; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.cloud.context.config.annotation.RefreshScope; +import org.springframework.context.annotation.Configuration; + +/** + * 放行白名单配置 + * + * @author bawei + */ +@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/src/main/java/com/bawei/gateway/config/properties/XssProperties.java b/src/main/java/com/bawei/gateway/config/properties/XssProperties.java new file mode 100644 index 0000000..fb030ad --- /dev/null +++ b/src/main/java/com/bawei/gateway/config/properties/XssProperties.java @@ -0,0 +1,48 @@ +package com.bawei.gateway.config.properties; + +import java.util.ArrayList; +import java.util.List; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.cloud.context.config.annotation.RefreshScope; +import org.springframework.context.annotation.Configuration; + +/** + * XSS跨站脚本配置 + * + * @author bawei + */ +@Configuration +@RefreshScope +@ConfigurationProperties(prefix = "security.xss") +public class XssProperties +{ + /** + * Xss开关 + */ + private Boolean enabled; + + /** + * 排除路径 + */ + private List excludeUrls = new ArrayList<>(); + + public Boolean getEnabled() + { + return enabled; + } + + public void setEnabled(Boolean enabled) + { + this.enabled = enabled; + } + + public List getExcludeUrls() + { + return excludeUrls; + } + + public void setExcludeUrls(List excludeUrls) + { + this.excludeUrls = excludeUrls; + } +} diff --git a/src/main/java/com/bawei/gateway/filter/AuthFilter.java b/src/main/java/com/bawei/gateway/filter/AuthFilter.java new file mode 100644 index 0000000..844b96e --- /dev/null +++ b/src/main/java/com/bawei/gateway/filter/AuthFilter.java @@ -0,0 +1,135 @@ +package com.bawei.gateway.filter; + +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.http.server.reactive.ServerHttpRequest; +import org.springframework.stereotype.Component; +import org.springframework.web.server.ServerWebExchange; +import com.bawei.common.core.constant.CacheConstants; +import com.bawei.common.core.constant.HttpStatus; +import com.bawei.common.core.constant.SecurityConstants; +import com.bawei.common.core.constant.TokenConstants; +import com.bawei.common.core.utils.JwtUtils; +import com.bawei.common.core.utils.ServletUtils; +import com.bawei.common.core.utils.StringUtils; +import com.bawei.common.redis.service.RedisService; +import com.bawei.gateway.config.properties.IgnoreWhiteProperties; +import io.jsonwebtoken.Claims; +import reactor.core.publisher.Mono; + +/** + * 网关鉴权 + * + * @author bawei + */ +@Component +public class AuthFilter implements GlobalFilter, Ordered +{ + private static final Logger log = LoggerFactory.getLogger(AuthFilter.class); + + // 排除过滤的 uri 地址,nacos自行添加 + @Autowired + private IgnoreWhiteProperties ignoreWhite; + + @Autowired + private RedisService redisService; + + + @Override + public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) + { + ServerHttpRequest request = exchange.getRequest(); + ServerHttpRequest.Builder mutate = request.mutate(); + + String url = request.getURI().getPath(); + // 跳过不需要验证的路径 + if (StringUtils.matches(url, ignoreWhite.getWhites())) + { + return chain.filter(exchange); + } + String token = getToken(request); + if (StringUtils.isEmpty(token)) + { + return unauthorizedResponse(exchange, "令牌不能为空"); + } + Claims claims = JwtUtils.parseToken(token); + if (claims == null) + { + return unauthorizedResponse(exchange, "令牌已过期或验证不正确!"); + } + String userkey = JwtUtils.getUserKey(claims); + boolean islogin = redisService.hasKey(getTokenKey(userkey)); + if (!islogin) + { + return unauthorizedResponse(exchange, "登录状态已过期"); + } + String userid = JwtUtils.getUserId(claims); + String username = JwtUtils.getUserName(claims); + if (StringUtils.isEmpty(userid) || StringUtils.isEmpty(username)) + { + return unauthorizedResponse(exchange, "令牌验证失败"); + } + + // 设置用户信息到请求 + 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); + } + + /** + * 获取缓存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; + } + + @Override + public int getOrder() + { + return -200; + } +} diff --git a/src/main/java/com/bawei/gateway/filter/BlackListUrlFilter.java b/src/main/java/com/bawei/gateway/filter/BlackListUrlFilter.java new file mode 100644 index 0000000..aba7316 --- /dev/null +++ b/src/main/java/com/bawei/gateway/filter/BlackListUrlFilter.java @@ -0,0 +1,65 @@ +package com.bawei.gateway.filter; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Pattern; +import org.springframework.cloud.gateway.filter.GatewayFilter; +import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory; +import org.springframework.stereotype.Component; +import com.bawei.common.core.utils.ServletUtils; + +/** + * 黑名单过滤器 + * + * @author bawei + */ +@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(), "请求地址不允许访问"); + } + + 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/src/main/java/com/bawei/gateway/filter/CacheRequestFilter.java b/src/main/java/com/bawei/gateway/filter/CacheRequestFilter.java new file mode 100644 index 0000000..1165f7c --- /dev/null +++ b/src/main/java/com/bawei/gateway/filter/CacheRequestFilter.java @@ -0,0 +1,87 @@ +package com.bawei.gateway.filter; + +import java.util.Collections; +import java.util.List; +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; + +/** + * 获取body请求数据(解决流不能重复读取问题) + * + * @author bawei + */ +@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/src/main/java/com/bawei/gateway/filter/ValidateCodeFilter.java b/src/main/java/com/bawei/gateway/filter/ValidateCodeFilter.java new file mode 100644 index 0000000..ec01bda --- /dev/null +++ b/src/main/java/com/bawei/gateway/filter/ValidateCodeFilter.java @@ -0,0 +1,79 @@ +package com.bawei.gateway.filter; + +import java.nio.CharBuffer; +import java.nio.charset.StandardCharsets; +import java.util.concurrent.atomic.AtomicReference; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cloud.gateway.filter.GatewayFilter; +import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory; +import org.springframework.core.io.buffer.DataBuffer; +import org.springframework.core.io.buffer.DataBufferUtils; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.stereotype.Component; +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; +import com.bawei.common.core.utils.ServletUtils; +import com.bawei.common.core.utils.StringUtils; +import com.bawei.gateway.config.properties.CaptchaProperties; +import com.bawei.gateway.service.ValidateCodeService; +import reactor.core.publisher.Flux; + +/** + * 验证码过滤器 + * + * @author bawei + */ +@Component +public class ValidateCodeFilter extends AbstractGatewayFilterFactory +{ + private final static String[] VALIDATE_URL = new String[] { "/auth/login", "/auth/register" }; + + @Autowired + private ValidateCodeService validateCodeService; + + @Autowired + private CaptchaProperties captchaProperties; + + private static final String CODE = "code"; + + private static final String UUID = "uuid"; + + @Override + public GatewayFilter apply(Object config) + { + return (exchange, chain) -> { + ServerHttpRequest request = exchange.getRequest(); + + // 非登录/注册请求或验证码关闭,不处理 + if (!StringUtils.containsAnyIgnoreCase(request.getURI().getPath(), VALIDATE_URL) || !captchaProperties.getEnabled()) + { + return chain.filter(exchange); + } + + try + { + String rspStr = resolveBodyFromRequest(request); + JSONObject obj = JSON.parseObject(rspStr); + validateCodeService.checkCaptcha(obj.getString(CODE), obj.getString(UUID)); + } + catch (Exception e) + { + return ServletUtils.webFluxResponseWriter(exchange.getResponse(), e.getMessage()); + } + return chain.filter(exchange); + }; + } + + private String resolveBodyFromRequest(ServerHttpRequest serverHttpRequest) + { + // 获取请求体 + Flux body = serverHttpRequest.getBody(); + AtomicReference bodyRef = new AtomicReference<>(); + body.subscribe(buffer -> { + CharBuffer charBuffer = StandardCharsets.UTF_8.decode(buffer.asByteBuffer()); + DataBufferUtils.release(buffer); + bodyRef.set(charBuffer.toString()); + }); + return bodyRef.get(); + } +} diff --git a/src/main/java/com/bawei/gateway/filter/XssFilter.java b/src/main/java/com/bawei/gateway/filter/XssFilter.java new file mode 100644 index 0000000..db30e8f --- /dev/null +++ b/src/main/java/com/bawei/gateway/filter/XssFilter.java @@ -0,0 +1,124 @@ +package com.bawei.gateway.filter; + +import java.nio.charset.StandardCharsets; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +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.core.io.buffer.DataBufferFactory; +import org.springframework.core.io.buffer.DataBufferUtils; +import org.springframework.core.io.buffer.DefaultDataBufferFactory; +import org.springframework.core.io.buffer.NettyDataBufferFactory; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.http.server.reactive.ServerHttpRequestDecorator; +import org.springframework.stereotype.Component; +import org.springframework.web.server.ServerWebExchange; +import com.bawei.common.core.utils.StringUtils; +import com.bawei.common.core.utils.html.EscapeUtil; +import com.bawei.gateway.config.properties.XssProperties; +import io.netty.buffer.ByteBufAllocator; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +/** + * 跨站脚本过滤器 + * + * @author bawei + */ +@Component +@ConditionalOnProperty(value = "security.xss.enabled", havingValue = "true") +public class XssFilter implements GlobalFilter, Ordered +{ + // 跨站脚本的 xss 配置,nacos自行添加 + @Autowired + private XssProperties xss; + + @Override + public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) + { + ServerHttpRequest request = exchange.getRequest(); + // GET DELETE 不过滤 + HttpMethod method = request.getMethod(); + if (method == null || method == HttpMethod.GET || method == HttpMethod.DELETE) + { + return chain.filter(exchange); + } + // 非json类型,不过滤 + if (!isJsonRequest(exchange)) + { + return chain.filter(exchange); + } + // excludeUrls 不过滤 + String url = request.getURI().getPath(); + if (StringUtils.matches(url, xss.getExcludeUrls())) + { + return chain.filter(exchange); + } + ServerHttpRequestDecorator httpRequestDecorator = requestDecorator(exchange); + return chain.filter(exchange.mutate().request(httpRequestDecorator).build()); + + } + + private ServerHttpRequestDecorator requestDecorator(ServerWebExchange exchange) + { + ServerHttpRequestDecorator serverHttpRequestDecorator = new ServerHttpRequestDecorator(exchange.getRequest()) + { + @Override + public Flux getBody() + { + Flux body = super.getBody(); + return body.buffer().map(dataBuffers -> { + DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory(); + DataBuffer join = dataBufferFactory.join(dataBuffers); + byte[] content = new byte[join.readableByteCount()]; + join.read(content); + DataBufferUtils.release(join); + String bodyStr = new String(content, StandardCharsets.UTF_8); + // 防xss攻击过滤 + bodyStr = EscapeUtil.clean(bodyStr); + // 转成字节 + byte[] bytes = bodyStr.getBytes(); + NettyDataBufferFactory nettyDataBufferFactory = new NettyDataBufferFactory(ByteBufAllocator.DEFAULT); + DataBuffer buffer = nettyDataBufferFactory.allocateBuffer(bytes.length); + buffer.write(bytes); + return buffer; + }); + } + + @Override + public HttpHeaders getHeaders() + { + HttpHeaders httpHeaders = new HttpHeaders(); + httpHeaders.putAll(super.getHeaders()); + // 由于修改了请求体的body,导致content-length长度不确定,因此需要删除原先的content-length + httpHeaders.remove(HttpHeaders.CONTENT_LENGTH); + httpHeaders.set(HttpHeaders.TRANSFER_ENCODING, "chunked"); + return httpHeaders; + } + + }; + return serverHttpRequestDecorator; + } + + /** + * 是否是Json请求 + * + * @param exchange HTTP请求 + */ + public boolean isJsonRequest(ServerWebExchange exchange) + { + String header = exchange.getRequest().getHeaders().getFirst(HttpHeaders.CONTENT_TYPE); + return StringUtils.startsWithIgnoreCase(header, MediaType.APPLICATION_JSON_VALUE); + } + + @Override + public int getOrder() + { + return -100; + } +} diff --git a/src/main/java/com/bawei/gateway/handler/GatewayExceptionHandler.java b/src/main/java/com/bawei/gateway/handler/GatewayExceptionHandler.java new file mode 100644 index 0000000..b937f73 --- /dev/null +++ b/src/main/java/com/bawei/gateway/handler/GatewayExceptionHandler.java @@ -0,0 +1,56 @@ +package com.bawei.gateway.handler; + +import org.springframework.cloud.gateway.support.NotFoundException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler; +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 com.bawei.common.core.utils.ServletUtils; +import reactor.core.publisher.Mono; + +/** + * 网关统一异常处理 + * + * @author bawei + */ +@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 = "服务未找到"; + } + else if (ex instanceof ResponseStatusException) + { + ResponseStatusException responseStatusException = (ResponseStatusException) ex; + msg = responseStatusException.getMessage(); + } + else + { + msg = "内部服务器错误"; + } + + log.error("[网关异常处理]请求路径:{},异常信息:{}", exchange.getRequest().getPath(), ex.getMessage()); + + return ServletUtils.webFluxResponseWriter(response, msg); + } +} diff --git a/src/main/java/com/bawei/gateway/handler/SentinelFallbackHandler.java b/src/main/java/com/bawei/gateway/handler/SentinelFallbackHandler.java new file mode 100644 index 0000000..5b2d961 --- /dev/null +++ b/src/main/java/com/bawei/gateway/handler/SentinelFallbackHandler.java @@ -0,0 +1,41 @@ +package com.bawei.gateway.handler; + +import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager; +import com.alibaba.csp.sentinel.slots.block.BlockException; +import com.bawei.common.core.utils.ServletUtils; +import org.springframework.web.reactive.function.server.ServerResponse; +import org.springframework.web.server.ServerWebExchange; +import org.springframework.web.server.WebExceptionHandler; +import reactor.core.publisher.Mono; + +/** + * 自定义限流异常处理 + * + * @author bawei + */ +public class SentinelFallbackHandler implements WebExceptionHandler +{ + private Mono writeResponse(ServerResponse response, ServerWebExchange exchange) + { + return ServletUtils.webFluxResponseWriter(exchange.getResponse(), "请求超过最大数,请稍候再试"); + } + + @Override + public Mono handle(ServerWebExchange exchange, Throwable ex) + { + if (exchange.getResponse().isCommitted()) + { + return Mono.error(ex); + } + if (!BlockException.isBlockException(ex)) + { + return Mono.error(ex); + } + return handleBlockedRequest(exchange, ex).flatMap(response -> writeResponse(response, exchange)); + } + + private Mono handleBlockedRequest(ServerWebExchange exchange, Throwable throwable) + { + return GatewayCallbackManager.getBlockHandler().handleRequest(exchange, throwable); + } +} diff --git a/src/main/java/com/bawei/gateway/handler/SwaggerHandler.java b/src/main/java/com/bawei/gateway/handler/SwaggerHandler.java new file mode 100644 index 0000000..86cd76b --- /dev/null +++ b/src/main/java/com/bawei/gateway/handler/SwaggerHandler.java @@ -0,0 +1,56 @@ +package com.bawei.gateway.handler; + +import java.util.Optional; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import reactor.core.publisher.Mono; +import springfox.documentation.swagger.web.SecurityConfiguration; +import springfox.documentation.swagger.web.SecurityConfigurationBuilder; +import springfox.documentation.swagger.web.SwaggerResourcesProvider; +import springfox.documentation.swagger.web.UiConfiguration; +import springfox.documentation.swagger.web.UiConfigurationBuilder; + +@RestController +@RequestMapping("/swagger-resources") +public class SwaggerHandler +{ + @Autowired(required = false) + private SecurityConfiguration securityConfiguration; + + @Autowired(required = false) + private UiConfiguration uiConfiguration; + + private final SwaggerResourcesProvider swaggerResources; + + @Autowired + public SwaggerHandler(SwaggerResourcesProvider swaggerResources) + { + this.swaggerResources = swaggerResources; + } + + @GetMapping("/configuration/security") + public Mono> securityConfiguration() + { + return Mono.just(new ResponseEntity<>( + Optional.ofNullable(securityConfiguration).orElse(SecurityConfigurationBuilder.builder().build()), + HttpStatus.OK)); + } + + @GetMapping("/configuration/ui") + public Mono> uiConfiguration() + { + return Mono.just(new ResponseEntity<>( + Optional.ofNullable(uiConfiguration).orElse(UiConfigurationBuilder.builder().build()), HttpStatus.OK)); + } + + @SuppressWarnings("rawtypes") + @GetMapping("") + public Mono swaggerResources() + { + return Mono.just((new ResponseEntity<>(swaggerResources.get(), HttpStatus.OK))); + } +} diff --git a/src/main/java/com/bawei/gateway/handler/ValidateCodeHandler.java b/src/main/java/com/bawei/gateway/handler/ValidateCodeHandler.java new file mode 100644 index 0000000..68b4a96 --- /dev/null +++ b/src/main/java/com/bawei/gateway/handler/ValidateCodeHandler.java @@ -0,0 +1,41 @@ +package com.bawei.gateway.handler; + +import java.io.IOException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Component; +import org.springframework.web.reactive.function.BodyInserters; +import org.springframework.web.reactive.function.server.HandlerFunction; +import org.springframework.web.reactive.function.server.ServerRequest; +import org.springframework.web.reactive.function.server.ServerResponse; +import com.bawei.common.core.exception.CaptchaException; +import com.bawei.common.core.web.domain.AjaxResult; +import com.bawei.gateway.service.ValidateCodeService; +import reactor.core.publisher.Mono; + +/** + * 验证码获取 + * + * @author bawei + */ +@Component +public class ValidateCodeHandler implements HandlerFunction +{ + @Autowired + private ValidateCodeService validateCodeService; + + @Override + public Mono handle(ServerRequest serverRequest) + { + AjaxResult ajax; + try + { + ajax = validateCodeService.createCaptcha(); + } + catch (CaptchaException | IOException e) + { + return Mono.error(e); + } + return ServerResponse.status(HttpStatus.OK).body(BodyInserters.fromValue(ajax)); + } +} diff --git a/src/main/java/com/bawei/gateway/service/ValidateCodeService.java b/src/main/java/com/bawei/gateway/service/ValidateCodeService.java new file mode 100644 index 0000000..904cf47 --- /dev/null +++ b/src/main/java/com/bawei/gateway/service/ValidateCodeService.java @@ -0,0 +1,23 @@ +package com.bawei.gateway.service; + +import java.io.IOException; +import com.bawei.common.core.exception.CaptchaException; +import com.bawei.common.core.web.domain.AjaxResult; + +/** + * 验证码处理 + * + * @author bawei + */ +public interface ValidateCodeService +{ + /** + * 生成验证码 + */ + public AjaxResult createCaptcha() throws IOException, CaptchaException; + + /** + * 校验验证码 + */ + public void checkCaptcha(String key, String value) throws CaptchaException; +} diff --git a/src/main/java/com/bawei/gateway/service/impl/ValidateCodeServiceImpl.java b/src/main/java/com/bawei/gateway/service/impl/ValidateCodeServiceImpl.java new file mode 100644 index 0000000..6315c33 --- /dev/null +++ b/src/main/java/com/bawei/gateway/service/impl/ValidateCodeServiceImpl.java @@ -0,0 +1,119 @@ +package com.bawei.gateway.service.impl; + +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.util.concurrent.TimeUnit; +import javax.annotation.Resource; +import javax.imageio.ImageIO; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.util.FastByteArrayOutputStream; +import com.google.code.kaptcha.Producer; +import com.bawei.common.core.constant.CacheConstants; +import com.bawei.common.core.constant.Constants; +import com.bawei.common.core.exception.CaptchaException; +import com.bawei.common.core.utils.StringUtils; +import com.bawei.common.core.utils.sign.Base64; +import com.bawei.common.core.utils.uuid.IdUtils; +import com.bawei.common.core.web.domain.AjaxResult; +import com.bawei.common.redis.service.RedisService; +import com.bawei.gateway.config.properties.CaptchaProperties; +import com.bawei.gateway.service.ValidateCodeService; + +/** + * 验证码实现处理 + * + * @author bawei + */ +@Service +public class ValidateCodeServiceImpl implements ValidateCodeService +{ + @Resource(name = "captchaProducer") + private Producer captchaProducer; + + @Resource(name = "captchaProducerMath") + private Producer captchaProducerMath; + + @Autowired + private RedisService redisService; + + @Autowired + private CaptchaProperties captchaProperties; + + /** + * 生成验证码 + */ + @Override + public AjaxResult createCaptcha() throws IOException, CaptchaException + { + AjaxResult ajax = AjaxResult.success(); + boolean captchaEnabled = captchaProperties.getEnabled(); + ajax.put("captchaEnabled", captchaEnabled); + if (!captchaEnabled) + { + return ajax; + } + + // 保存验证码信息 + String uuid = IdUtils.simpleUUID(); + String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + uuid; + + String capStr = null, code = null; + BufferedImage image = null; + + String captchaType = captchaProperties.getType(); + // 生成验证码 + if ("math".equals(captchaType)) + { + String capText = captchaProducerMath.createText(); + capStr = capText.substring(0, capText.lastIndexOf("@")); + code = capText.substring(capText.lastIndexOf("@") + 1); + image = captchaProducerMath.createImage(capStr); + } + else if ("char".equals(captchaType)) + { + capStr = code = captchaProducer.createText(); + image = captchaProducer.createImage(capStr); + } + + redisService.setCacheObject(verifyKey, code, Constants.CAPTCHA_EXPIRATION, TimeUnit.MINUTES); + // 转换流信息写出 + FastByteArrayOutputStream os = new FastByteArrayOutputStream(); + try + { + ImageIO.write(image, "jpg", os); + } + catch (IOException e) + { + return AjaxResult.error(e.getMessage()); + } + + ajax.put("uuid", uuid); + ajax.put("img", Base64.encode(os.toByteArray())); + return ajax; + } + + /** + * 校验验证码 + */ + @Override + public void checkCaptcha(String code, String uuid) throws CaptchaException + { + if (StringUtils.isEmpty(code)) + { + throw new CaptchaException("验证码不能为空"); + } + if (StringUtils.isEmpty(uuid)) + { + throw new CaptchaException("验证码已失效"); + } + String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + uuid; + String captcha = redisService.getCacheObject(verifyKey); + redisService.deleteObject(verifyKey); + + if (!code.equalsIgnoreCase(captcha)) + { + throw new CaptchaException("验证码错误"); + } + } +} diff --git a/src/main/resources/banner.txt b/src/main/resources/banner.txt new file mode 100644 index 0000000..ceced29 --- /dev/null +++ b/src/main/resources/banner.txt @@ -0,0 +1,10 @@ +Spring Boot Version: ${spring-boot.version} +Spring Application Name: ${spring.application.name} + _ _ + (_) | | + _ __ _ _ ___ _ _ _ ______ __ _ __ _ | |_ ___ __ __ __ _ _ _ +| '__|| | | | / _ \ | | | || ||______| / _` | / _` || __| / _ \\ \ /\ / / / _` || | | | +| | | |_| || (_) || |_| || | | (_| || (_| || |_ | __/ \ V V / | (_| || |_| | +|_| \__,_| \___/ \__, ||_| \__, | \__,_| \__| \___| \_/\_/ \__,_| \__, | + __/ | __/ | __/ | + |___/ |___/ |___/ \ No newline at end of file diff --git a/src/main/resources/bootstrap.yml b/src/main/resources/bootstrap.yml new file mode 100644 index 0000000..c02a554 --- /dev/null +++ b/src/main/resources/bootstrap.yml @@ -0,0 +1,40 @@ +# Tomcat +server: + port: 8080 + +# Spring +spring: + application: + # 应用名称 + name: bawei-gateway + profiles: + # 环境配置 + active: dev + cloud: + nacos: + discovery: + # 服务注册地址 + server-addr: 192.168.111.130:8848 + config: + # 配置中心地址 + server-addr: 192.168.111.130: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: 192.168.111.130:8848 + dataId: sentinel-bawei-gateway + groupId: DEFAULT_GROUP + data-type: json + rule-type: flow diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml new file mode 100644 index 0000000..56b7fac --- /dev/null +++ b/src/main/resources/logback.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + ${log.pattern} + + + + + + ${log.path}/info.log + + + + ${log.path}/info.%d{yyyy-MM-dd}.log + + 60 + + + ${log.pattern} + + + + INFO + + ACCEPT + + DENY + + + + + ${log.path}/error.log + + + + ${log.path}/error.%d{yyyy-MM-dd}.log + + 60 + + + ${log.pattern} + + + + ERROR + + ACCEPT + + DENY + + + + + + + + + + + + + + + + + +