服务网关是什么
-
一般的流量入口是商品模块,假设商品模块有100个,则nginx要配置100个,为了减少配置量,有一个服务网关的概念
- pc要访问互联网,需要网关进行路由转发,类比到springcloud,就有了服务网关的概念
- 访问服务时,首先仍然是nginx,但是连接的不再是商品模块,而是zuul服务网关,由它来负责转发,这样可以减少nginx的配置量,此时由服务网关负责路由到100台商品模块
- 服务网关除了zuul,还有gateway,后者源码更复杂,但是原理都是一样的
-
服务网关可以做流量入口,所有微服务的请求必须经过网关
-
因为所有流量都要经过网关,因此可以做权限校验
-
服务网关除了路由到自己的微服务,还可以路由到其他的第三方应用
zuul服务网关搭建
导入jar包
-
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-zuul</artifactId> </dependency>
启动类
-
package com.xiangxue.jack; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.cloud.context.config.annotation.RefreshScope; import org.springframework.cloud.netflix.zuul.EnableZuulProxy; import org.springframework.cloud.netflix.zuul.filters.ZuulProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Primary; @SpringBootApplication @EnableZuulProxy //@EnableZuulServer public class MicroZuulApplication { public static void main(String[] args) { SpringApplication.run(MicroZuulApplication.class,args); } @Bean @RefreshScope @ConfigurationProperties("zuul") @Primary public ZuulProperties zuulProperties() { return new ZuulProperties(); } }
- @EnableZuulProxy注解
配置文件
-
# 必须连接Eureka,从Eureka中获取每一个服务的服务列表 # zuul天生就有ribbon和hystrix的功能 eureka.client.serviceUrl.defaultZone=http://admin:admin@localhost:8763/eureka/ # 使用路径方式匹配路由规则。 # 参数key结构: zuul.routes.customName.path=xxx # 用于配置路径匹配规则。 # 其中customName自定义。通常使用要调用的服务名称,方便后期管理 # 可使用的通配符有: * ** ? # ? 单个字符 # * 任意多个字符,不包含多级路径 # ** 任意多个字符,包含多级路径 zuul.routes.micro-web.path=/web/** # 参数key结构: zuul.routes.customName.url=xxx # url用于配置符合path的请求路径路由到的服务地址。 #zuul.routes.micro-order.url=http://localhost:8080/ # key结构 : zuul.routes.customName.serviceId=xxx # serviceId用于配置符合path的请求路径路由到的服务名称。 zuul.routes.micro-web.serviceId=micro-web-no #zuul.routes.micro-web1.path=/web/path/** #zuul.routes.micro-web1.serviceId=micro-web-no # ignored service id pattern # 配置不被zuul管理的服务列表。多个服务名称使用逗号','分隔。 # 配置的服务将不被zuul代理。 #zuul.ignored-services=eureka-application-service # prefix URL pattern 前缀路由匹配 # 配置请求路径前缀,所有基于此前缀的请求都由zuul网关提供代理。 #zuul.prefix=/api management.endpoints.web.exposure.include=* #指定全局的headers传输,对所有路由的微服务 #zuul.sensitive-headers=Cookie,Set-Cookie,Authorization #添加host头信息,标识最初的服务端请求地址 zuul.add-host-header=true #默认添加 X-Forwarded-*头域 zuul.add-proxy-headers=true #路由到zuul本地的/local的接口 zuul.routes.zuul-server.path=/local/** zuul.routes.zuul-server.url=forward:/local zuul.routes.blog.path=/blog/** zuul.routes.blog.url=http://localhost:8003/ #全局关闭重试 zuul.retryable=false #关闭该服务的重试 zuul.routes.micro-web.retryable=false
-
zuul.routes.micro-web.path=/web/**
zuul.routes.micro-web.serviceId=micro-web-no
如果请求的url中开头是/web,就会把它的请求路由到这个serviceId
- zuul.routes.micro-web.path中的micro-web随便起,关键等号后面的内容要配合起来
-
测试
- 启动Eureka、micro-order-no、micro-web-no、config-server、zuul这几个项目
请求接口查看zuul所有的路由规则
-
http://localhost:7070/actuator/routes
-
#访问/web/**时,会路由到服务名为micro-web的服务 "/web/**": "micro-web-no", #访问/local/**时,表示会路由到自己本身 "/local/**": "forward:/local", #只会路由到一台主机 "/blog/**": "http://localhost:8003/"
-
-
http://localhost:7070/web/user/queryUser
- 请求到Web工程的UserController的queryUser方法
zuul的路由配置
-
# 此方式相当于给所有新发现的服务默认排除zuul网关访问方式,只有配置了路由网关的服务才可以通过zuul网关访问 # 通配方式配置排除列表。 zuul.ignored-services=*
- 排除所有的
-
# 通配方式配置排除网关代理路径。所有符合ignored-patterns的请求路径都不被zuul网关代理。 zuul.ignored-patterns=/**/local/**
-
# http://localhost:6060/actuator/hystrix.stream #针对某个服务传输指定的headers信息 ,默认是过滤掉 Cookie,Set-Cookie,Authorization 这三个信息的 #这里置空就是不要过滤掉这三个 zuul.routes.micro-web.sensitive-headers=
- 在权限校验中讲到
- 会把敏感资源过滤掉,请求过程中默认会把带了Cookie,Set-Cookie,Authorization这三种信息的过滤掉
- 所以添加这条配置,并设置为null,就是不拦截敏感请求头
动态路由
-
需要用到分布式配置中心
-
把zuul中的application.properties改为application1.properties,并且把分布式配置中心的配置文件拷贝过来
-
spring.application.name=api-gateway server.port=7070 eureka.client.serviceUrl.defaultZone=http://admin:admin@localhost:8763/eureka/ spring.cloud.config.profile=dev spring.cloud.config.label=master #这种配置是configserver还单机情况,直接连接这个单机服务就行 spring.cloud.config.uri=http://localhost:8085/ #configserver高可用配置 #开启configserver服务发现功能 #spring.cloud.config.discovery.enabled=true #服务发现的服务名称 #spring.cloud.config.discovery.service-id=config-server #如果连接不上获取配置有问题,快速响应失败 spring.cloud.config.fail-fast=true #默认重试的间隔时间,默认1000ms spring.cloud.config.retry.multiplier=1000 #下一间隔时间的乘数,默认是1.1 #spring.cloud.config.retry.initial-interval=1.1 #最大间隔时间,最大2000ms spring.cloud.config.retry.max-interval=2000 #最大重试次数,默认6次 spring.cloud.config.retry.max-attempts=6 ribbon.ConnectTimeout=1000 ribbon.ReadTimeout=2000 hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=5000 spring.rabbitmq.host=192.168.67.139 spring.rabbitmq.port=5672 spring.rabbitmq.username=admin spring.rabbitmq.password=admin management.endpoints.web.exposure.include=*
-
这里仍然是用github作为配置文件存放地
-
zuul的主类需要修正
-
@Bean @RefreshScope @ConfigurationProperties("zuul") @Primary public ZuulProperties zuulProperties() { return new ZuulProperties(); }
- 增加@RefreshScope的注解,这个注解的里面又有一个@Scope(“refresh”)的注解,跟之前自定义的zookper实现的动态配置是相同的处理
- 这个注解起到标识作用,标识这个ZuulProperties可能会动态刷新,不会在一级缓存中管理,而是在自己定义的缓存中管理
- 增加@RefreshScope的注解,这个注解的里面又有一个@Scope(“refresh”)的注解,跟之前自定义的zookper实现的动态配置是相同的处理
zuul中的过滤器
- 有四种过滤器
- pre:前置
- post:后置,路由过滤器做完了,下游系统把值返回以后调用
- error:调用其余3种过滤器有异常了,就会进入error的过滤器
- route:路由,很少用,route过滤器是负责做路由的,自己写路由规则,但是因为zuul中已经把路由写得比较完善了,没有什么好补充的
过滤器的调用顺序
- 请求来到zuul,会有过滤器,先调pre的过滤器,再调route的过滤器,而路由过滤器是做后端服务调用的,下游系统会把结果返回给route的过滤器,再调用post的过滤器
自定义过滤器
前置过滤器
-
package com.xiangxue.jack.filter; import com.netflix.zuul.ZuulFilter; import com.netflix.zuul.context.RequestContext; import com.netflix.zuul.exception.ZuulException; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang.StringUtils; import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants; import javax.servlet.http.HttpServletRequest; @Slf4j //@Component public class AccessFilter extends ZuulFilter { @Override public String filterType() { // 标明是前置过滤器 return FilterConstants.PRE_TYPE; } @Override public int filterOrder() { // 优先级,值越小越优先执行 return 0; } @Override public boolean shouldFilter() { // 判断是否调用该过滤器 // 拿到当前上下文对象,从中再获取sendZuulResponse的值,这个值默认是true return RequestContext.getCurrentContext().sendZuulResponse(); } @Override public Object run() throws ZuulException { //获取上下文 RequestContext ctx = RequestContext.getCurrentContext(); //获取Request HttpServletRequest request = ctx.getRequest(); //获取请求参数accessToken String accessToken = request.getParameter("accessToken"); //使用String工具类 if (StringUtils.isBlank(accessToken)) { log.warn("accessToken is empty"); //设置为false不进行路由 ctx.setSendZuulResponse(false); //进行拦截 ctx.setResponseStatusCode(401); try { ctx.getResponse().getWriter().write("accessToken is empty"); } catch (Exception e) { } return null; } log.info("access is ok"); return null; } }
-
package com.xiangxue.jack.filter; import com.netflix.zuul.ZuulFilter; import com.netflix.zuul.context.RequestContext; import com.netflix.zuul.exception.ZuulException; import lombok.extern.slf4j.Slf4j; import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants; import javax.servlet.http.HttpServletRequest; @Slf4j //@Component public class PreLogFilter extends ZuulFilter { /* * pre: 这种过滤器在请求被路由之前调用。可利用这种过滤器实现身份验证、在集群中选择请求的微服务,记录调试信息等。 routing: 这种过滤器将请求路由到微服务。这种过滤器用于构建发送给微服务的请求,并使用apache httpclient或netflix ribbon请求微服务。 post: 这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准的http header、收集统计信息和指标、将响应从微服务发送给客户端等。 error: 在其他阶段发送错误时执行该过滤器。 * * */ @Override public String filterType() { return FilterConstants.PRE_TYPE; } @Override public int filterOrder() { return 1; } @Override public boolean shouldFilter() { return RequestContext.getCurrentContext().sendZuulResponse(); } @Override public Object run() throws ZuulException { RequestContext currentContext = RequestContext.getCurrentContext(); HttpServletRequest request = currentContext.getRequest(); log.info("zuul pre filter-->" + request.getRequestURL() + "-->" + request.getMethod()); return null; } }
-
为了打印日志
路由过滤器
-
package com.xiangxue.jack.filter; import com.netflix.zuul.ZuulFilter; import com.netflix.zuul.context.RequestContext; import com.netflix.zuul.exception.ZuulException; import com.netflix.zuul.util.HTTPRequestUtils; import lombok.extern.slf4j.Slf4j; import org.apache.http.Header; import org.apache.http.HttpHost; import org.apache.http.HttpRequest; import org.apache.http.HttpResponse; import org.apache.http.client.config.CookieSpecs; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpPatch; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPut; import org.apache.http.conn.HttpClientConnectionManager; import org.apache.http.entity.ContentType; import org.apache.http.entity.InputStreamEntity; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.message.BasicHeader; import org.apache.http.message.BasicHttpEntityEnclosingRequest; import org.apache.http.message.BasicHttpRequest; import org.springframework.cloud.commons.httpclient.ApacheHttpClientConnectionManagerFactory; import org.springframework.cloud.commons.httpclient.ApacheHttpClientFactory; import org.springframework.cloud.commons.httpclient.DefaultApacheHttpClientConnectionManagerFactory; import org.springframework.cloud.commons.httpclient.DefaultApacheHttpClientFactory; import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants; import org.springframework.cloud.netflix.zuul.util.ZuulRuntimeException; import org.springframework.http.HttpHeaders; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.util.UriTemplate; import org.springframework.web.util.UriUtils; import org.springframework.web.util.WebUtils; import javax.annotation.PostConstruct; import javax.servlet.http.HttpServletRequest; import java.io.IOException; import java.io.InputStream; import java.util.*; import java.util.concurrent.TimeUnit; import java.util.regex.Pattern; import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.REQUEST_ENTITY_KEY; import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.REQUEST_URI_KEY; @Slf4j //@Component public class RoutingFilter extends ZuulFilter { private ApacheHttpClientConnectionManagerFactory connectionManagerFactory; private ApacheHttpClientFactory httpClientFactory; private CloseableHttpClient httpClient; private HttpClientConnectionManager connectionManager; private boolean urlDecoded = true; private boolean addHostHeader = false; public static final String IGNORED_HEADERS = "ignoredHeaders"; public static final Pattern FORM_FEED_PATTERN = Pattern.compile("\f"); public static final Pattern COLON_PATTERN = Pattern.compile(":"); @Override public String filterType() { return FilterConstants.ROUTE_TYPE; } @Override public int filterOrder() { return 0; } @PostConstruct public void init() { connectionManagerFactory = new DefaultApacheHttpClientConnectionManagerFactory(); this.connectionManager = newConnectionManager(); httpClientFactory = new DefaultApacheHttpClientFactory(HttpClientBuilder.create()); httpClient = newClient(); } protected HttpClientConnectionManager newConnectionManager() { return connectionManagerFactory.newConnectionManager( false, 200, 20, -1, TimeUnit.MILLISECONDS, null); } @Override public boolean shouldFilter() { return RequestContext.getCurrentContext().sendZuulResponse(); } protected CloseableHttpClient newClient() { final RequestConfig requestConfig = RequestConfig.custom() .setConnectionRequestTimeout( -1) .setSocketTimeout(10000) .setConnectTimeout(2000) .setCookieSpec(CookieSpecs.IGNORE_COOKIES).build(); return httpClientFactory.createBuilder().setDefaultRequestConfig(requestConfig) .setConnectionManager(this.connectionManager).disableRedirectHandling() .build(); } public String buildZuulRequestURI(HttpServletRequest request) { RequestContext context = RequestContext.getCurrentContext(); String uri = request.getRequestURI(); String contextURI = (String) context.get(REQUEST_URI_KEY); if (contextURI != null) { try { uri = contextURI; if (this.urlDecoded) { uri = UriUtils.encodePath(contextURI, characterEncoding(request)); } } catch (Exception e) { log.debug( "unable to encode uri path from context, falling back to uri from request", e); } } return uri; } private String characterEncoding(HttpServletRequest request) { return request.getCharacterEncoding() != null ? request.getCharacterEncoding() : WebUtils.DEFAULT_CHARACTER_ENCODING; } private String getVerb(HttpServletRequest request) { String sMethod = request.getMethod(); return sMethod.toUpperCase(); } public boolean isIncludedHeader(String headerName) { String name = headerName.toLowerCase(); RequestContext ctx = RequestContext.getCurrentContext(); if (ctx.containsKey(IGNORED_HEADERS)) { Object object = ctx.get(IGNORED_HEADERS); if (object instanceof Collection && ((Collection<?>) object).contains(name)) { return false; } } switch (name) { case "host": if (addHostHeader) { return true; } case "connection": case "content-length": case "server": case "transfer-encoding": case "x-application-context": return false; default: return true; } } public MultiValueMap<String, String> buildZuulRequestHeaders( HttpServletRequest request) { RequestContext context = RequestContext.getCurrentContext(); MultiValueMap<String, String> headers = new HttpHeaders(); Enumeration<String> headerNames = request.getHeaderNames(); if (headerNames != null) { while (headerNames.hasMoreElements()) { String name = headerNames.nextElement(); if (isIncludedHeader(name)) { Enumeration<String> values = request.getHeaders(name); while (values.hasMoreElements()) { String value = values.nextElement(); headers.add(name, value); } } } } Map<String, String> zuulRequestHeaders = context.getZuulRequestHeaders(); for (String header : zuulRequestHeaders.keySet()) { if (isIncludedHeader(header)) { headers.set(header, zuulRequestHeaders.get(header)); } } if (!headers.containsKey(HttpHeaders.ACCEPT_ENCODING)) { headers.set(HttpHeaders.ACCEPT_ENCODING, "gzip"); } return headers; } public MultiValueMap<String, String> buildZuulRequestQueryParams( HttpServletRequest request) { Map<String, List<String>> map = HTTPRequestUtils.getInstance().getQueryParams(); MultiValueMap<String, String> params = new LinkedMultiValueMap<>(); if (map == null) { return params; } for (String key : map.keySet()) { for (String value : map.get(key)) { params.add(key, value); } } return params; } protected InputStream getRequestBody(HttpServletRequest request) { InputStream requestEntity = null; try { requestEntity = (InputStream) RequestContext.getCurrentContext() .get(REQUEST_ENTITY_KEY); if (requestEntity == null) { requestEntity = request.getInputStream(); } } catch (IOException ex) { log.error("error during getRequestBody", ex); } return requestEntity; } @Override public Object run() throws ZuulException { RequestContext currentContext = RequestContext.getCurrentContext(); StringBuilder sb = currentContext.getFilterExecutionSummary(); HttpServletRequest request = currentContext.getRequest(); MultiValueMap<String, String> headers = this .buildZuulRequestHeaders(request); MultiValueMap<String, String> params = this .buildZuulRequestQueryParams(request); String verb = getVerb(request); InputStream requestEntity = getRequestBody(request); String uri = this.buildZuulRequestURI(request); try { // 跳进这个方法看 CloseableHttpResponse response = forward(this.httpClient, verb, uri, request, headers, params, requestEntity); setResponse(response); } catch (Exception ex) { throw new ZuulRuntimeException(ex); } return null; } private void setResponse(HttpResponse response) throws IOException { RequestContext.getCurrentContext().set("zuulResponse", response); this.setResponse(response.getStatusLine().getStatusCode(), response.getEntity() == null ? null : response.getEntity().getContent(), revertHeaders(response.getAllHeaders())); } private MultiValueMap<String, String> revertHeaders(Header[] headers) { MultiValueMap<String, String> map = new LinkedMultiValueMap<>(); for (Header header : headers) { String name = header.getName(); if (!map.containsKey(name)) { map.put(name, new ArrayList<String>()); } map.get(name).add(header.getValue()); } return map; } public void setResponse(int status, InputStream entity, MultiValueMap<String, String> headers) throws IOException { RequestContext context = RequestContext.getCurrentContext(); context.setResponseStatusCode(status); if (entity != null) { context.setResponseDataStream(entity); } boolean isOriginResponseGzipped = false; for (Map.Entry<String, List<String>> header : headers.entrySet()) { String name = header.getKey(); for (String value : header.getValue()) { context.addOriginResponseHeader(name, value); if (name.equalsIgnoreCase(HttpHeaders.CONTENT_ENCODING) && HTTPRequestUtils.getInstance().isGzipped(value)) { isOriginResponseGzipped = true; } if (name.equalsIgnoreCase(HttpHeaders.CONTENT_LENGTH)) { context.setOriginContentLength(value); } if (isIncludedHeader(name)) { context.addZuulResponseHeader(name, value); } } } context.setResponseGZipped(isOriginResponseGzipped); } private HttpHost getHttpHost() { HttpHost httpHost = new HttpHost("127.0.0.1", 8083); return httpHost; } private CloseableHttpResponse forward(CloseableHttpClient httpclient, String verb, String uri, HttpServletRequest request, MultiValueMap<String, String> headers, MultiValueMap<String, String> params, InputStream requestEntity) throws Exception { ContentType contentType = null; // URL host = RequestContext.getCurrentContext().getRouteHost(); HttpHost httpHost = getHttpHost(); if (request.getContentType() != null) { contentType = ContentType.parse(request.getContentType()); } InputStreamEntity entity = new InputStreamEntity(requestEntity, request.getContentLength(), contentType); HttpRequest httpRequest = buildHttpRequest(verb, uri, entity, headers, params, request); try { log.debug(httpHost.getHostName() + " " + httpHost.getPort() + " " + httpHost.getSchemeName()); // 把这个请求请求到具体的服务去 CloseableHttpResponse zuulResponse = forwardRequest(httpclient, httpHost, httpRequest); return zuulResponse; } finally { } } private CloseableHttpResponse forwardRequest(CloseableHttpClient httpclient, HttpHost httpHost, HttpRequest httpRequest) throws IOException { return httpclient.execute(httpHost, httpRequest); } private String getEncodedQueryString(HttpServletRequest request) { String query = request.getQueryString(); return (query != null) ? "?" + query : ""; } public String getQueryString(MultiValueMap<String, String> params) { if (params.isEmpty()) { return ""; } StringBuilder query = new StringBuilder(); Map<String, Object> singles = new HashMap<>(); for (String param : params.keySet()) { int i = 0; for (String value : params.get(param)) { query.append("&"); query.append(param); if (!"".equals(value)) { // don't add =, if original is ?wsdl, output is // not ?wsdl= String key = param; // if form feed is already part of param name double // since form feed is used as the colon replacement below if (key.contains("\f")) { key = (FORM_FEED_PATTERN.matcher(key).replaceAll("\f\f")); } // colon is special to UriTemplate if (key.contains(":")) { key = COLON_PATTERN.matcher(key).replaceAll("\f"); } key = key + i; singles.put(key, value); query.append("={"); query.append(key); query.append("}"); } i++; } } UriTemplate template = new UriTemplate("?" + query.toString().substring(1)); return template.expand(singles).toString(); } protected HttpRequest buildHttpRequest(String verb, String uri, InputStreamEntity entity, MultiValueMap<String, String> headers, MultiValueMap<String, String> params, HttpServletRequest request) { HttpRequest httpRequest; String uriWithQueryString = uri + (false ? getEncodedQueryString(request) : this.getQueryString(params)); switch (verb.toUpperCase()) { case "POST": HttpPost httpPost = new HttpPost(uriWithQueryString); httpRequest = httpPost; httpPost.setEntity(entity); break; case "PUT": HttpPut httpPut = new HttpPut(uriWithQueryString); httpRequest = httpPut; httpPut.setEntity(entity); break; case "PATCH": HttpPatch httpPatch = new HttpPatch(uriWithQueryString); httpRequest = httpPatch; httpPatch.setEntity(entity); break; case "DELETE": BasicHttpEntityEnclosingRequest entityRequest = new BasicHttpEntityEnclosingRequest( verb, uriWithQueryString); httpRequest = entityRequest; entityRequest.setEntity(entity); break; default: httpRequest = new BasicHttpRequest(verb, uriWithQueryString); log.debug(uriWithQueryString); } httpRequest.setHeaders(convertHeaders(headers)); return httpRequest; } private Header[] convertHeaders(MultiValueMap<String, String> headers) { List<Header> list = new ArrayList<>(); for (String name : headers.keySet()) { for (String value : headers.get(name)) { list.add(new BasicHeader(name, value)); } } return list.toArray(new BasicHeader[0]); } }
路由规则配置拓展—正则表达式
-
package com.xiangxue.jack.routeMapper; import org.springframework.cloud.netflix.zuul.filters.discovery.PatternServiceRouteMapper; //@Configuration public class PatternServiceRouteMapperConfig { // @Bean public PatternServiceRouteMapper patternServiceRouteMapper() { /* * servicePattern 指定微服务的正则 * routePattern 指定路由正则 * */ return new PatternServiceRouteMapper("(?<name>^.+)-(?<version>v.+$)", "${version}/${name}"); } }
- 除非服务很多,不然没必要用这种规则
敏感头测试
-
注释掉"zuul.routes.micro-web.sensitive-headers="这条配置
-
调用http://localhost:7070/web/user/printInfo
-
此时打印头信息,但是里面没有cookie和权限信息
-
利用postman在这个请求方法里面添加cookie信息,但是打印头信息里面没有打印出cookie信息
-
micro-admin工程
-
当实例变得很多后,可以方便管理,只有五六台实例用处并不大
-
localhost:8282/login
- 账号和密码都是amin
-
监控整个系统的状态和实例个数
-
sprint boot admin项目
- 应用墙
- 随便点击一个应用进入分为
- Insights
- 细节,一些监控属性,像mysql,eureka,进程,线程,垃圾回收情况
- 性能,一些具体的指标
- 环境,配置的变量值
- 类
- 配置属性,在工程内涉及到的属性
- 计划任务
- 日志配置
- JVM
- java管理扩展
- 线程转储
- 内存转储
- 映射
- 请求url相关信息
- 缓存
- 应用
- 每个应用下面会有多少个正在运行的实例
- 日志报表
- 应用墙
搭建
jar包导入
-
<dependency> <groupId>de.codecentric</groupId> <artifactId>spring-boot-admin-starter-server</artifactId> <version>LATEST</version> </dependency> <dependency> <groupId>de.codecentric</groupId> <artifactId>spring-boot-admin-server-ui</artifactId> <version>LATEST</version> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>
其余要被admin管理,客户端必须配置的jar包
-
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
启动类
-
package com.xiangxue.jack; import de.codecentric.boot.admin.server.config.EnableAdminServer; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; @SpringBootApplication @EnableEurekaClient @EnableAdminServer public class MicroAdminApplication { public static void main(String[] args) { SpringApplication.run(MicroAdminApplication.class,args); } }
- @EnableAdminServer
登陆账号密码配置
-
package com.xiangxue.jack; import de.codecentric.boot.admin.server.config.AdminServerProperties; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler; @Configuration public class SecuritySecureConfig extends WebSecurityConfigurerAdapter { private final String adminContextPath; public SecuritySecureConfig(AdminServerProperties adminServerProperties) { this.adminContextPath = adminServerProperties.getContextPath(); } @Override protected void configure(HttpSecurity http) throws Exception { SavedRequestAwareAuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler(); successHandler.setTargetUrlParameter("redirectTo"); http.authorizeRequests() .antMatchers(adminContextPath + "/assets/**").permitAll() .antMatchers(adminContextPath + "/login").permitAll() .anyRequest().authenticated() .and() .formLogin().loginPage(adminContextPath + "/login").successHandler(successHandler).and() .logout().logoutUrl(adminContextPath + "/logout").and() .httpBasic().and() .csrf().disable(); } }
配置文件
-
spring.application.name=micro-admin server.port=8282 #是否注册到eureka eureka.client.registerWithEureka=true #是否从eureka中拉取注册信息 eureka.client.fetchRegistry=true eureka.client.serviceUrl.defaultZone=http://admin:admin@localhost:8763/eureka/ # 安全配置 spring.security.user.name=admin spring.security.user.password=admin eureka.instance.metadata-map.user.name=${spring.security.user.name} eureka.instance.metadata-map.user.password=${spring.security.user.password}