gateway learning notes
1, Introduction
1.1 what can be done
-
Routing addressing
-
load balancing
-
Current limiting
-
Authentication
1.2 comparison with zuul
1.3 gateway automatic assembly
1.3.1 AutoConfig
It mainly initializes routing, assertion factory, and Filter
1.3.2 LoadBalanceClient
After loading GatewayAutoConfig, load GatewayLoadBalanceClientAutoConfig to load ribbon and a series of load balancing components
1.3.3 ClassPathWarning
After loading GatewayAutoConfig, also load GatewayClassPathWarning AutoConfig to check whether the configuration is correct
1.3.4 redis
Load redis, which is mainly responsible for current limiting.
2, Build a simple gateway
2.1 creating a gateway dome
2.1.1 dependency
<dependencies> <!--eureka--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <!--actuator--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <!--gateway--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> <!--redis--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis-reactive</artifactId> </dependency> </dependencies>
2.1.2 configuration
server: port: 8801 spring: application: name: gateway-server cloud: gateway: discovery: locator: enabled: true # Start service discovery to facilitate routing lower-case-service-id: true # This attribute enables access to lowercase instance names. When not configured, uppercase instance names are used, such as http://localhost:8801/FEIGN-PRODUCER/getServerNoParam, configured as true, using lowercase instance names, such as feign producer eureka: instance: hostname: localhost client: register-with-eureka: true # Whether to register yourself on eureka without configuration. The default is true fetch-registry: true # Whether to pull service information from eureka service is not configured. The default is true serviceUrl: defaultZone: http://localhost:7005/eureka # registers with eureka service. Services in coordination with feign chapter # Expose all endpoints of the actor management: security: enabled: false # No authority authentication is required endpoints: web: exposure: include: "*" # All interfaces endpoint: health: show-details: always
2.1.3 startup
@SpringBootApplication @EnableDiscoveryClient public class GatewayServerApplication { public static void main(String[] args) { SpringApplication.run(GatewayServerApplication.class, args); }
2.2 link eureka automatically creates routes
2.2.1 viewing routing rules
Address: http://localhost:8801/actuator/gateway/routes
[ { "predicate": "Paths: [/feign-producer/**], match trailing slash: true", "metadata": { "management.port": "8401" }, "route_id": "ReactiveCompositeDiscoveryClient_FEIGN-PRODUCER", "filters": [ "[[RewritePath /feign-producer/?(?<remaining>.*) = '/${remaining}'], order = 1]" ], "uri": "lb://FEIGN-PRODUCER", "order": 0 }, { "predicate": "Paths: [/gateway-server/**], match trailing slash: true", "metadata": { "management.port": "8801" }, "route_id": "ReactiveCompositeDiscoveryClient_GATEWAY-SERVER", "filters": [ "[[RewritePath /gateway-server/?(?<remaining>.*) = '/${remaining}'], order = 1]" ], "uri": "lb://GATEWAY-SERVER", "order": 0 } ]
2.2.2 return route interpretation
The default routing rule can call all instances on the eureka service
For example: localhost:8801/FEIGN-PRODUCER/getServerNoParam
Default gateway address / instance name (in uppercase) / path. Add lower case service ID in the configuration: true # this attribute enables access to lowercase instance names, feign producer.
2.2.2.1 field interpretation
Explain the first of the above routing list
-
predicate: assert that / feign producer / * *. When we call localhost: 8801 / feign producer / getservernoparam, it will be matched to the instance of uri
-
uri: the instance on eureka, which is routed to it through assertion
As shown in the route list returned in 2.2.1, feign producer / * *, which is routed to the feign producer instance
Assertions can be added and modified. 2.3 / 2.4 will demonstrate the configuration in Java files and yml configuration files
2.3 configuring in yml
Core configuration, underscores represent arrays
routes: - id: feign-producer # The routing configuration supports multiple, so an array is used uri: lb://FEIGN-PRODUCER predicates: - Path=/yml/** # Match yml / * * Note!!!!, Path P is uppercase filters: - StripPrefix=1 # Remove the first address, e.g http://localhost:port/yml/XXX Will be replaced with http://FEIGN-PRODUCER/XXX That is, the first string after the port will be removed
All configurations
server: port: 8801 spring: application: name: gateway-server cloud: gateway: discovery: locator: enabled: true # Start service discovery to facilitate routing lower-case-service-id: true # This attribute enables access to lowercase instance names. When not configured, uppercase instance names are used, such as http://localhost:8801/FEIGN-PRODUCER/getServerNoParam, configured as true, using lowercase instance names, such as feign producer routes: - id: feign-producer # The routing configuration supports multiple, so an array is used uri: lb://FEIGN-PRODUCER predicates: - Path=/yml/** # Match yml / * * Note!!!!, Path P is uppercase filters: - StripPrefix=1 # Remove the first address, e.g http://localhost:port/yml/XXX Will be replaced with http://FEIGN-PRODUCER/XXX That is, the first string after the port will be removed eureka: instance: hostname: localhost client: register-with-eureka: true # Whether to register yourself on eureka without configuration. The default is true fetch-registry: true # Whether to pull service information from eureka service is not configured. The default is true serviceUrl: defaultZone: http://localhost:7005/eureka # registers with eureka service. Services in coordination with feign chapter # Expose all endpoints of the actor management: security: enabled: false # No authority authentication is required endpoints: web: exposure: include: "*" # All interfaces endpoint: health: show-details: always
2.4 configuring in Java
Create a new configuration class GatewayConfiguration
@Configuration public class GatewayConfiguration { @Bean public RouteLocator getRouteLocator(RouteLocatorBuilder routeLocatorBuilder){ return routeLocatorBuilder.routes() //Multiple routes can be added after routes, corresponding to multiple routing rules .route(r -> r.path("/java-one/**") .filters(f -> // Filter condition f.stripPrefix(1) // The first string after removing the port... And so on, there are many conditions ) .uri("lb://Feign-producer ") / / the URI is placed at the end, indicating the end of the route ) .route(r -> r.path("/java-two/**") .and().method(HttpMethod.GET) // Only get methods are allowed .and().header("name") // There must be a name field in the header .filters(f -> // Filter condition f.stripPrefix(1) // The first string after removing the port... And so on, there are many conditions ) .uri("lb://Feign-producer ") / / the URI is placed at the end, indicating the end of the route ) .build(); } }
2.5 timing
Simulate the second kill scenario to realize the time waiting in the gateway
// Add second kill routing rule .route(r -> r.path("/seckill/**") .and().method(HttpMethod.GET) // Only get methods are allowed .and().after(ZonedDateTime.now().plusMinutes(1)) // In... Effective after time // . and().before() in... Effective before // . and().between() passes in two times and takes effect in between .filters(f -> // Filter condition f.stripPrefix(1) // The first string after removing the port... And so on, there are many conditions .addResponseHeader("test","testAddResponseHeader") ) .uri("lb://Feign-producer ") / / the URI is placed at the end, indicating the end of the route )
The above are mainly three routing matching rules related to time
- . and().after(ZonedDateTime.now().plusMinutes(1)) / / takes effect after
- . and().before() takes effect before
- . and().between() passes in two times and takes effect in between
Before the time expires, the provider will report 404
3, Filter principle and life cycle
3.1 custom filters
Create a new Java class to implement GatewayFilter as a filter. When using, just add. Filter to the route configured in 2.4
/** * @author zhoushengtao * @date * @desc 1. It is flexible to implement GatewayFilter as a filter and add it to the routes to be used, as follows: * .filters(f -> // Filter condition * f.stripPrefix(1) // The first string after removing the port... And so on, there are many conditions * .filter(myGatewayFilter) // Add custom filter * ) * 2. Implement GlobalFilter as a global filter to filter all routes * @version v1.0 */ @Slf4j @Component public class MyGatewayFilter implements GatewayFilter, Ordered { @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { StopWatch stopwatch = new StopWatch(); stopwatch.start(exchange.getRequest().getURI().getPath()); // Execute some logic before calling return chain.filter(exchange).then( Mono.fromRunnable(()->{ stopwatch.stop(); log.info(stopwatch.prettyPrint()); }) ); } @Override public int getOrder() { return 0; } }
3.2 global filter
Implement GlobalFilter as a global filter to filter all routes without specifying a filter for routes. The code is the same as above.
4, Authentication in gateway
-
Session authentication is used in early single applications. In distributed applications, although tomcat has session synchronization, there will be a delay in the synchronization process, resulting in two adjacent requests sent to different servers, resulting in the absence of sessions.
-
Redis has also been used. Putting sessions into redis can solve the problem of session synchronization, but it also leads to the coupling of authentication and container.
-
The commonly used distributed authentication methods oauth2.0 and jwt almost carry token s for authentication. Oauth2.0 includes user permissions, roles and other functions. jwt is relatively lightweight.
4.1 jwt demonstration
Create an auth server project
Introduce dependency
<!-- jwt --> <dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>3.7.0</version> </dependency>
Tool class
/** * Created by Banxian */ @Slf4j @Service public class JwtServiceUtil { // The production environment cannot be used in this way (the encryption confusion field is read in the configuration center) private static final String KEY = "changeIt"; private static final String ISSUER = "yao"; private static final long TOKEN_EXP_TIME = 60000; private static final String USER_NAME = "username"; /** * Generate Token * * @param username * @return */ public static String token(String username) { Date now = new Date(); Algorithm algorithm = Algorithm.HMAC256(KEY); String token = JWT.create() .withIssuer(ISSUER) .withIssuedAt(now) .withExpiresAt(new Date(now.getTime() + TOKEN_EXP_TIME)) .withClaim(USER_NAME, username) // .withClaim("ROLE", "") .sign(algorithm); log.info("jwt generated user={}", username); return token; } /** * Verify Token * * @param token * @param username * @return */ public static boolean verify(String token, String username) { log.info("verifying jwt - username={}", username); try { Algorithm algorithm = Algorithm.HMAC256(KEY); JWTVerifier verifier = JWT.require(algorithm) .withIssuer(ISSUER) .withClaim(USER_NAME, username) .build(); verifier.verify(token); return true; } catch (Exception e) { log.error("auth failed", e); return false; } } public static void main(String[] args) { String userName= "zhoust"; String token = token(userName); log.info("According to user name:{}. generate token: {}",userName,token); boolean verify = verify(token, userName); log.info("check token: {},user name:{},Verification results:{}",token,userName,verify); boolean lisi = verify(token, "lisi"); log.info("check token: {},user name:{},Verification results:{}",token,"lisi",lisi); } }
Generate and verify token s through tool classes
External interface
@FeignClient("auth-service") public interface AuthService { @PostMapping("/login") @ResponseBody public AuthResponse login(@RequestParam("username") String username, @RequestParam("password") String password); @GetMapping("/verify") public AuthResponse verify(@RequestParam("token") String token, @RequestParam("username") String username); @PostMapping("/refresh") @ResponseBody public AuthResponse refresh(@RequestParam("refresh") String refresh); }
Use in geteway server
It mainly checks the token in the filter and adds relevant information
@Slf4j @Component public class AuthFilter implements GatewayFilter, Ordered { // The defined key value passed to the header private static final String TOKEN = "token"; private static final String USERNAME = "userName"; @Autowired private AuthService authService; @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { log.info("Auth start"); ServerHttpRequest request = exchange.getRequest(); HttpHeaders header = request.getHeaders(); String token = header.getFirst(TOKEN); String username = header.getFirst(USERNAME); ServerHttpResponse response = exchange.getResponse(); if (StringUtils.isBlank(token)) { log.error("token not found"); response.setStatusCode(HttpStatus.UNAUTHORIZED); return response.setComplete(); } AuthResponse resp = authService.verify(token, username); if (resp.getCode() != 1L) { log.error("invalid token"); response.setStatusCode(HttpStatus.FORBIDDEN); return response.setComplete(); } // TODO stores the user information in the request header and passes it to downstream services ServerHttpRequest.Builder mutate = request.mutate(); mutate.header(USERNAME, username); ServerHttpRequest buildReuqest = mutate.build(); //todo if you need to put data in the response, you can also put it in the header of the response response.setStatusCode(HttpStatus.OK); response.getHeaders().add(USERNAME,username); return chain.filter(exchange.mutate() .request(buildReuqest) .response(response) .build()); } @Override public int getOrder() { return 0; } }
When calling, you need to put the token and username in the header.
5, Current limiting
// todo
6, Unified error reporting in gateway
// todo