一、简介
SpringCloud Gateway 是 Spring Cloud 的一个全新项目,该项目是基于 Spring 5.0,Spring Boot 2.0 和 Project Reactor 等技术开发的网关,它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式。目标是替代 Zuul,为了提升网关的性能,SpringCloud Gateway是基于WebFlux框架实现的,而WebFlux框架底层则使用了高性能的Reactor模式通信框架Netty。它是一个微服务 它能够整体进行一个鉴权,监控,限流,不用每个微服务都进行这样的重复流程
二、组成
路由:一个Route模块由一个 ID,一个目标 URI,一组断言和一组过滤器定义。如果断言为真,则路由匹配,目标URI会被访问。
断言:自定义断言,可以使用它来匹配来自 HTTP 请求的任何内容,例如 headers 或参数。
过滤器:全局过滤器(implements GlobalFilter, Ordered)
局部过滤器(extends AbstractGatewayFilterFactory<LogGatewayFilterFactory.Config>)
routes: # 路由数组 当满足什么条件时转发到哪个微服务上
- id: message-gateway # 一般填写微服务的id
uri: http://127.0.0.1:8081 # 请求最终转发地址
# uri: lb://message-core # 要转发到的地址 lb是负载均衡,后面是微服务在nacos上的标识
order: 1 # 优先级
predicates: # 断言,匹配上就进行转发
- Path=/user/test
- Age=18,36
filters: # 对请求做一些处理
- StripPrefix = 1
- Log=true,false
自定义断言
/**
* 自定义断言工厂
* 参照内置断言工厂写 BeforeRoutePredicateFactory
* 类的命名是固定的,yml配置了XXX,就需要命名为XXXRoutePredicateFactory
* 需要自定义配置类,获取配置内容
*/
@Component
public class AgeRoutePredicateFactory extends AbstractRoutePredicateFactory<AgeRoutePredicateFactory.Config> {
public AgeRoutePredicateFactory() {
super(AgeRoutePredicateFactory.Config.class);
}
//读取配置文件中的参数值 给他赋值到配置类中
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList("minAge","maxAge");
}
//断言逻辑
@Override
public Predicate<ServerWebExchange> apply(AgeRoutePredicateFactory.Config config) {
return new Predicate<ServerWebExchange>() {
@Override
public boolean test(ServerWebExchange serverWebExchange) {
//接收前台的参数
String age = serverWebExchange.getRequest().getQueryParams().getFirst("age");
if (StrUtil.isNotBlank(age)) {
int i = Integer.parseInt(age);
return i < config.getMaxAge() && i > config.getMinAge();
}
return false;
}
};
}
//配置类,用于接收配置文件中的对应参数
@Data
@NoArgsConstructor
public static class Config {
private int maxAge;
private int minAge;
}
}
/**
* 自定义局部过滤器
* 类名需要规范,XXXGatewayFilterFactory
* 需要自定义配置类,获取配置内容
*/
@Component
public class LogGatewayFilterFactory extends AbstractGatewayFilterFactory<LogGatewayFilterFactory.Config> {
public LogGatewayFilterFactory() {
super(LogGatewayFilterFactory.Config.class);
}
//读取配置文件的参数,赋值到配置类中
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList("consoleLog", "cacheLog");
}
//过滤器逻辑
@Override
public GatewayFilter apply(Config config) {
return new GatewayFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
if (config.isCacheLog()) {
System.out.println("cache开启了");
}
if (config.isConsoleLog()) {
System.out.println("控制台开启了");
}
return chain.filter(exchange);
}
};
}
//配置类,用于接收配置文件中的对应参数
@Data
@NoArgsConstructor
public static class Config {
private boolean consoleLog;
private boolean cacheLog;
}
}
/**
* todo 需要防止xss攻击和sql注入问题
* <p>
* 自定义全局过滤器 必须实现GlobalFilter, Ordered
* 作用:统一鉴权,用户是否经过认证都必须进过这一步
* 经过认证,那么进行请求转发
* 未经过认证,跳转到security进行认证
*/
@Component
@Slf4j
@RequiredArgsConstructor
public class AuthGlobalFilter implements GlobalFilter, Ordered {
private String[] sqlPatternList = {"%'", "select ", "insert ", "delete ", " from",
"count\\(", "drop table", "update ", "truncate ", "asc\\(",
"mid\\(", "char\\(", "xp_cmdshell ", "exec ",
"netlocalgroup administrators", "net user", " or ", " and ", "'"};
private String[] paramNamePattern = {"[", "]", "<", ">"};
private AntPathMatcher antPathMatcher = new AntPathMatcher();
private final RedisTemplate<String, String> redisTemplate;
/**
* 过滤器逻辑,用户发送的请求是否携带token或token是否正确
* 正确放行,错误返回错误信息
*
* @param exchange
* @param chain
* @return
*/
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
/**
* String path = exchange.getRequest().getURI().getPath();
if (antPathMatcher.match("mess/**",path)) {
System.out.println("使用nginx配置时,可能会出现统一的前缀,那么就可以使用这个");
}
*/
//指定编码,否则在浏览器中会中文乱码
exchange.getResponse().getHeaders().add("Content-Type", "application/json;charset=UTF-8");
String hostName = exchange.getRequest().getRemoteAddress().getHostName();
//判断访问的地址是否出现在黑名单
Boolean ip = redisTemplate.opsForSet().isMember("blackIp", hostName);
if (ip) {
return exchange.getResponse().setComplete();
}
//防止sql注入
MultiValueMap<String, String> queryParams = exchange.getRequest().getQueryParams();
for (String key : queryParams.keySet()) {
//参数名特殊字符判断
for (String item : paramNamePattern) {
if (key.contains(item)) {
return exchange.getResponse().setComplete();
}
}
String param = queryParams.getFirst(key);
if (param != null) {
//sql注入校验
if (valid(param)) {
return exchange.getResponse().setComplete();
}
}
}
// 获取请求头的token
String token = exchange.getRequest().getHeaders().getFirst(JwtUtils.AUTHORIZATIONTOKEN);
if (StrUtil.isBlank(token)) {
log.info("没有携带token,需要进行认证");
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
}
/**
* 标识过滤器优先级
*
* @return
*/
@Override
public int getOrder() {
return 0;
}
private boolean valid(String value) {
for (String item : sqlPatternList) {
if (value.contains(item)) {
return true;
}
}
return false;
}
}
限流。后续更新
springSecurity认证流程
首先进行账号密码的验证
/**
* 验证账号密码
*
* @param userDetailsService
* @return
*/
@Bean
public ReactiveAuthenticationManager reactiveAuthenticationManager(SecurityUserDetailService userDetailsService) {
UserDetailsRepositoryReactiveAuthenticationManager authenticationManager = new UserDetailsRepositoryReactiveAuthenticationManager(userDetailsService);
authenticationManager.setPasswordEncoder(myPasswordEncoder);
return authenticationManager;
}
进入认证,生成token,并将token存放到redis里,返回给前端
git网址
最后,项目的登录流程简单讲就是
1、用户输入账号密码进行登录后,首先IP会先经过验证是否是黑名单,并输入的数据是否为sql注入,如果是,那么就直接进行拦截
2、通过验证之后,使用springSecurity进行账号密码的验证,验证通过后进行认证,认证后会通过jwt生成token,并发送一个token给前端,并将此token存到redis里
参考链接:https://www.jianshu.com/p/014e0d754bfe