前言:通过官方提供的列子和网上找的资料学习,总结一下springcloud gateway使用的关键点。

官网地址:https://cloud.spring.io/spring-cloud-gateway/spring-cloud-gateway.html

以下主要通过Demo来介绍,如何通过gateway进行转发功能熔断功能限流功能工作

源码:git@github.com:Joe192168/springcloud-sso.git

环境:

  • idea2019
  • jdk1.8
  • maven3.6.2
  • springboot2.3.3.RELEASE
  • Redis3.0
  • mybaties-plus 3.1.1

工程目录结束

springcloud-sso

    --springcloud-api 接口服务

    --springcloud-authorization 授权服务

   --springcloud-commons 公共服务

   --springcloud-eureka 注册中心

   --springcloud-gateway 网关

springcloud-api工程 pom

<?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>springcloud-sso</artifactId>
        <groupId>com.joe</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>springcloud-api</artifactId>

    <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-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>

        <dependency>
            <groupId>com.joe</groupId>
            <artifactId>springcloud-commons</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
        </dependency>
    </dependencies>

</project>

application.yml

server:
  port: 8081
  #tomcat设置
  tomcat:
    uri-encoding: UTF-8
    accesslog:
      enabled: false
spring:
  application:
    name: api-service
eureka:
  instance:
    # 使用 ip 代替实例名
    prefer-ip-address: true
    # 实例的主机名
    hostname: ${spring.cloud.client.ip-address}
    # 实例的 ID 规则
    instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}
  client:
    serviceUrl:
      # 注册中心地址
      defaultZone: http://${eureka.instance.hostname}:8080/eureka/
#配置向认证服务器认证权限
security:
  oauth2:
    client:
      client-id: c1
      client-secret: 123
      access-token-uri: http://localhost:8082/oauth/token
      user-authorization-uri: http://localhost:8082/oauth/authorize
    resource:
      token-info-uri: http://localhost:8082/oauth/check_token
#远程调用
feign:
  hystrix:
    enabled: true
  compression:
    request:
      enabled: true
      mime-types[0]: text/xml
      mime-types[1]: application/xml
      mime-types[2]: application/json
      min-request-size: 2048
    response:
      enabled: true

配置api的资源服务器在这块api服务也相当是资源服务

package com.joe.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.joe.commons.Result;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;

/**
 * @description: 配置资源服务器
 * @author: Joe
 **/
@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

    private ObjectMapper objectMapper = new ObjectMapper();

    /**
     * 资源服务器安全配置
     * @param resources
     * @throws Exception
     */
    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        //设置资源服务器id,需要与认证服务器对应
        resources.resourceId("api-service");
        //当权限不足时返回
        resources.accessDeniedHandler((request, response, e) -> {
            response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
            response.getWriter()
                    .write(objectMapper.writeValueAsString(Result.from("0001", "权限不足", null)));
        });
        //当token不正确时返回
        resources.authenticationEntryPoint((request, response, e) -> {
            response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
            response.getWriter()
                    .write(objectMapper.writeValueAsString(Result.from("0002", "access_token错误", null)));
        });
    }

    /**
     * 配置uri拦截策略
     * @param http
     * @throws Exception
     */
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http
                .csrf().disable()
                .httpBasic().disable()
                .exceptionHandling()
                .authenticationEntryPoint((req, resp, exception) -> {
                    resp.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
                    resp.getWriter()
                            .write(objectMapper.writeValueAsString(Result.from("0002", "没有携带token", null)));
                })
                .and()
                //无需登陆
                .authorizeRequests().antMatchers("/test/noauth").permitAll()
                .and()
                //拦截所有请求,并且检查sope
                .authorizeRequests().anyRequest().access("isAuthenticated() && #oauth2.hasScope('ROLE_API')");
    }

}

testController代码

package com.joe.controller;

import com.joe.commons.Result;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author: Joe
 * @Description: 测试接口
 */
@RestController
@RequestMapping("/test")
public class TestController {

    @Value("${server.port}")
    private String port;

    @GetMapping(value = "/r1",produces = "application/json; charset=utf-8")
    @PreAuthorize("hasAnyAuthority('ROLE_USER')")
    public Result test1() {
        return new Result("200","请求成功","订单1");
    }

    @GetMapping(value = "/r2",produces = "application/json; charset=utf-8")
    @PreAuthorize("hasAnyAuthority('ROLE_ADMIN')")
    public Result test2() {
        return new Result("200","请求成功","订单2");
    }

    //无需登录
    @GetMapping("/noauth")
    public Result noauth() {
        return new Result("200","请求成功","noauth");
    }

    @RequestMapping("/name")
    public String name(String name){
        return "My name is " + name + ". aaa";
    }

    @RequestMapping("/hello")
    public String hello(){
        return "Hello!I'm a. port:" + port;
    }

    @RequestMapping("/age")
    public String age(String age){
        return "I am " + age + " years old this year. bbb";
    }

    @RequestMapping("/routeAll")
    public String routeAll(String pass) {
        return "Can I pass? " + pass + "! port:" + port;
    }
}

springcloud-authorization工程 pom

<?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>springcloud-sso</artifactId>
        <groupId>com.joe</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>springcloud-authorization</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.1.1</version>
        </dependency>
        <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-hystrix</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.10</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </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>

application.yml

server:
  port: 8082
spring:
  application:
    name: auth-service
  #Redis配置
  redis:
    #ip地址
    host: localhost
    #端口
    port: 6379
    #默认从0使用数据库
    database: 0
    # Redis服务器连接密码(默认为空)
    password:
  #数据源
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/user_db?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowMultiQueries=true
    username: root
    password: root
    type: com.alibaba.druid.pool.DruidDataSource
    druid:
      #初始化大小
      initialSize: 5
      #最小值
      minIdle: 5
      #最大值
      maxActive: 20
      #最大等待时间,配置获取连接等待超时,时间单位都是毫秒ms
      maxWait: 60000
      #配置间隔多久才进行一次检测,检测需要关闭的空闲连接
      timeBetweenEvictionRunsMillis: 60000
      removeAbandoned: true
      removeAbandonedTimeout: 1800
      #配置一个连接在池中最小生存的时间
      minEvictableIdleTimeMillis: 300000
      validationQuery: SELECT 1 FROM DUAL
      testWhileIdle: true
      testOnBorrow: false
      testOnReturn: false
      poolPreparedStatements: true
      # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,
      #'wall'用于防火墙,SpringBoot中没有log4j,我改成了log4j2
      filters: stat,wall,log4j2
      #最大PSCache连接
      maxPoolPreparedStatementPerConnectionSize: 20
      useGlobalDataSourceStat: true
      # 通过connectProperties属性来打开mergeSql功能;慢SQL记录
      connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
      # 配置StatFilter
      web-stat-filter:
        #默认为false,设置为true启动
        enabled: true
        url-pattern: "/*"
        exclusions: "*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*"
      #配置StatViewServlet
      stat-view-servlet:
        url-pattern: "/druid/*"
        #允许那些ip
        allow: 127.0.0.1
        login-username: admin
        login-password: admin
        #禁止那些ip
        deny: 192.168.1.102
        #是否可以重置
        reset-enable: true
        #启用
        enabled: true
  #支持名称相同的bean被覆盖
  main:
    allow-bean-definition-overriding: true
eureka:
  instance:
    # 使用 ip 代替实例名
    prefer-ip-address: true
    # 实例的主机名
    hostname: ${spring.cloud.client.ip-address}
    # 实例的 ID 规则
    instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}
  client:
    serviceUrl:
      # 注册中心地址
      defaultZone: http://${eureka.instance.hostname}:8080/eureka/
mybatis-plus:
  # 如果是放在src/main/java目录下 classpath:/com/yourpackage/*/mapper/*Mapper.xml
  # 如果是放在resource目录 classpath:/mapper/*Mapper.xml
  mapper-locations: classpath:/mappers/*Mapper.xml
  #实体扫描,多个package用逗号或者分号分隔
  typeAliasesPackage: com.joe.uaa.uaa.entity
  global-config:
    #主键类型  0:"数据库ID自增", 1:"用户输入ID",2:"全局唯一ID (数字类型唯一ID)", 3:"全局唯一ID UUID";
    id-type: 0
    #字段策略 0:"忽略判断",1:"非 NULL 判断"),2:"非空判断"
    field-strategy: 1
    #驼峰下划线转换
    db-column-underline: true
    #刷新mapper 调试神器
    #refresh-mapper: true
    #数据库大写下划线转换
    capital-mode: true
    # Sequence序列接口实现类配置
    #key-generator: com.baomidou.mybatisplus.incrementer.OracleKeyGenerator
    #逻辑删除配置(下面3个配置)
    #logic-delete-value: 1
    #logic-not-delete-value: 0
    #sql-injector: com.baomidou.mybatisplus.mapper.LogicSqlInjector
    #自定义填充策略接口实现
    #meta-object-handler: com.baomidou.springboot.MyMetaObjectHandler
    #去掉表前缀
    db-config:
      table-prefix: t_
  configuration:
    map-underscore-to-camel-case: false
    cache-enabled: false
    #配置JdbcTypeForNull
    jdbc-type-for-null: 'null'

认证服务器配置
package com.joe.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.code.JdbcAuthorizationCodeServices;
import org.springframework.security.oauth2.provider.token.*;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;
import javax.sql.DataSource;
import java.util.Arrays;

/**
 * @description: 认证服务器配置
 * @author: Joe
 **/
@Order(2)
@EnableAuthorizationServer
@Configuration
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private AuthenticationManager authenticationManager;
    @Autowired
    private BCryptPasswordEncoder passwordEncoder;
    @Autowired
    private DruidConfig druidConfig;
    @Autowired
    private RedisConnectionFactory redisConnectionFactory;

    //这块jwt的key,使用这个key就可以进行解密操作
    private String SIGGNING_KEY = "sso123";

    /**
     * 数据源配置
     * @return
     */
    @Bean
    public DataSource dataSource() {
          return druidConfig.dataSource();
    }

    /**
     * 配置jwt
     * @return
     */
    @Bean
    public JwtAccessTokenConverter accessTokenConverter(){
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setSigningKey(SIGGNING_KEY);//对称秘钥,资源服务器使用该秘钥解密
        return converter;
    }

    /**
     * 将客户端信息从数据库获取
     * @return
     */
    @Bean
    public ClientDetailsService clientDetailsService(){
        ClientDetailsService clientDetailsService = new JdbcClientDetailsService(dataSource());
        ((JdbcClientDetailsService)clientDetailsService).setPasswordEncoder(passwordEncoder);
        return clientDetailsService;
    }

    /**
     * 令牌存储方式
     * @return
     */
    @Bean
    public TokenStore tokenStore(){
        //存储缓存中
        //return new InMemoryTokenStore();
        //存储数据库
        //return new JwtTokenStore(accessTokenConverter());
        //存储Redis
        return new RedisTokenStore(redisConnectionFactory);
    }

    /**
     * 授权码服务
     * @return
     */
    @Bean
    public AuthorizationCodeServices authorizationCodeServices(){
        //设置授权码模式的授权码如何存取,暂时采用内存方式
        //return new InMemoryAuthorizationCodeServices();
        //采用数据方式存储授权码
        return new JdbcAuthorizationCodeServices(dataSource());
    }

    /**
     * 令牌管理服务
     * @return
     */
    @Bean
    public AuthorizationServerTokenServices tokenServices(){
        DefaultTokenServices services = new DefaultTokenServices();
        services.setClientDetailsService(clientDetailsService());//客户端服务信息
        services.setSupportRefreshToken(true);//是否产生刷新令牌
        services.setTokenStore(tokenStore());//令牌存储策略
        TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
        tokenEnhancerChain.setTokenEnhancers(Arrays.asList(accessTokenConverter()));
        services.setTokenEnhancer(tokenEnhancerChain);
        services.setAccessTokenValiditySeconds(7200);//令牌默认有效期2小时
        services.setRefreshTokenValiditySeconds(259200);//刷新令牌默认的有效期3天
        return services;
    }

    /**
     * 令牌访问端点的安全约束
     * @param security
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        //配置允许认证的权限
        // 允许通过form提交客户端认证信息(client_id,client_secret),默认为basic方式认证
        security.allowFormAuthenticationForClients();
        // "/oauth/check_token"端点默认不允许访问
        security.checkTokenAccess("isAuthenticated()");
        // "/oauth/token_key"断点默认不允许访问
        security.tokenKeyAccess("isAuthenticated()");
    }

    /**
     * 客户端配置
     * @param clients
     * @throws Exception
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.withClientDetails(clientDetailsService());
        /*clients.inMemory()//使用in‐memory存储
                .withClient("c1") // client_id
                .secret(bCryptPasswordEncoder.encode("123")) // client_secret
                .authorizedGrantTypes("authorization_code","password","client_credentials","implicit","refresh_token") // 该client允许的授权类型
                .scopes("all") // 允许的授权范围
                .redirectUris("https://www.baidu.com")//回调地址
                .resourceIds("order-service")//资源服务器id,需要与资源服务器对应
                .autoApprove(false);//登录后绕过批准询问(/oauth/confirm_access)*/
    }

    /**
     * 令牌配置访问端点和令牌服务
     * @param endpoints
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints
                .authenticationManager(authenticationManager)//密码模式
                .authorizationCodeServices(authorizationCodeServices())//授权码模式
                .tokenServices(tokenServices())//令牌管理服务
                .allowedTokenEndpointRequestMethods(HttpMethod.GET,HttpMethod.POST);//允许POST提交
    }

}
安全验证用户身份配置
package com.joe.config;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.google.common.base.Joiner;
import com.joe.entity.Users;
import com.joe.mapper.RoleMapper;
import com.joe.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;
import java.nio.channels.AcceptPendingException;
import java.util.List;

/**
 * @description: 安全验证用户身份配置
 * @author: Joe
 **/
@Component
public class UserDetailsServiceConfig implements UserDetailsService {

    @Autowired
    private UserMapper userMapper;
    @Autowired
    private RoleMapper roleMapper;

    /**
     * 生产环境使用数据库进行验证
     * @param username
     * @return
     * @throws UsernameNotFoundException
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        QueryWrapper<Users> wrapper = new QueryWrapper<>();
        wrapper.eq("username", username);
        Users user = userMapper.selectOne(wrapper);
        //该账号为空
        if (user == null) {
            throw new UsernameNotFoundException(username);
        }
        //该账号不正确
        if (!username.equals(user.getUserName())) {
            throw new AcceptPendingException();
        }
        //根据用户id查询用户角色
        List<String> roles = roleMapper.findRolesByUserId(user.getId());
        //将roles转成字符串
        String result = Joiner.on(",").join(roles);
        return new User(username, user.getPassWord(),
                AuthorityUtils.commaSeparatedStringToAuthorityList(result));
    }
}
身份认证拦截配置
package com.joe.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

/**
 * @description: 身份认证拦截
 * @author: Joe
 **/
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    public UserDetailsServiceConfig userDetailsServiceConfig;

    /**
     * 密码模式
     * @return
     * @throws Exception
     */
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    /**
     * 用户密码和认证服务器客户端密码都需要加密算法
     * @return
     */
    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //验证用户权限
        auth.userDetailsService(userDetailsServiceConfig);
    }

    /**
     * uri权限拦截,生产可以设置为启动动态读取数据库
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                // 登陆页
                .formLogin().permitAll()
                // 登出页
                .and().logout().logoutUrl("/logout").logoutSuccessUrl("/")
                // 其余所有请求全部需要鉴权认证
                .and().authorizeRequests().anyRequest().authenticated()
                // 关闭csrf
                .and().csrf().disable();
    }

    /**
     * 网络安全设置
     * @param web
     * @throws Exception
     */
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/oauth/check_token");
    }
}

这块使用的RBAC(角色授权),其它mapper就不贴了,这块RoleMapper的代码

package com.joe.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.joe.entity.Role;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.util.List;

@Mapper
public interface RoleMapper extends BaseMapper<Role> {

    @Select("select tr.role_name from t_role tr,t_user_role tur,t_user tu where tr.id = tur.role_id and tu.id = #{userId}")
    List<String> findRolesByUserId(@Param("userId") int userId);
}

springcloud-commons工程 pom

<?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>springcloud-sso</artifactId>
        <groupId>com.joe</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>springcloud-commons</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>

</project>
公共返回json实体消息代码
package com.joe.commons;

import lombok.Data;

/**
 * @description: 公共返回json实体消息
 * @author: Joe
 **/
@Data
public class Result<T> {
    private String code;
    private String msg;
    private T data;

    public Result(String code, String msg, T data) {
        this.code = code;
        this.msg = msg;
        this.data = data;
    }

    public static <T> Result from(String code, String msg, T data) {
        return new Result(code, msg, data);
    }
}

springcloud-eureka工程 pom

<?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>springcloud-sso</artifactId>
        <groupId>com.joe</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>springcloud-eureka</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

</project>

启动类

package com.joe;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

/**
 * @description: 注册中心启动类
 * @author: Joe
 **/
@EnableEurekaServer
@SpringBootApplication
public class EurekaApplication {

    public static void main(String[] args) {
        SpringApplication.run(EurekaApplication.class,args);
    }

}

springcloud-gateway工程 pom

<?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>springcloud-sso</artifactId>
        <groupId>com.joe</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>springcloud-gateway</artifactId>

    <dependencies>
        <!-- 提供者消费者 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</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>
        <!-- hystrix -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>
        <!-- ali json依赖 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.47</version>
        </dependency>

    </dependencies>

    <!-- 插件依赖 -->
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

网关启动类 GateWayApplication

 

 

package com.joe;

import com.joe.filter.TokenFilter;
import com.joe.filter.UriKeyResolver;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.context.annotation.Bean;

/**
 * @description: 网关启动类
 * @author: Joe
 **/
@SpringBootApplication
@EnableEurekaClient
public class GateWayApplication {

    public static void main(String[] args) {
        SpringApplication.run(GateWayApplication.class,args);
    }

    /**
     * 配置限流 bean
     * @return
     */
    @Bean(name = "uriKeyResolver")
    public UriKeyResolver uriKeyResolver() {
        return new UriKeyResolver();
    }

    /**
     * 配置认证过滤器 Bean
     * @return
     */
    @Bean(name = "tokenFilter")
    public TokenFilter tokenFilter() {
        return new TokenFilter();
    }

}

application.yml 详细说明

  • 注意配置注册中心地址的端口都为 8080 也就是上面注册中心工程配置的端口
  • 每一对 '---' 符号中的配置文件都是单独的,使用 spring.profiles.active 指定
  • 每一对 '---' 符号中的配置文件都只配置了一个 route(路由)
  • route(路由)由四部分组成,其中 filters 不是必须参数
  • 唯一标识(ID)、目标服务地址(uri)、一组断言(predicates)和一组过滤器组成(filters)

简单模式 

---

#简单尝试
spring:
  # 配置文件名称,用来标识不同环境的配置。由 spring.profiles.active 的值来决定使用哪组配置。
  ## 简单尝试
  profiles: route_simple
  application:
    # 应用名称
    name: gateway-service
  cloud:
    gateway:
      discovery:
        locator:
          # 是否和服务注册与发现组件结合,设置为 true 后可以直接使用应用名称调用服务
          enabled: true
      # 路由(routes:路由,它由唯一标识(ID)、目标服务地址(uri)、一组断言(predicates)和一组过滤器组成(filters)。filters 不是必需参数。)
      routes:
        # 路由标识(id:标识,具有唯一性)   简单尝试
        - id: route_simple
          # 目标服务地址(uri:地址,请求转发后的地址)
          uri: http://localhost:8081
          # 路由条件(predicates:断言,匹配 HTTP 请求内容)
          predicates:
            ## 转发地址格式为 uri/test/r1
            - Path=/test/r1

eureka:
  instance:
    # 使用 ip 代替实例名
    prefer-ip-address: true
    # 实例的主机名
    hostname: ${spring.cloud.client.ip-address}
    # 实例的 ID 规则
    instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}
  client:
    serviceUrl:
      # 注册中心地址
      defaultZone: http://${eureka.instance.hostname}:8080/eureka/

logging:
  level:
    # log 级别
    org.springframework.cloud.gateway: debug

 1. 启动注册中心工程(eureka-server)、API服务工程(api-server),授权服务工程(auth-service),网关服务工程(gateway-service)

 2. 把 gateway-service- application.yml 配置文件中最上面的 spring.profiles.active 的值更改为 route_simple

 3. 上面配置文件内容意思是当访问 http://localhost:8000/test/r1 (网关地址/archive)

      会被转发到 http://localhost:8081/test/r1/ (uri/test/r1)

  4. 项目启动成功后访问:http://localhost:8000/test/r1

  5. 发现页面会自动被跳转到:http://localhost:8081/test/r1/这块其实是不发生变化的

  6. 证明服务转发成功

截取请求

---

#截取请求
spring:
  # 配置文件名称,用来标识不同环境的配置。由 spring.profiles.active 的值来决定使用哪组配置。
  ## 简单尝试
  profiles: route_stripPrefix
  application:
    # 应用名称
    name: gateway-service
  cloud:
    gateway:
      discovery:
        locator:
          # 是否和服务注册与发现组件结合,设置为 true 后可以直接使用应用名称调用服务
          enabled: true
      # 路由(routes:路由,它由唯一标识(ID)、目标服务地址(uri)、一组断言(predicates)和一组过滤器组成(filters)。filters 不是必需参数。)
      routes:
        # 路由标识(id:标识,具有唯一性)   截取请求
        - id: route_stripPrefix
          # 目标服务地址(uri:地址,请求转发后的地址)
          uri: http://localhost:8081
          # 路由条件(predicates:断言,匹配 HTTP 请求内容)
          predicates:
            ## 转发地址格式为 uri/archive
            - Path=/str/test/r1
          filters:
            ## 截取路径位数
            - StripPrefix=1

eureka:
  instance:
    # 使用 ip 代替实例名
    prefer-ip-address: true
    # 实例的主机名
    hostname: ${spring.cloud.client.ip-address}
    # 实例的 ID 规则
    instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}
  client:
    serviceUrl:
      # 注册中心地址
      defaultZone: http://${eureka.instance.hostname}:8080/eureka/

logging:
  level:
    # log 级别
    org.springframework.cloud.gateway: debug

  1. 启动注册中心工程(eureka-server)、API服务工程(api-server),授权服务工程(auth-service),网关服务工程(gateway-service)

  2. 把 gateway-service - application.yml 配置文件中最上面的 spring.profiles.active 的值更改为 route_stripPrefix

  3. 上面配置文件内容意思是访问的路径 http://localhost:8000/str/test/r1 (网关地址/str/test/r1)截取 /str 部分,

      截取后被转发到 http://localhost:8081/test/r1 (uri/test/r1)

  4. 启动注册中心工程(eureka-server)和网关工程(gateway-service)

  5. 项目启动成功后访问:http://localhost:8000/str/test/r1

  6. 发现页面会自动被跳转到:http://localhost:8081/test/r1 (地址栏是感觉不到变化)

  7. 证明路径被截取并服务转发成功

转发指定地址并传入参数

---

#转发指定地址并传入参数
spring:
  # 配置文件名称,用来标识不同环境的配置。由 spring.profiles.active 的值来决定使用哪组配置。
  ## 简单尝试
  profiles: route_uri
  application:
    # 应用名称
    name: gateway-service
  cloud:
    gateway:
      discovery:
        locator:
          # 是否和服务注册与发现组件结合,设置为 true 后可以直接使用应用名称调用服务
          enabled: true
      # 路由(routes:路由,它由唯一标识(ID)、目标服务地址(uri)、一组断言(predicates)和一组过滤器组成(filters)。filters 不是必需参数。)
      routes:
        # 路由标识(id:标识,具有唯一性)   转发指定地址并传入参数
        - id: route_uri
          # 目标服务地址(uri:地址,请求转发后的地址)
          uri: http://localhost:8081
          # 路由条件(predicates:断言,匹配 HTTP 请求内容)
          predicates:
            ## 匹配 GET 请求
            - Method=GET
            # 过滤器(filters:过滤器,过滤规则)
          filters:
            ## 添加指定参数
            - AddRequestParameter=name, zwc

eureka:
  instance:
    # 使用 ip 代替实例名
    prefer-ip-address: true
    # 实例的主机名
    hostname: ${spring.cloud.client.ip-address}
    # 实例的 ID 规则
    instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}
  client:
    serviceUrl:
      # 注册中心地址
      defaultZone: http://${eureka.instance.hostname}:8080/eureka/

logging:
  level:
    # log 级别
    org.springframework.cloud.gateway: debug

1. 启动注册中心工程(eureka-server)、API服务工程(api-server),授权服务工程(auth-service),网关服务工程(gateway-service)

  2. 把 gateway-service- application.yml 配置文件中最上面的 spring.profiles.active 的值更改为 route_uri

  3. 上面配置文件内容意思是访问的路径 http://localhost:8000/test/name (网关地址/name)

      会被转发到 http://localhost:8081/test/name(uri/name),并传入 'name=zwc' 参数(注意为 Get 请求)

  4. 项目启动成功后访问:http://localhost:8000/test/name

  5. 输出内容:'My name is zwc'(通过网关转发 - 参数有值)

  6. 证明转发指定地址并传入参数成功

转发指定服务并传入参数

---

#转发指定服务并传入参数
spring:
  # 配置文件名称,用来标识不同环境的配置。由 spring.profiles.active 的值来决定使用哪组配置。
  ## 简单尝试
  profiles: route_addRequestParameter
  application:
    # 应用名称
    name: gateway-service
  cloud:
    gateway:
      discovery:
        locator:
          # 是否和服务注册与发现组件结合,设置为 true 后可以直接使用应用名称调用服务
          enabled: true
      # 路由(routes:路由,它由唯一标识(ID)、目标服务地址(uri)、一组断言(predicates)和一组过滤器组成(filters)。filters 不是必需参数。)
      routes:
        # 路由标识(id:标识,具有唯一性)   转发指定服务并传入参数
        - id: route_addRequestParameter
          # 目标服务地址(uri:地址,请求转发后的地址)
          uri: lb://api-service
          # 路由条件(predicates:断言,匹配 HTTP 请求内容)
          predicates:
            ## 匹配 GET 请求
            - Method=GET
            # 过滤器(filters:过滤器,过滤规则)
          filters:
            ## 添加指定参数
            - AddRequestParameter=age, three

eureka:
  instance:
    # 使用 ip 代替实例名
    prefer-ip-address: true
    # 实例的主机名
    hostname: ${spring.cloud.client.ip-address}
    # 实例的 ID 规则
    instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}
  client:
    serviceUrl:
      # 注册中心地址
      defaultZone: http://${eureka.instance.hostname}:8080/eureka/

logging:
  level:
    # log 级别
    org.springframework.cloud.gateway: debug

  1. 启动注册中心工程(eureka-server)、API服务工程(api-server),授权服务工程(auth-service),网关服务工程(gateway-service)

  2. 把 master-service - application.yml 配置文件中最上面的 spring.profiles.active 的值更改为 route_addRequestParameter

  3. 上面配置文件内容意思是访问的路径 http://localhost:8000/test/age (网关地址/age)

      会被转发到 http://gateway-service/test/age(uri/test/age),并传入 'age=three' 参数(注意为 Get 请求)

  4. 注意此处的配置 uri: lb://api-service 与之前都有所不同,之前都是指定了明确的转发地址,可以满足

      单个服务转发的需求,但是一般情况都会有多个服务,所以这里是指定的服务名称,格式为:lb://应用注册服务名。

  5. 项目启动成功后访问:http://localhost:8000/testage

  6. 这时可能会报错 500.错误信息为 'Unable to find instance for gateway-service'

  7. 这种情况不要慌张,只是服务还没有被注册到注册中心,稍等片刻再访问

  8. 多次访问:http://localhost:8000/test/age

  9. 轮流输出内容:'I am three years old this year'

 10. 证明转发指定服务并传入参数成功

熔断

#熔断
spring:
  # 配置文件名称,用来标识不同环境的配置。由 spring.profiles.active 的值来决定使用哪组配置。
  ## 熔断
  profiles: route_hystrix
  application:
    # 应用名称
    name: gateway-service
  cloud:
    gateway:
      discovery:
        locator:
          # 是否和服务注册与发现组件结合,设置为 true 后可以直接使用应用名称调用服务
          enabled: true
      # 路由(routes:路由,它由唯一标识(ID)、目标服务地址(uri)、一组断言(predicates)和一组过滤器组成(filters)。filters 不是必需参数。)
      routes:
        # 路由标识(id:标识,具有唯一性)   熔断
        - id: route_hystrix
          # 目标服务地址(uri:地址,请求转发后的地址)
          uri: lb://api-service
          # 路由条件(predicates:断言,匹配 HTTP 请求内容)
          predicates:
            ## 匹配 GET 请求
            - Method=GET
          # 过滤器(filters:过滤器,过滤规则)
          filters:
            ## 添加指定参数
            - AddRequestParameter=age, three
            ## 熔断
            - name: Hystrix
              args:
                name: fallbackcmd
                ### fallback 时调用的方法 http://localhost:8000/fallback
                fallbackUri: forward:/fallback


eureka:
  instance:
    # 使用 ip 代替实例名
    prefer-ip-address: true
    # 实例的主机名
    hostname: ${spring.cloud.client.ip-address}
    # 实例的 ID 规则
    instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}
  client:
    serviceUrl:
      # 注册中心地址
      defaultZone: http://${eureka.instance.hostname}:8080/eureka/

logging:
  level:
    # log 级别
    org.springframework.cloud.gateway: debug

  1. 启动注册中心工程(eureka-server)、API服务工程(api-server),授权服务工程(auth-service),网关服务工程(gateway-service)

  2. 把 master-service - application.yml 配置文件中最上面的 spring.profiles.active 的值更改为 route_hystrix

  3. 上面配置文件内容意思是访问的路径 http://localhost:8000/test/age (网关地址/test/age)

      会被转发到 http://api-service/test/age(uri/age),并传入 'age=three' 参数(注意为 Get 请求)

  4. 注意此处的配置 uri: lb://api-service 与之前都有所不同,之前都是指定了明确的转发地址,可以满足

      单个服务转发的需求,但是一般情况都会有多个服务,所以这里是指定的服务名称,格式为:lb://应用注册

      服务名。

  5. 此处还多配置了一个过滤器 '- name: Hystrix'(熔断)

  6. 当请求服务出错时,会调用 fallback,路径为:http://localhost:8000/fallback (网关地址/fallback)

  7. 此时就需要如下前端控制器

gateway-service - 熔断 - controller

package com.joe.hystrix;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @ClassName FallbackController
 * @Desc TODO   网关断路器
 * @Version 1.0
 */
@RestController
public class FallbackController {

    @RequestMapping("/fallback")
    public String fallback() {
        return "I'm Spring Cloud Gateway fallback.";
    }

}

  8. 项目启动成功后访问:http://localhost:8000/test/age

  9. 输出内容:'I'm Spring Cloud Gateway fallback.'

 10. 证明熔断成功

限流

---

#限流
spring:
  # 配置文件名称,用来标识不同环境的配置。由 spring.profiles.active 的值来决定使用哪组配置。
  ## 熔断
  profiles: route_requestRateLimiter
  application:
    # 应用名称
    name: gateway-service
  cloud:
    gateway:
      discovery:
        locator:
          # 是否和服务注册与发现组件结合,设置为 true 后可以直接使用应用名称调用服务
          enabled: true
      # 路由(routes:路由,它由唯一标识(ID)、目标服务地址(uri)、一组断言(predicates)和一组过滤器组成(filters)。filters 不是必需参数。)
      routes:
        # 路由标识(id:标识,具有唯一性)   限流
        - id: route_requestRateLimiter
          # 目标服务地址(uri:地址,请求转发后的地址)
          uri: lb://api-service
          # 路由条件(predicates:断言,匹配 HTTP 请求内容)
          predicates:
            ## 匹配 GET 请求
            - Method=GET
          # 过滤器(filters:过滤器,过滤规则)
          filters:
            ## 添加指定参数
            - AddRequestParameter=age, three
            ## 限流
            - name: RequestRateLimiter
              args:
                ### 限流过滤器的 Bean 名称
                key-resolver: '#{@uriKeyResolver}'
                ### 希望允许用户每秒处理多少个请求
                redis-rate-limiter.replenishRate: 1
                ### 用户允许在一秒钟内完成的最大请求数
                redis-rate-limiter.burstCapacity: 3


eureka:
  instance:
    # 使用 ip 代替实例名
    prefer-ip-address: true
    # 实例的主机名
    hostname: ${spring.cloud.client.ip-address}
    # 实例的 ID 规则
    instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}
  client:
    serviceUrl:
      # 注册中心地址
      defaultZone: http://${eureka.instance.hostname}:8080/eureka/

logging:
  level:
    # log 级别
    org.springframework.cloud.gateway: debug

  1. 启动注册中心工程(eureka-server)、API服务工程(api-server),授权服务工程(auth-service),网关服务工程(gateway-service)

  2. 把 gateway-service - application.yml 配置文件中最上面的 spring.profiles.active 的值

      更改为 route_requestRateLimiter

  3. 上面配置文件内容意思是访问的路径 http://localhost:8000/test/age (网关地址/age)

      会被转发到 http://api-service/test/age(uri/test/age),并传入 'age=three' 参数(注意为 Get 请求)

  4. 注意此处还需要配置 redis 的连接信息

  5. 注意此处是结合 redis 实现的限流,所以 filter 过滤器的 name 必须为 RequestRateLimiter

  6. 并且通过实现 KeyResolver 类来自定义限流策略,如下

gateway-service - 限流 - 策略

package com.joe.filter;

import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

/**
 * @ClassName UriKeyResolver
 * @Desc TODO   Spring Cloud Gateway 网关限流过滤器
 * @Version 1.0
 */
public class UriKeyResolver implements KeyResolver {

    /**a
     * 根据请求的 uri 限流
     * @param exchange
     * @return
     */
    @Override
    public Mono<String> resolve(ServerWebExchange exchange) {
        return Mono.just(exchange.getRequest().getURI().getPath());
    }

}

 7. 启动本地 redis(redis-server.exe) 服务

 8 项目启动成功后访问:http://localhost:8000/test/age

 9. 此时限流却无论如何都不生效,原因有如下两点

① redis-server 版本过低!我 Windows 本地是 redis-2.4.2 版本的,要用 3 以上的版本!!!

② 数据在 redis 中存储的时间只有几秒,所以得使用 monitor 指令来动态的观察!!!

 10. 打开 redis-cli.exe,输入命令 monitor 

 11. 快速刷新地址:http://localhost:8000/test/age

 12. 页面上会出现 429,redis-cli.exe 中会出现很多数据交互(request_rate_limiter.xxx 开头的 key)

 13. 证明限流成功

gateway-service - 综合

# 端口
server:
  port: 8000

spring:
  profiles:
    # 指定配置
    # route_simple:简单尝试
    # route_stripPrefix:截取请求
    # route_uri:转发指定地址并传入参数
    # route_addRequestParameter:转发指定服务并传入参数
    # route_hystrix:熔断
    # route_requestRateLimiter:限流
    # route_all:综合
    active: route_all

---

#简单尝试
spring:
  # 配置文件名称,用来标识不同环境的配置。由 spring.profiles.active 的值来决定使用哪组配置。
  ## 简单尝试
  profiles: route_simple
  application:
    # 应用名称
    name: gateway-service
  cloud:
    gateway:
      discovery:
        locator:
          # 是否和服务注册与发现组件结合,设置为 true 后可以直接使用应用名称调用服务
          enabled: true
      # 路由(routes:路由,它由唯一标识(ID)、目标服务地址(uri)、一组断言(predicates)和一组过滤器组成(filters)。filters 不是必需参数。)
      routes:
        # 路由标识(id:标识,具有唯一性)   简单尝试
        - id: route_simple
          # 目标服务地址(uri:地址,请求转发后的地址)
          uri: http://localhost:8081
          # 路由条件(predicates:断言,匹配 HTTP 请求内容)
          predicates:
            ## 转发地址格式为 uri/test/r1
            - Path=/test/r1

eureka:
  instance:
    # 使用 ip 代替实例名
    prefer-ip-address: true
    # 实例的主机名
    hostname: ${spring.cloud.client.ip-address}
    # 实例的 ID 规则
    instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}
  client:
    serviceUrl:
      # 注册中心地址
      defaultZone: http://${eureka.instance.hostname}:8080/eureka/

logging:
  level:
    # log 级别
    org.springframework.cloud.gateway: debug

---

#截取请求
spring:
  # 配置文件名称,用来标识不同环境的配置。由 spring.profiles.active 的值来决定使用哪组配置。
  ## 简单尝试
  profiles: route_stripPrefix
  application:
    # 应用名称
    name: gateway-service
  cloud:
    gateway:
      discovery:
        locator:
          # 是否和服务注册与发现组件结合,设置为 true 后可以直接使用应用名称调用服务
          enabled: true
      # 路由(routes:路由,它由唯一标识(ID)、目标服务地址(uri)、一组断言(predicates)和一组过滤器组成(filters)。filters 不是必需参数。)
      routes:
        # 路由标识(id:标识,具有唯一性)   截取请求
        - id: route_stripPrefix
          # 目标服务地址(uri:地址,请求转发后的地址)
          uri: http://localhost:8081
          # 路由条件(predicates:断言,匹配 HTTP 请求内容)
          predicates:
            ## 转发地址格式为 uri/archive
            - Path=/str/test/r1
          filters:
            ## 截取路径位数
            - StripPrefix=1

eureka:
  instance:
    # 使用 ip 代替实例名
    prefer-ip-address: true
    # 实例的主机名
    hostname: ${spring.cloud.client.ip-address}
    # 实例的 ID 规则
    instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}
  client:
    serviceUrl:
      # 注册中心地址
      defaultZone: http://${eureka.instance.hostname}:8080/eureka/

logging:
  level:
    # log 级别
    org.springframework.cloud.gateway: debug

---

#转发指定地址并传入参数
spring:
  # 配置文件名称,用来标识不同环境的配置。由 spring.profiles.active 的值来决定使用哪组配置。
  ## 简单尝试
  profiles: route_uri
  application:
    # 应用名称
    name: gateway-service
  cloud:
    gateway:
      discovery:
        locator:
          # 是否和服务注册与发现组件结合,设置为 true 后可以直接使用应用名称调用服务
          enabled: true
      # 路由(routes:路由,它由唯一标识(ID)、目标服务地址(uri)、一组断言(predicates)和一组过滤器组成(filters)。filters 不是必需参数。)
      routes:
        # 路由标识(id:标识,具有唯一性)   转发指定地址并传入参数
        - id: route_uri
          # 目标服务地址(uri:地址,请求转发后的地址)
          uri: http://localhost:8081
          # 路由条件(predicates:断言,匹配 HTTP 请求内容)
          predicates:
            ## 匹配 GET 请求
            - Method=GET
            # 过滤器(filters:过滤器,过滤规则)
          filters:
            ## 添加指定参数
            - AddRequestParameter=name, zwc

eureka:
  instance:
    # 使用 ip 代替实例名
    prefer-ip-address: true
    # 实例的主机名
    hostname: ${spring.cloud.client.ip-address}
    # 实例的 ID 规则
    instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}
  client:
    serviceUrl:
      # 注册中心地址
      defaultZone: http://${eureka.instance.hostname}:8080/eureka/

logging:
  level:
    # log 级别
    org.springframework.cloud.gateway: debug


---

#转发指定服务并传入参数
spring:
  # 配置文件名称,用来标识不同环境的配置。由 spring.profiles.active 的值来决定使用哪组配置。
  ## 简单尝试
  profiles: route_addRequestParameter
  application:
    # 应用名称
    name: gateway-service
  cloud:
    gateway:
      discovery:
        locator:
          # 是否和服务注册与发现组件结合,设置为 true 后可以直接使用应用名称调用服务
          enabled: true
      # 路由(routes:路由,它由唯一标识(ID)、目标服务地址(uri)、一组断言(predicates)和一组过滤器组成(filters)。filters 不是必需参数。)
      routes:
        # 路由标识(id:标识,具有唯一性)   转发指定服务并传入参数
        - id: route_addRequestParameter
          # 目标服务地址(uri:地址,请求转发后的地址)
          uri: lb://api-service
          # 路由条件(predicates:断言,匹配 HTTP 请求内容)
          predicates:
            ## 匹配 GET 请求
            - Method=GET
            # 过滤器(filters:过滤器,过滤规则)
          filters:
            ## 添加指定参数
            - AddRequestParameter=age, three

eureka:
  instance:
    # 使用 ip 代替实例名
    prefer-ip-address: true
    # 实例的主机名
    hostname: ${spring.cloud.client.ip-address}
    # 实例的 ID 规则
    instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}
  client:
    serviceUrl:
      # 注册中心地址
      defaultZone: http://${eureka.instance.hostname}:8080/eureka/

logging:
  level:
    # log 级别
    org.springframework.cloud.gateway: debug


---

#熔断
spring:
  # 配置文件名称,用来标识不同环境的配置。由 spring.profiles.active 的值来决定使用哪组配置。
  ## 熔断
  profiles: route_hystrix
  application:
    # 应用名称
    name: gateway-service
  cloud:
    gateway:
      discovery:
        locator:
          # 是否和服务注册与发现组件结合,设置为 true 后可以直接使用应用名称调用服务
          enabled: true
      # 路由(routes:路由,它由唯一标识(ID)、目标服务地址(uri)、一组断言(predicates)和一组过滤器组成(filters)。filters 不是必需参数。)
      routes:
        # 路由标识(id:标识,具有唯一性)   熔断
        - id: route_hystrix
          # 目标服务地址(uri:地址,请求转发后的地址)
          uri: lb://api-service
          # 路由条件(predicates:断言,匹配 HTTP 请求内容)
          predicates:
            ## 匹配 GET 请求
            - Method=GET
          # 过滤器(filters:过滤器,过滤规则)
          filters:
            ## 添加指定参数
            - AddRequestParameter=age, three
            ## 熔断
            - name: Hystrix
              args:
                name: fallbackcmd
                ### fallback 时调用的方法 http://localhost:8000/fallback
                fallbackUri: forward:/fallback


eureka:
  instance:
    # 使用 ip 代替实例名
    prefer-ip-address: true
    # 实例的主机名
    hostname: ${spring.cloud.client.ip-address}
    # 实例的 ID 规则
    instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}
  client:
    serviceUrl:
      # 注册中心地址
      defaultZone: http://${eureka.instance.hostname}:8080/eureka/

logging:
  level:
    # log 级别
    org.springframework.cloud.gateway: debug

---

#限流
spring:
  # 配置文件名称,用来标识不同环境的配置。由 spring.profiles.active 的值来决定使用哪组配置。
  ## 熔断
  profiles: route_requestRateLimiter
  application:
    # 应用名称
    name: gateway-service
  cloud:
    gateway:
      discovery:
        locator:
          # 是否和服务注册与发现组件结合,设置为 true 后可以直接使用应用名称调用服务
          enabled: true
      # 路由(routes:路由,它由唯一标识(ID)、目标服务地址(uri)、一组断言(predicates)和一组过滤器组成(filters)。filters 不是必需参数。)
      routes:
        # 路由标识(id:标识,具有唯一性)   限流
        - id: route_requestRateLimiter
          # 目标服务地址(uri:地址,请求转发后的地址)
          uri: lb://api-service
          # 路由条件(predicates:断言,匹配 HTTP 请求内容)
          predicates:
            ## 匹配 GET 请求
            - Method=GET
          # 过滤器(filters:过滤器,过滤规则)
          filters:
            ## 添加指定参数
            - AddRequestParameter=age, three
            ## 限流
            - name: RequestRateLimiter
              args:
                ### 限流过滤器的 Bean 名称
                key-resolver: '#{@uriKeyResolver}'
                ### 希望允许用户每秒处理多少个请求
                redis-rate-limiter.replenishRate: 1
                ### 用户允许在一秒钟内完成的最大请求数
                redis-rate-limiter.burstCapacity: 3


eureka:
  instance:
    # 使用 ip 代替实例名
    prefer-ip-address: true
    # 实例的主机名
    hostname: ${spring.cloud.client.ip-address}
    # 实例的 ID 规则
    instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}
  client:
    serviceUrl:
      # 注册中心地址
      defaultZone: http://${eureka.instance.hostname}:8080/eureka/

logging:
  level:
    # log 级别
    org.springframework.cloud.gateway: debug


---

#综合
spring:
  # 配置文件名称,用来标识不同环境的配置。由 spring.profiles.active 的值来决定使用哪组配置。
  ## 综合
  profiles: route_all
  redis:
    host: localhost
    port: 6379
    database: 0
  application:
    # 应用名称
    name: gateway-service
  cloud:
    gateway:
      discovery:
        locator:
          # 是否和服务注册与发现组件结合,设置为 true 后可以直接使用应用名称调用服务
          enabled: true
      # 路由(routes:路由,它由唯一标识(ID)、目标服务地址(uri)、一组断言(predicates)和一组过滤器组成(filters)。filters 不是必需参数。)
      routes:
        # 路由标识(id:标识,具有唯一性)   综合
        - id: route_all
          # 目标服务地址(uri:地址,请求转发后的地址)
          uri: lb://api-service
          # 路由条件(predicates:断言,匹配 HTTP 请求内容)
          predicates:
            ## 转发地址格式为 uri/test/routeAll,/all 部分会被下面的过滤器给截取掉
            - Path=/all/test/routeAll
            ## 匹配 GET 请求
            - Method=GET
          # 过滤器(filters:过滤器,过滤规则)
          filters:
            ## 截取路径位数
            - StripPrefix=1
            ## 添加指定参数
            - AddRequestParameter=pass, yes
            ## 熔断
            - name: Hystrix
              args:
                name: fallbackcmd
                ### fallback 时调用的方法 http://localhost:8000/fallback
                fallbackUri: forward:/fallback
            ## 限流
            - name: RequestRateLimiter
              args:
                ### 限流过滤器的 Bean 名称
                key-resolver: '#{@uriKeyResolver}'
                ### 希望允许用户每秒处理多少个请求
                redis-rate-limiter.replenishRate: 1
                ### 用户允许在一秒钟内完成的最大请求数
                redis-rate-limiter.burstCapacity: 3

eureka:
  instance:
    # 使用 ip 代替实例名
    prefer-ip-address: true
    # 实例的主机名
    hostname: ${spring.cloud.client.ip-address}
    # 实例的 ID 规则
    instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}
  client:
    serviceUrl:
      # 注册中心地址
      defaultZone: http://${eureka.instance.hostname}:8080/eureka/

logging:
  level:
    # log 级别
    org.springframework.cloud.gateway: debug

  1. 启动注册中心工程(eureka-server)、API服务工程(api-server),授权服务工程(auth-service),网关服务工程(gateway-service)

  2. 把 gateway-service - application.yml 配置文件中最上面的 spring.profiles.active 的值更改为 route_all

  3. 上面配置文件内容意思是访问的路径 http://localhost:8000/all/test/routeAll (网关地址/all/test/routeAll)截取 /all 部分,

      会被转发到 http://api-service/test/routeAll(uri/test/routeAll),并传入 'pass=yes' 参数(注意为 Get 请求)

  5. 项目启动成功后访问:http://localhost:8000/all/test/routeAll

  6. 首先会返回 'I'm Spring Cloud Gateway fallback.',因为服务还未被注册到注册中心

  7. 然后会返回 '{"msg":"缺少凭证","code":-1}',因为配置了全局过滤器,如下

package com.joe.filter;

import com.alibaba.fastjson.JSONObject;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.nio.charset.StandardCharsets;

/**
 * @ClassName TokenFilter
 * @Desc TODO   请求认证过滤器
 */
public class TokenFilter implements GlobalFilter{
 
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 请求对象
        ServerHttpRequest request = exchange.getRequest();
        // 响应对象
        ServerHttpResponse response = exchange.getResponse();
        // Headers对象
        HttpHeaders httpHeaders = request.getHeaders();

        // 只有综合路由才添加这个全局过滤器(routesId:route_all)
        // 如果请求路径中不存在 routeAll 字符串
        /*if(request.getURI().toString().indexOf("routeAll") == -1){
            System.out.println("filter -> return");
            // 直接跳出
            return chain.filter(exchange);
        }*/
 
        // 从请求中获取 token 参数
        //String token = exchange.getRequest().getQueryParams().getFirst("token");
        // 从请求headers中获取token参数
        String token = httpHeaders.getFirst("Authorization");
        // 如果为空,那么将返回 401
        if (token == null || token.isEmpty()) {
            // 响应消息内容对象
            JSONObject message = new JSONObject();
            // 响应状态
            message.put("code", -1);
            // 响应内容
            message.put("msg", "缺少token凭证");
            // 转换响应消息内容对象为字节
            byte[] bits = message.toJSONString().getBytes(StandardCharsets.UTF_8);
            DataBuffer buffer = response.bufferFactory().wrap(bits);
            // 设置响应对象状态码 401
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            // 设置响应对象内容并且指定编码,否则在浏览器中会中文乱码
            response.getHeaders().add("Content-Type", "text/plain;charset=UTF-8");
            // 返回响应对象
            return response.writeWith(Mono.just(buffer));
        }
        // 获取请求地址
        String beforePath = request.getPath().pathWithinApplication().value();
        // 获取响应状态码
        HttpStatus beforeStatusCode = response.getStatusCode();
        System.out.println("响应码:" + beforeStatusCode + ",请求路径:" + beforePath);
        // 请求前
        System.out.println("filter -> before");
        // 如果不为空,就通过
        return chain.filter(exchange).then(Mono.fromRunnable(() -> {
            // 获取请求地址
            String afterPath = request.getPath().pathWithinApplication().value();
            // 获取响应状态码
            HttpStatus afterStatusCode = response.getStatusCode();
            System.out.println("响应码:" + afterStatusCode + ",请求路径:" + afterPath);
            // 响应后
            System.out.println("filter -> after");
        }));
    }
 
}

8. 全局过滤器,不需要配置在配置文件中,作用于所有路由;只是这里在处理前做了判断,只有路径中存在

      routeAll 字符串才到后续处理;并且处理分为请求前的处理,和响应后的处理 (这块代码已经注释,需要测试可以解除即可,此次直接使用Headers中获取token)

  9. 此时在地址:http://localhost:8000/all/test/routeAll 中Headers中添加 Authorization参数值为token

 10. 访问:http://localhost:8000/all/test/routeAll

 11. 轮流输出内容:'Can I pass? yes! port:8081'

 12. 观察 gateway 工程的控制台,会有如下内容输出

响应码:200 OK,请求路径:/test/routeAll
filter -> before
响应码:200 OK,请求路径:/test/routeAll
filter -> after

 13. 证明全局过滤器过滤成功

说明:

这块gateway参考 https://blog.csdn.net/qq_41402200/article/details/94333830

其它是使用oauth2结合springcloud实现单点登陆以及授权操作。

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐