006 gateway learning notes

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

Tags: Spring Cloud

Posted on Mon, 01 Nov 2021 08:33:43 -0400 by timvw