zuul概述
在微服务架构盛行的年代,我们将一个大型的系统,拆解成各个服务,要完成一个业务逻辑,就可能需要,调用不同主机或不同端口的接口,这样的话看似清晰的服务拆分,实则杂乱无章。这样就我们就需要一个面向服务治理,服务编排的组件—-微服务网关,于是乎zuul就出现了。
zuul用一句话来就是,网站到后端程序所有请求的前门。zuul本质上是由一系列的filter所构成的责任链。
网关一般需要具备如下功能:
- 认证和鉴权
- 动态路由
- 流量管理转发限流
目前spring cloud gateway功能和性能更好,本文介绍zuul。
注意:Hystrix和Ribbon的超时时间,较小的值生效,Hystrix超时时间要设置比Ribbon大,不然熔断失效。 ribbon 总超时时间(conn_time read_time)(MaxAutoRetries 1) (MaxAutoRetriesServer 1)
zuul入门案例
1.引入依赖
代码语言:javascript复制<!--需要注册到 eureka -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
2.启动类
代码语言:javascript复制@SpringBootApplication(scanBasePackages = { "com.ding" })
@EnableDiscoveryClient
@EnableZuulProxy
public class ZuulApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulApplication.class,args);
}
}
3.配置文件
代码语言:javascript复制zuul:
routes:
cloud-member:
path: /member/**
serviceId: cloud-member
这样的话我们使用/member/开头的url都会转发到cloud-member服务
cloud-member项目的配置文件指定名字这样注册到eureka就能被发现
代码语言:javascript复制spring:
application:
name: cloud-member
我们访问 http://zuu-ip:zuul-port/member/list就能转发到cloud-member项目的list接口
zuul常见配置
1.路由配置规则
代码语言:javascript复制zuul:
routes:
cloud-member:
path: /member/**
serviceId: cloud-member
上例中我们这样配置,还有比较简单的配置
代码语言:javascript复制zuul:
routes:
cloud-member: /member/**
默认映射到cloud-member
zuul:
routes:
cloud-member:
zuul:
routes:
cloud-member:
path: /member/**
url: http://cloud-member-ip:cloud-member-port
配置多个项目
代码语言:javascript复制zuul:
routes:
cloud-order:
path: /order/**
serviceId: cloud-order
cloud-member:
path: /member/**
serviceId: cloud-member
路由本地转发
有时候我们需要zuul项目中转发到zuul本身的接口
代码语言:javascript复制@RequestMapping("/callBack")
@ResponseBody
public String callBack(String code,String state) throws Exception{
System.out.println("code:::::" code);
return "dddd";
}
配置文件
代码语言:javascript复制zuul:
routes:
cloud-zuul:
path: /zuul/**
url: forward:/callback
这样的话我们访问zuul接口的时候都会跳转到callback处理
通配符
路由重定向ip变化
我们经常会这样客户通过zuul访问认证服务,认证服务认证成功后重定向到欢迎页面,这个时候发现浏览器的地址变成了认证服务的ip,这样认证服务就暴露了。我们可以通过配置add-host-header
来处理这样的问题
zuul:
add-host-header: true
routes:
cloud-order:
path: /order/**
serviceId: cloud-order
重试机制
由于各种各样的原因我们可能会出现请求偶然失败,这时候我们就可能需要使用ribbon的重试功能,zuul默认结合ribbon
代码语言:javascript复制zuul:
retryable: true #开启重试
ribbon:
MaxAutoRetries: 1 #同一个服务重试次数(不算第一次)
MaxAutoRetriesServer: 1 #切换相同服务数量
spring:
cloud:
loadbalancer:
retry:
enabled: true #默认就是开启的
根据上面配置,当第一次请求失败的时候,会就同一个服务重试1次(MaxAutoRetries),如果还是失败就切换到另一服务重试(MaxAutoRetriesServer决定切换次数) 上面配置一共会调用4次(原本主机1次,失败重试,切换主机调用一次 失败重试1次) ribbon 总超时时间(conn_time read_time)(MaxAutoRetries 1) (MaxAutoRetriesServer 1)
重试要保证接口的幂等性
路由前缀
代码语言:javascript复制zuul:
add-host-header: true
prefix: /pre #前缀
routes:
cloud-member:
path: /member/**
serviceId: cloud-member
我们访问 http://zuu-ip:zuul-port/pre/member/list就能转发到cloud-member项目的list接口
敏感头问题
我们这样将认证信息放在请求header中,cookie就是其中一种,或者将信息加密放在Authorization中,就会暴露给下层服务,zuul提供了配置sensitiveHeaders
来切断zuul与下层服务的交互
sensitiveHeaders会过滤客户端附带的headers 例如:sensitiveHeaders: X-ABC 如果客户端在发请求是带了X-ABC,那么X-ABC不会传递给下游服务
ignoredHeaders会过滤服务之间通信附带的headers 例如:ignoredHeaders: X-ABC 如果客户端在发请求是带了X-ABC,那么X-ABC依然会传递给下游服务。但是如果下游服务再转发就会被过滤
还有一种情况就是客户端带了X-ABC,在ZUUL的Filter中又addZuulRequestHeader(“X-ABC”, “new”), 那么客户端的X-ABC将会被覆盖,此时不需要sensitiveHeaders。如果设置了sensitiveHeaders: X-ABC,那么Filter中设置的X-ABC依然不会被过滤。
ZUUL服务都是可以接受到敏感信息的。至于传不传给后台服务就看配置了。
代码语言:javascript复制zuul:
add-host-header: true
routes:
#cloud-order:
#path: /order/**
#serviceId: cloud-order
cloud-member:
path: /member/**
sensitiveHeaders: Cookie,Set-Cookie,Authorization
serviceId: cloud-member
服务/接口屏蔽
有时候我们不需要将某些服务或者接口暴露出去我们可以通过配置来实现屏蔽
代码语言:javascript复制zuul:
ignored-services: cloud-order #屏蔽服务
ignored-patterns: /**/div/** #屏蔽接口
add-host-header: true
routes:
#cloud-order:
#path: /order/**
#serviceId: cloud-order
cloud-member:
path: /member/**
sensitiveHeaders: Cookie,Set-Cookie,Authorization
serviceId: cloud-member
饥饿加载
zuul内部是默认是ribbon调用远程服务的,由于ribbon的原因,第一次经过zuul调用回去注册中心查询服务注册表,初始化ribbon负载均衡信息,这是一种懒加载策略,但是这个过程会耗时比较久,所以我们可以设置启动zuul的时候就去加载。
代码语言:javascript复制zuul:
ribbon:
eager-load:
enable: true
使用 OKHttp 替换 HttpClient
在Java平台上,Java 标准库提供了 HttpURLConnection 类来支持 HTTP 通讯。不过 HttpURLConnection 本身的 API 不够友好,所提供的功能也有限。大部分 Java 程序都选择使用 Apache 的开源项目 HttpClient 作为 HTTP 客户端。Apache HttpClient 库的功能强大,使用率也很高,基本上是 Java 平台中事实上的标准 HTTP 客户端。
zuul默认是使用:Apache HttpClient,但是HttpClient由于难于扩展等原因,慢慢的被弃用了。我们可以使用okhttp来替换
okhttp优点: 1.对同一个主机发出的所有请求都可以共享相同的套接字连接。(对HTTP/2 、spdy支持,对同一个host的请求进行合并) 2.OkHttp 会使用连接池来复用连接以提高效率。 3.OkHttp 提供了对 GZIP 的默认支持来降低传输内容的大小。 4.OkHttp 也提供了对 HTTP 响应的缓存机制,可以避免不必要的网络请求。 5.当网络出现问题时,OkHttp 会自动重试一个主机的多个 IP 地址。
1.引入依赖
代码语言:javascript复制<!-- OKHttp 支持 -->
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
</dependency>
2.修改配置文件
Ribbon 配置
代码语言:javascript复制ribbon:
httpclient:
# 关闭 httpclient 支持
enabled: false
okhttp:
# 开启 okhttp 支持
enabled: true
zuul拦截器
zuul本质上是一系列拦截器构成的责任链。 zuul拦截器之前是不能直接通信的,zuul提供RequestContext来实现拦截器之前共享状态,通信,本质上来说RequestContext是底层使用了ThreadLocal下面会有实例。
拦截器类型
- pre filters 在zuul路由带下级服务之前执行,一般用来鉴权、限流
- routing filters zuul路由动作的执行者,是发送构建和发送http请求的地方
- post filters 源服务返回结果或者异常信息发生后执行的,可以对返回结果加工处理
- error filters 在生命周期内如果执行出现异常,则会进入这个类型,这里可以做全局异常处理
从上图可以看出 pre或者routing类型执行报错post类型的也会执行拦截器执行顺序 相同类型的拦截器,数字越低,优先级也高 使用filterOrder()来定义,后面会有例子。 不同类型的拦截器,优先级高到低 pre > routing > post
官网推荐的一些拦截器的执行顺序定义 当我们需要做限流的时候,可以在pre拦截器类型做,并且优先级要最高,比鉴权都要高,可以使用
代码语言:javascript复制@Override
public int filterOrder() {
return FilterConstants.SERVLET_DETECTION_FILTER_ORDER -1 ;
}
如果是普通pre类型拦截器order可以使用
代码语言:javascript复制@Override
public int filterOrder() {
return FilterConstants.PRE_DECORATION_FILTER_ORDER -1 ;
}
如果是post类型的拦截器order可以使用
代码语言:javascript复制@Override
public int filterOrder() {
return FilterConstants.SEND_RESPONSE_FILTER_ORDER -1 ;
}
拦截器生命周期
自定义拦截器实例
1.继承ZuulFilter类 2.实现shouldFilter、filterOrder、filterType、run方法
- shouldFilter:表示当前拦截器执不执行,可以作为开关,返回true执行,false不执行
- filterOrder:返回数字,越小优先级越高
- filterType:指定拦截器的类型 pre、route、post、error
- run:具体的逻辑,业务处理
@Component
public class FirstFilter extends ZuulFilter{
/**
* 表示当前的Filter执行不执行
* 返回true 执行run方法 false则不执行
*/
@Override
public boolean shouldFilter() {
return true;
}
/**
* 定义同类型的Filter的执行顺序
* 值越小 越早执行
*/
@Override
public int filterOrder() {
return 0;
}
/**
* 表示具体的Filter类型
*/
@Override
public String filterType() {
return "pre";
}
/**
* 具体的Filter逻辑
*/
@Override
public Object run() throws ZuulException {
System.out.println("FirstFilter run....................pre.................");
//RequestContext使用这个来共享状态
RequestContext rc = RequestContext.getCurrentContext();
HttpServletRequest req = rc.getRequest();
String orderNo = req.getParameter("orderNo");
if(null == orderNo || "".equals(orderNo)){
rc.setResponseBody("{"message":"order is not null"}");
//禁止路由到下游服务
rc.setSendZuulResponse(false);
//false表示不执行同类型的 下面的Filter 但是post的还会执行
rc.set("isExecFilter",false);
}
rc.set("isExecFilter",true);
return null;
}
}
@Component
public class SecondFilter extends ZuulFilter{
/**
* 表示当前的Filter执行不执行
* 返回true 执行run方法 false则不执行
*/
@Override
public boolean shouldFilter() {
RequestContext rc = RequestContext.getCurrentContext();
System.out.println("SecondFilter=======================" rc.get("isExecFilter"));
return (boolean)rc.get("isExecFilter");
}
/**
* 定义同类型的Filter的执行顺序
* 值越小 越早执行
*/
@Override
public int filterOrder() {
// TODO Auto-generated method stub
return 1;
}
/**
* 表示具体的Filter类型
*/
@Override
public String filterType() {
return "pre";
}
/**
* 具体的Filter逻辑
*/
@Override
public Object run() throws ZuulException {
System.out.println("SecondFilter run ..............pre............");
return null;
}
}
zuul高可用
我们知道网关是一切请求的入口,如果网关挂了,所有的请求都挂了。所以我们要保证网关的高可用。
如果我们使用了zuul作为网关的话,实现高可用是很简单的,这里我分为两种:
一种是服务之间(eureka的客户端) 我们可以将多个zuul注册到注册中心eureka中,那么service A,B,C也注册到注册中心,Zuul客户端会自动从Eureka Server中查询Zuul Server的列表,并使用Ribbon负责均衡地请求Zuul集群。
另个一种,是用户手机电脑等不是eureka的客户端,即没有注册到注册中心 这种我们多个zuul,不知道要访问那个zuul我们可以使用nginx来对zuul进行负载均衡,nginx在使用keeplived来实现高可用。
当我们使用nginx作为负载的时候,nginx跟zuul是没有感知功能的,当zuul挂掉的时候,nginx是不会将挂掉zuul踢出去的,这样子就会出现分配到这个zuul的请求失败。
所以我们可以结合nginx lua zuul来完美处理这个问题
可以写lua脚本定时去注册中心获取 up状态的zuul服务集群进行负载均衡
参考 https://www.ctolib.com/SpringCloud-nginx-zuul-dynamic-lb.html
zuul限流
网关是一切请求的入口,我们可以在网关使用pre拦截器,对请求进行限流。并且优先级要最高,比鉴权都要高,可以使用来定义order
代码语言:javascript复制@Override
public int filterOrder() {
return FilterConstants.SERVLET_DETECTION_FILTER_ORDER -1 ;
}
我们可以使用这个开源的工具对api进行限流 https://www.cnblogs.com/lihaoyang/p/12127768.html https://github.com/marcosbarbero/spring-cloud-zuul-ratelimit
注意:我们不能将所有的限流都放在网关上来做,网关一般微服务外的请求进行限流,而服务之间一般不会经过网关,都是服务间之间调用,所以这个时候网关的限流没用,服务之间一般使用熔断。 网关主要为服务器硬件设备的并发处理能力做限流。细粒度的限流还是交给专门的熔断限流微服务去处理,这样利于各微服务之间的解构和各团队的协同开发。
zuul动态路由
动态路由原理
zuul在启动的时候会将配置文件中的映射规则加载到内存中,但是当我们需要修改映射规则,我们就需要重启zuul网关,让其生效。那么有没有一种方法来实现不重启zuul服务,直接让配置文件实时生效——这就是动态路由
要实现动态路由有两种方案 一、使用配置中心spring-clould-config。二、将配置信息放在mysql中,手动触发更新或者等待心跳租约触发。通常都是使用配置中心。
现在我们来了解一下zuul实现动态路由的原理、
zuul提供了SimpleRouteLocator这个类可以用来加载配置文件的路由和刷新路由。从下图我们可以看出doRefresh()用来刷新路由,如果实现RefreshableRouteLocator接口重写refresh()并调用doRefresh()可以实现动态路由,locateRoutes()方法只会从配置文件加载静态的配置,如要动态路由子类要重写这个方法。
SimpleRouteLocator 提供了静态路由加载,没有动态刷新的功能 RefreshableRouteLocator 提供了动态刷新功能。实现refresh(),并调用doRefresh(),从而调用locateRoutes()来重新加载路由 DiscoveryClientRouteLocator zuul提供的,继承SimpleRouteLocator并实现RefreshableRouteLocator接口,能加载配置文件路由,并动态感知注册中心是不是新增注册服务,实现注册中心的动态路由。
所以我们要动态路由必须重新继承SimpleRouteLocator并实现RefreshableRouteLocator接口。
zuul是使用事件刷新机制的。
zuul会注册一个监听器,当接收到特定的事件HeartbeatEvent、RoutesRefreshedEvent等就会将 this.zuulHandlerMapping.setDirty(true);设置为脏数据,这样映射路由转发的时候会判断是不是脏数据是的话,就会重新加载一次路由配置。
所以我们要手动触发路由更新的时候我们可以自己一个controller调用service,service用来发布事件。 或者等待心跳租约触发。
基于mysql动态路由
0.表结构定义参考zuul的ZuulRoute类
1.定义一个路由加载
代码语言:javascript复制public class DynamicZuulRouteLocator extends SimpleRouteLocator implements RefreshableRouteLocator {
@Autowired
private ZuulProperties properties;
@Autowired
private IZuulRouteService zuulRouteService;
public DynamicZuulRouteLocator(String servletPath, ZuulProperties properties) {
super(servletPath, properties);
this.properties = properties;
}
//实现RefreshableRouteLocator的refresh从而能实现动态路由
@Override
public void refresh() {
doRefresh();
}
//重写locateRoutes方法从数据库加载路由配置
@Override
protected Map<String, ZuulRoute> locateRoutes() {
LinkedHashMap<String, ZuulRoute> routesMap = new LinkedHashMap<>();
routesMap.putAll(super.locateRoutes());//配置文件的静态路由
routesMap.putAll(zuulRouteService.getZuulRoutes());//从数据库加载
LinkedHashMap<String, ZuulRoute> values = new LinkedHashMap<>();
routesMap.forEach((key, value) -> {//前缀处理
String path = key;
if (!path.startsWith("/")) {
path = "/" path;
}
if (StringUtils.hasText(this.properties.getPrefix())) {
path = this.properties.getPrefix() path;
if (!path.startsWith("/")) {
path = "/" path;
}
}
values.put(path, value);
});
return values;
}
}
2.将我们定义的路由加载器交给spring管理
代码语言:javascript复制@Configuration
public class DynamicZuulConfig {
@Autowired
private ZuulProperties zuulProperties;
@Autowired
private ServerProperties serverProperties;
@Bean
public DynamicZuulRouteLocator routeLocator() {
DynamicZuulRouteLocator routeLocator = new DynamicZuulRouteLocator(
serverProperties.getServlet().getServletPrefix(), zuulProperties);
return routeLocator;
}
}
以上这样就可以实现动态路由了(心跳租约触发)
zuul提供了CompositeRouteLocator,这个类可以整合多个RouteLocator,在项目启动的时候会将RouteLocator类型的bean注入CompositeRouteLocator,CompositeRouteLocator会调用getRoutes()、getMatchingRoute()、refresh() 时都会逐一调用每个RouteLocator相应的方法。
最后我们还有一个步骤就是要手动触发,我们通过手动发布事件来触发
代码语言:javascript复制@Service
public class RefreshRouteService {
@Autowired
ApplicationEventPublisher publisher;
@Autowired
RouteLocator routeLocator;
public void refreshRoute() {
RoutesRefreshedEvent routesRefreshedEvent = new RoutesRefreshedEvent(routeLocator);
publisher.publishEvent(routesRefreshedEvent);
}
}
@RestController
public class RefreshController {
@Autowired
RefreshRouteService refreshRouteService;
@GetMapping("/refreshRoute")
public String refresh() {
refreshRouteService.refreshRoute();
return "refresh success";
}
}
参考 https://blog.51cto.com/4925054/2136267 https://www.jianshu.com/p/5e5197e00d65
zuul鉴权
我们结合Spring Security OAuth2 1.token可以使用和JWT 2.也可以保存在数据库或redis
OAuth2是一种协议我们简单介绍一下
OAuth2有4个角色
- 资源所有者:指的是用户, 2.认证、授权服务器:用于发放访问令牌给客户端 cloud-auth 3.资源服务器:资源服务器存放受保护资源,要访问这些资源,需要获得访问令牌 这里可以是cloud-order、cloud-member 4.客户端:客户端代表请求资源服务器资源的第三方程序,这里是cloud-zuul
一般来说我们可能有多个资源服务器(cloud-order、cloud-member里面的接口就是我们的资源),一个统一认证授权服务器。
客户端需要对配置资源服务器放行 需要在HttpSecurity放行资源服务器和授权服务器 资源服务器需要对自己的接口权限校验 需要在HttpSecurity配置
工作流程
下面流程如果出现401的情况,可以切换一下get或post分别试一下。或者检查参数的完整性,缺少参数也会401
1.客户端cloud-zuul 将自己注册到认证服务器cloud-auth,一般会有client_id和client_secret,这里使用了password模式,和 refresh_token
代码语言:javascript复制INSERT INTO `token`.`oauth_client_details` (`client_id`, `resource_ids`, `client_secret`, `scope`, `authorized_grant_types`, `web_server_redirect_uri`, `authorities`, `access_token_validity`, `refresh_token_validity`, `additional_information`, `autoapprove`) VALUES ('cloud-zuul', 'cloud-order', '123456', '1234561', 'password,refresh_token', NULL, '123456', '300', '7200', NULL, NULL);
2.用户将账号密码给zuul,zuul拿着账号密码发一个请求给auth申请token(使用password),auth返回token给zuul,zuul写入cookie,如果是支持refresh_token还会返回 refresh_token(不能给用户,保存在zuul中数据库,redis中)
代码语言:javascript复制post http://localhost:10090/auth/oauth/token?username=admin88&password=123456&grant_type=password&scope=1234561&client_id=cloud-zuul&client_secret=123456
get不支持
{
"access_token": "bf6ee0a0-6680-4055-a86a-60b193cc460d",
"token_type": "bearer",
"refresh_token": "39b836bb-2c76-4e95-81f6-1c566752f157",
"expires_in": 299,
"scope": "1234561"
}
3.之后用户就可以拿token来请求资源服务器了
代码语言:javascript复制。直接访问 资源服务器
get http://localhost:10092/orderInfo/getMemberByOrderNo?orderNo=123456
没有带token的情况
{
"error": "unauthorized",
"error_description": "Full authentication is required to access this resource"
}
在header中放入token key:Authorization value:bearer 3b78f74e-f87e-4c58-807c-6f54890b9748
注意有个空格
成功访问到资源服务器
2.通过zuul网关去访问资源服务器
get http://localhost:10086/order/orderInfo/getMemberByOrderNo?orderNo=123456
在zuul的WebSecurityConfig中要对资源服务器放行
@Override
protected void configure(HttpSecurity http) throws Exception {
//错误写法 放行不了会报401 不知道为什么放行不了 不知道为什么不放行会401,只能放行让资源服务器校验toekn
//http
//.authorizeRequests().anyRequest() .authenticated()
//.antMatchers("/login/**","/callBack/**","/dynamicZuul/**","/callBack/login/**","/order/**") .permitAll()
//.and() .csrf() .disable();
//正确写法,不知道为什么上面不行
http
.authorizeRequests()
.antMatchers("/callBack/**", "/order11/**", "/about").permitAll()
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/db/**").access("hasRole('ADMIN') and hasRole('DBA')")
.anyRequest().authenticated();
}
即使这样做了还不行。还是会401,因为敏感头的问题
zuul:
sensitiveHeaders:
这样就可以访问
4.上面说的token都是指access_token:一般过期时间较短(试了默认12小时),refresh_token的过期时间较长 当access_token过期了,用户就要重新登录授权。。一般来说我们为了安全会将access_token设置过期时间较短,这样的话用户就要经常重新登录授权比较麻烦,所以我们可以使用refresh_token进行刷新access_token。
代码语言:javascript复制post http://localhost:10090/auth/oauth/token?grant_type=refresh_token&refresh_token=39b836bb-2c76-4e95-81f6-1c566752f157&client_id=cloud-zuul&client_secret=123456
注意带上client_id=cloud-zuul&client_secret=123456不然会401
https://blog.csdn.net/u012040869/article/details/80140515
auth会报错 refresh_token UserDetailsService is required
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
//endpoints.tokenStore(jwtTokenStore());
//endpoints.tokenEnhancer(jwtTokenConverter());
endpoints.tokenStore(new JdbcTokenStore(dataSource));
endpoints.authenticationManager(authenticationManager);
//refresh_token UserDetailsService is required https://www.jianshu.com/p/ea5ccaddaef7
endpoints.userDetailsService(baseUserDetailService);
}
退出
代码语言:javascript复制@Autowired
private ConsumerTokenServices consumerTokenServices;
/**
*注销access_token,注销access_token的同时refresh_token也会被注销掉
* @param access_token
* @return
*/
@RequestMapping(value = "/member/exit",method=RequestMethod.DELETE)
@ResponseBody
public Result<String> revokeToken(String access_token) {
if (access_token!=null && !"".equals(access_token) && consumerTokenServices.revokeToken(access_token)) {
return Result.ok("成功注销");
} else {
return Result.error("注销失败");
}
}
delete http://localhost:10090/auth/member/exit?access_token=cc6312ca-f3a8-4711-bd95-6dc0adb1e3aa
注意 退出登录是写在授权服务器中,所以授权服务器也是资源服务器。需要对访问的接口放行
授权服务器的HttpSecurity配置
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/member/**").permitAll().and().csrf().disable();
}
demo git地址 git@github.com:348786639/cloud-parent.git cloud-parent项目中的拦截器优先于 oauth2 拦截器执行
参考 https://www.jianshu.com/p/68f22f9a00ee