springboot整合zuul跨域转发鉴权
选择在项目中新起一个zuul 模块,以达到业务需要。网上大部分是要zuul eureka 一起使用的demo,但是eureka 我们项目业务没有用到,没必要集成。
我参考的博客地址(步骤不适用,项目起不来,后面详细说):
https://www.cnblogs.com/gdjlc/p/11899276.html
第一步:新起一个maven 工程
第二步:添加maven pom.xml依赖
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>icnoc-data</artifactId> <groupId>com.hginfo.icnoc-data</groupId> <version>1.0.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>zuul-core</artifactId> <properties> <java.version>1.8</java.version> <spring-cloud.version>Greenwich.SR4</spring-cloud.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-zuul</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> </project>
第三步:编写启动类,开启zuul
package com.hginfo.zuul; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.zuul.EnableZuulProxy; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; @SpringBootApplication @RestController @EnableZuulProxy public class ZuulServerApplication { public static void main(String[] args) { SpringApplication.run(ZuulServerApplication.class, args); } @GetMapping("/hello/{name}") public String hello(@PathVariable String name){ return "hello " + name; } }
第四步:配置application.yml
zuul: routes: test: path: /a/** url: http://localhost:8090 b: url: https://www.baidu.com/ server: port: 8090
第五步:加入LoadBalancerAutoConfiguration 配置类(不然启动ribbon报错)
package com.hginfo.zuul.config; import java.util.ArrayList; import java.util.Collections; import java.util.List; import org.springframework.beans.factory.SmartInitializingSingleton; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.cloud.client.loadbalancer.LoadBalancerClient; import org.springframework.cloud.client.loadbalancer.LoadBalancerInterceptor; import org.springframework.cloud.client.loadbalancer.RestTemplateCustomizer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.client.ClientHttpRequestInterceptor; import org.springframework.web.client.RestTemplate; /** * Auto configuration for Ribbon (client side load balancing). * * @author Spencer Gibb * @author Dave Syer */ //表示这是一个配置类 @Configuration //必须存在RestTemplate类 @ConditionalOnClass(RestTemplate.class) //必须存在LoadBalancerClient类型的bean @ConditionalOnBean(LoadBalancerClient.class) public class LoadBalancerAutoConfiguration { //所有被@LoadBalanced注解修饰的RestTemplate // @LoadBalanced @Autowired(required = false) private List<RestTemplate> restTemplates = Collections.emptyList(); //对于所有被@LoadBalanced注解修饰的RestTemplate,调用SmartInitializingSingleton的 //customize方法 @Bean public SmartInitializingSingleton loadBalancedRestTemplateInitializer( final List<RestTemplateCustomizer> customizers) { return new SmartInitializingSingleton() { @Override public void afterSingletonsInstantiated() { for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) { for (RestTemplateCustomizer customizer : customizers) { customizer.customize(restTemplate); } } } }; } //对于所有被@LoadBalanced注解修饰的RestTemplate,增加loadBalancerInterceptor属性 @Bean @ConditionalOnMissingBean public RestTemplateCustomizer restTemplateCustomizer( final LoadBalancerInterceptor loadBalancerInterceptor) { return new RestTemplateCustomizer() { @Override public void customize(RestTemplate restTemplate) { List<ClientHttpRequestInterceptor> list = new ArrayList<>( restTemplate.getInterceptors()); list.add(loadBalancerInterceptor); restTemplate.setInterceptors(list); } }; } //产生一个LoadBalancerInterceptor类型的bean,包含loadBalancerClient类型的bean @Bean public LoadBalancerInterceptor ribbonInterceptor( LoadBalancerClient loadBalancerClient) { return new LoadBalancerInterceptor(loadBalancerClient); } }
然后访问:http://localhost:8090/a/hello/xiaoming,效果如下图
访问 http://localhost:8090/b/,效果如下:
如果不跨域的话,上面的步骤就可以了,跨域的话往下看。
修改application.yml 配置如下
#需要忽略的头部信息,不在传播到其他服务 sensitive-headers: Access-Control-Allow-Origin ignored-headers: Access-Control-Allow-Origin,H-APP-Id,Token,APPToken
解决跨域需要的代码
package com.hginfo.zuul; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.zuul.EnableZuulProxy; import org.springframework.context.annotation.Bean; import org.springframework.web.filter.CorsFilter; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; @SpringBootApplication @EnableZuulProxy public class ZuulServerApplication { public static void main(String[] args) { SpringApplication.run(ZuulServerApplication.class, args); } @Bean public CorsFilter corsFilter() { final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); final CorsConfiguration config = new CorsConfiguration(); config.setAllowCredentials(true); // 允许cookies跨域 config.addAllowedOrigin("*");// 允许向该服务器提交请求的URI,*表示全部允许。。这里尽量限制来源域,比如http://xxxx:8080 ,以降低安全风险。。 config.addAllowedHeader("*");// 允许访问的头信息,*表示全部 config.setMaxAge(18000L);// 预检请求的缓存时间(秒),即在这个时间段里,对于相同的跨域请求不会再预检了 config.addAllowedMethod("*");// 允许提交请求的方法,*表示全部允许,也可以单独设置GET、PUT等 config.addAllowedMethod("HEAD"); config.addAllowedMethod("GET");// 允许Get的请求方法 config.addAllowedMethod("PUT"); config.addAllowedMethod("POST"); config.addAllowedMethod("DELETE"); config.addAllowedMethod("PATCH"); source.registerCorsConfiguration("/**", config); return new CorsFilter(source); } }
如果需要做鉴权,token 验证之类的,可以在ZuulFilter里面做,代码:
package com.hginfo.zuul.filter; import com.netflix.zuul.ZuulFilter; import com.netflix.zuul.context.RequestContext; import com.netflix.zuul.http.HttpServletRequestWrapper; import com.netflix.zuul.http.ServletInputStreamWrapper; import net.sf.json.JSONObject; import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.lang.StringUtils; import org.springframework.stereotype.Component; import org.springframework.util.StreamUtils; import javax.servlet.ServletInputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.InputStream; import java.nio.charset.Charset; import java.util.*; @Component public class PreFilter extends ZuulFilter { @Override public String filterType() { /*filterType: 过滤器的类型,它决定过滤器在请求的哪个生命周期中执行。 这里定义为pre,代表会在请求被路由之前执行。有前置、后置和路由过滤器。 pre:可以在请求被路由之前调用。 routing: 路由请求时被调用。post: 在routing和error过滤器之后被调用。 error:处理请求时发生错误时被调用*/ return "pre"; } @Override public int filterOrder() { // filterOrder: 过滤器的执行顺序。当请求在一个阶段中存在多个过滤器时, // 需要根据该方法返回的值来依次执行。 return 0; } @Override public boolean shouldFilter() { // shouldFilter: 判断该过滤器是否需要被执行。这里我们直接返回了true, // 因此该过滤器对所有请求都会生效。实际运用中我们可以利用该函数来指定过滤器的有效范围。 return true; } @lombok.SneakyThrows @Override public Object run() { // 过滤器的具体逻辑。 RequestContext requestContext = RequestContext.getCurrentContext(); HttpServletRequest request = requestContext.getRequest(); HttpServletResponse response = requestContext.getResponse(); Map<String, String> params = new TreeMap<String, String>(); Enumeration<String> p = request.getParameterNames(); while (p.hasMoreElements()) { String k = p.nextElement(); String v = request.getParameter(k); params.put(k, v); } if ("GET".equals(request.getMethod()) && params.size() <= 0) { return null; } if ("POST".equals(request.getMethod())) { InputStream in = request.getInputStream(); String body = StreamUtils.copyToString(in, Charset.forName("UTF-8")); if (StringUtils.isNotBlank(body)) { JSONObject bodyParams = JSONObject.fromObject(body); //参数重组后新的json String newBody = bodyParams.toString(); final byte[] reqBodyBytes = newBody.getBytes(); requestContext.setRequest(new HttpServletRequestWrapper(requestContext.getRequest()) { @Override public ServletInputStream getInputStream() throws IOException { return new ServletInputStreamWrapper(reqBodyBytes); } @Override public int getContentLength() { return reqBodyBytes.length; } @Override public long getContentLengthLong() { return reqBodyBytes.length; } }); } return null; } /*String sign = request.getParameter("sign"); if (StringUtils.isBlank(sign)) { try { requestContext.setSendZuulResponse(false); requestContext.setResponseStatusCode(401); response.setContentType("text/html;charset=UTF-8"); JSONObject json = new JSONObject(); json.put("status", "-1"); json.put("message", "没有签名"); response.getWriter().print(json.toString()); } catch (IOException e) { e.printStackTrace(); } return null; }*/ /*StringBuilder content = new StringBuilder(); List<String> keys = new ArrayList<String>(params.keySet()); for (int i = 0; i < keys.size(); i++) { String key = keys.get(i); if (!"sign".equals(key)) { String value = params.get(key); content.append(key); content.append("="); content.append(value); content.append("&"); } }*/ /* String md5sign = DigestUtils.md5Hex(content.toString() + "key=eSv3lVaNSiLmt7bxqrWFQum4zxeGfzEX"); if (!md5sign.equalsIgnoreCase(sign)) { try { requestContext.setSendZuulResponse(false); requestContext.setResponseStatusCode(401); response.setContentType("text/html;charset=UTF-8"); JSONObject json = new JSONObject(); json.put("status", "-1"); json.put("message", "签名错误"); response.getWriter().print(json.toString()); } catch (IOException e) { e.printStackTrace(); } }*/ return null; } }