网关作用:
- 将请求路由到真实的服务器上 ,进而保护真实服务器的IP地址, 避免直接地攻击真实服务器
- 作为负载均衡的手段,使得请求按照特定的算法平摊到多个节点上,减缓单点压力
- 作为过滤器的使用可以判定请求是否为有效请求一旦判定失败,就可以将请求阻止,避免发送到真实的服务器,这样就能降低真实服务器的压力
一、新建工程,导入依赖
spring-cloud-starter-netflix-euraka-client
spring-cloud-tarter-netflix-zuul
二、主类添加注解,启用Zuul网关
@SpringBootApplication(scanBasePackage="com.springboot.chapter.zuul")
@EnableZuulProxy //开启Zuul注解代理,内部已经引入了断路机制
public class ChapterZuulApplication{
//...略
}
三、配置properties
server.port=80
spring.application.name=zuul
# 指定ANT风格的URL
zuul.routes.user-service.path=/u/**
#指定映射的服务用户地址,这样Zuul就会将请求转发到用户微服务上了
zuul.routes.user-servcie.url=http://localhost:8001/
# 产品微服务映射规则
zuul.routes.product-service.path=/p/**
# 映射产品服务中心ID,zuul会自动使用服务端负载均衡,分摊请求
zuul.routes.product-service.serviceId=product
# 注册给服务治理中心集群
euraka.client.serviceUrl.defaultZone=http://localhost:7001/euraka,http://localhost:7002/euraka
使用过滤器
监测用户登录、黑名单用户、购物验证码、恶意刷请求攻击等场景
一,引入Redis依赖
<dependency>
<groupid>org.sprigframework.boot</g oupid>
<artifactid>spring-boot-starter-data-redis</artifactid>
<exclusions>
<!--不依赖redis的异步客户端-->
<exclusion>
<groupid>io.lettuce</group i d>
<artifactid>lettuce-core</artifactid>
</exclusion>
</exclusions>
</dependency>
<!--Redis客户端驱动jedis-->
<dependency>
<groupId> red ients</groupId>
<artifactid>jedis</artifactid>
</dependency>
二、配置文件
spring.redis.jedis.pool.min-idle=5
spring.redis.jedis.pool.max-active=10
spring.redis.jedis.pool.max-idle=10
spring.redis.jedis.pool.max-wait=2000
spring.redis.port=6379
spring.redis.host=192.168.11.131
spring.redis.password=123456
spring.redis.timeout=1000
三、使用过滤器判定验证码
@Component //该注解表示Spring会扫描,并装配到IOC容器中
public class MyZuulFilter extends ZuulFilter{
@Autowired
private StringRedisTemplate redisTemplate = null;
@Override
public boolean shoudFilter(){ //返回true,则主席那个这个过滤器的run方法
//请求上下文
RequestContext ctx = RequestContext.getCurrentContext();
//获取HttpServletRequest对象
HttpServletRequest req = ctx.getRequest();
//取出表单序列号
String serialNumber = req.getParameter("serialNumber");
//如果存在验证码,返回true,启用过滤器
return !StringUtils.isEmpty(serialNumber);
}
@Override
public Object run(){
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest req = ctx.getRequest();
//去除表单序列号和请求验证码
String serialNumber = req.getParameter("serialNumber");
String reqCode = req.getParameter("verificationCode");
//从Redis中取出验证码
String verifCode = redisTemplate.opsForValue().get(serialNumber);
//若redis验证码为空或者与请求不一致,拦截请求爆出错误
if(verifCode == null || !verifCode.equals(reqCode)){
ctx.setSendZuulResponse(false);//不再转发请求
ctx.setResponseStateusCode(401);//设置HTTP响应码为401(未授权)
ctx.getResponse().setContentType(MediaType.APPLICATION_JSON_UTF8.getType());
//设置响应体
ctx.setResponseBody("{'success':false," + "'message':Verification Code Error");
}
//一致放过
return null;
}
//过滤器类型为请求前
@Override
public String filterType(){
return "pre";
}
//过滤器排序,数字越小优先级越高
@Override
public int filterOrder(){
return 0;
}
}
四、通过浏览器请求进行测试
@SpringCloudApplication注解
会启用SpringBoot的应用,以及开发服务发现和断路器的功能
但缺乏扫描包配置项,需要配合@ComponentScan定义扫描包
@EnableFeignClients(basePackages="com.springboot.chapter.product")
@ComponentScan(basePackages="com.springboot.chapter.product")
@SpringCloudApplication //并不提供配置包
public class ChapterProductApplication{
}
=============================================================================
Gateway网关
解决跨域问题,不需要在每个Controller上添加@CrossOrigin注解
跨域问题是浏览器对于ajax请求的一种安全限制:一个月面发起的ajax请求,只能是与当前页域名相同的路径,这能有效的阻止跨站攻击。
替代NetflixZuul
提供了统一的路由方式,并且基于Filer链的方式提供了网关基本的功能,例如:安全、监控/埋点、限流等
一、创建一个maven模块,添加依赖
spring-cloud-starter-gateway
spring-cloud-starter-alibaba-nacos-discovery
二、配置文件application.properties
server.port=80
spring.application.name=service-gateway
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
#开启服务发现路由
spring.cloud.gateway.discovery.locator.enabled=true
#设置路由id
spring.coud.gateway.routes[0].id=service-hosp
#设置路由的uri
spring.cloud.gateway.routes[0].uri=lb://service-hosp
#设置路由断言,代理serviceId为auth-service的/auth/路径
spring.cloud.gateway.routes[0].predicates=Path=/*/hosp/**
#设置路由id
spring.coud.gateway.routes[1].id=service-cmn
#设置路由的uri
spring.cloud.gateway.routes[1].uri=lb://service-cmn
#设置路由断言,代理serviceId为auth-service的/auth/路径(一个*表示一层目录)
spring.cloud.gateway.routes[1].predicates=Path=/*/cmn/**
三、添加启动类
@SpringBootApplication
public class ServerGatewayApplication{
public static void main(String[] args){
SpringApplication.run(ServerGatewayApplication.class,args);
}
}
四、配合前端测试
config/dev.env.js中的配置内容(指定网关路径)
Gateway解决跨域的配置类实现(Controller中的@CrossOrigin注解必须去掉)
@Configuration
public class CorsConfig{
@Bean
public CorsWebFilter corsFilter(){
CorsConfiguration config = new CorsConfiguration();
config.addAllowedMethod("*");
config.addAllowedOrigin("*");
config.addAllowedHeader("*");
UrlBaseCorsConfigurationSource source = new UrlBaseCorsConfigurationSource(new PathPatternParser());
source.registerCorsConfiguration("/**",config);
return new CorsWebFilter(source);
}
}
网关与用户认证的整合
- 所有请求都会经过服务网关,服务网关对外暴露服务,在网关进行统一用户认证
- 既然在网关进行用户认证,网关需要得知哪些url进行认证,所以需要对url制定规则
- Api接口异步请求的,采取url规则匹配。如:/api//auth/,凡是满足该规则都必须进行用户认证
一、创建一个Filter包,在里面创建制定规则的过滤类
@Component
public class AuthGlobalFilter implements GlobalFilter,Ordered{
private AntPathMatcher antPathMatcher = new AntPathMatcher();
@Override
public Mono<Void> filter(ServerWebExchange exchange,GatewayFilterChain chain){
ServerHttpRequest request = exchange.getRequest();
String path = request.getURI().getPath();
System.out.println("===" + path);
//内部服务接口,不允许外部访问
if(antPathMatcher.match("/**/inner/**",path)){
ServerHttpResponse response = exchange.getResponse();
return out(response,ResultCodeEnum.PERMISSION);
}
Long userId = this.getUserId(request);
//api接口,异步请求,校验用户必须登录
if(antPathMatcher.match("/api/**/auth/**",path)){
if(StringUtils.isEmpty(userId)){
ServerHttpResponse response = exchange.getResponse();
return out(response,ResultCodeEnum.LOGIN_AUTH);
}
}
return chain.fileter(exchange)
}
@Override
public int getOrder(){
return 0;
}
//api接口鉴权失败返回数据
private Mono<Void> out(ServerHttpResponse response,ResultCodeEnum resultCodeEnum){
Result result = Result.build(null,resultCodeEnum);
byte[] bits = JSONObject.toJSONString(result).getBytes(StandardCharsets.UTF_8);
DataBuffer buffer = response.bufferFactory().wrap(bits);
//指定编码,否则在浏览器中会中文乱码
response.getHeaders().add("Content-type","application/json;charset=UTF-8");
return response.writeWith(Mono.just(buffer));
}
//获取当前登录用户id
private Long getUserId(ServerHttpRequest request){
String token = "";
List<String> tokenList = request.getHeaders().get("token");
if(null != tokenList){
return JwtHelper.getUserId(token);
}
return null;
}
}
二、前端request.js
import axios from 'axios'
import {MessageBox,Message} from 'element-ui'
import cookie from 'js-cookie'
//创建axios实例
const service = axios.create({
baseURL:'http://localhost'//网关路径
timeout:15000 //请求超时时间
})
//http request 拦截器
service.interceptors.request.use(
config => {
//判断cookie是否有token里面
if(cookie.get('token')){
//将token值放到cookie里面
config.headers['token'] = cookie.get('token')
}
return config
},
err => {
return Promise.reject(err)
}
)
//http response拦截器
service.interceptors.response.use(
response => {
if(response.data.code===208){
//后端返回的状态码208,就弹出登录弹框
loginEvent.$emit('loginDialogEvent')
return
}else{
if(response.data.code !== 200){
Message({
message:response.data.message,
type:'error',
duration:5*1000
})
return Promise.reject(response.data)
}else{
return response.data
}
}
)