1.Gateway的介绍

路由(Route): 路由是网关的基本组成部分,路由信息由ID、目标URL、一组断言和一组过滤器组成,如果断言为真,则说明请求的URL和配置匹配。
断言(Predicate): Java8中的断言函数,Spring Cloud Gateway中的断言函数输入类型是Spring5.0框架中的ServerWebExchange。Spring Cloud Gateway中的断言函数允许开发者自定义匹配来自于Http Request中的任何信息,比如请求头和参数等
过滤器(Flute): 一个标准的SpringWebFilter,Spring Cloud Gateway中的Filter分为两种类型,分别是Gateway Filter和Globa lFilter,过滤器将会对请求和响应进行处理。

2.Gateway的作用

Gateway是SpringCloud提供的API网关,提供主要功能有:路由和鉴权以及限流、统一配置等。

在这里插入图片描述

3.Gateway的工作原理

1.客户端发送请求到网关
2.网关通过HandlerMapping处理器映射器获得Handler处理器
3.Handler执行通过网关内部的过滤器链,过滤器分为两种:pre前置、post后置
4.前置过滤器主要实现鉴权和路由,后置过滤器可以进行性能监控和数据统计等
5.通过所有过滤器才能访问到需要的服务

在这里插入图片描述

4.Gateway的路由功能

使用过程:
1.创建网关项目
2.引入geteway依赖
3.网关需要注册到注册中心
5.配置路由

spring
	cloud:
		gateway:
			routes:	#路由
			- id: 路由id
			  uri: lb: //order-service-ruter	#服务名
			  predicates:	#断言
			  - Path=/order/**,/orders/**
			- id: 路由id
			  uri: lb: //product-service-ruter	#服务名
			  predicates:	#断言
			  - Path=/product/**,/products/**
			  

5.Gateway跨域配置

通过网关统一跨域,其它服务中不配置跨域

spring:
  application:
    name: gateway-service
  cloud:
    gateway:
      globalcors:
        cors-configurations: # 跨域配置
          '[/**]': # 匹配所有路径
            allowed-origins: # 允许的域名
              - "http://localhost:8080"
            allowed-headers: "*" # 允许的请求头
            allowed-methods: "*" # 允许的方法
            allow-credentials: true # 是否携带cookie

6.Gateway过滤器

从过滤器生命周期(影响时机点)的角度来说,主要有两个pre和post:

生命周期时机点作用
pre这种过滤器在请求被路由之前调用。我们可利用这种过滤器实现身份验证、在集群中选择请求的微服务、记录调试信息等。
post这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准的 HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等。

从过滤器类型的角度,Spring Cloud GateWay的过滤器分为GateWayFilter和GlobalFilter两种

过滤器类型影响范围
GateWayFilter应用到单个路由上
GlobalFilter应用到所有的路由上

7.使用Gateway实现单点登录

单点登录(Single Sign On),简称为 SSO,在分布式架构项目中,只需要在一个节点进行登录验证,就能够在其它的所有相关节点实现访问。

实现方案:

  1. JWT+Gateway方案
  2. OAuth2方案
  3. 共享session

以下是使用JWT+Gateway方案实现单点登录

在这里插入图片描述
实现步骤:

1.创建数据库,数据表(商品,订单,用户)

2.创建用户服务,添加所需依赖

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>

    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.5.1</version>
    </dependency>

    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-test</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>com.blb</groupId>
        <artifactId>comment_api</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>


</dependencies>

3.注册到Eureka上(启动类加注解@EnableDiscoveryClient、写配置文件)

server.port=10000
#服务名
spring.application.name=mysql-user-service
#是否从注册中心获得数据
eureka.client.register-with-eureka=true
#是否注册到注册中心上
eureka.client.fetch-registry=true
#配置注册中心的地址
eureka.client.serviceUrl.defaultZone=http://127.0.0.1:8888/eureka/

4.配置数据源和mybatis-plus

#数据库配置
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/feigin_user?serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=root

mybatis-plus.type-aliases-package=com.blb.comment.entity
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
mybatis-plus.configuration.map-underscore-to-camel-case=true
mybatis-plus.mapper-locations=classpath:mapper/*.xml

5.编写entity、mapper、service

略…

6.编写UserDetailsService实现类

@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private TUserMapper userMapper;

    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        //按用户名查询
        TUser user = userMapper.selectOne(new QueryWrapper<TUser>().lambda().eq(TUser::getUsername, s));
        if(user == null){
            throw new UsernameNotFoundException("用户名不存在");
        }
        //返回正确的用户信息
        return new org.springframework.security.core.userdetails.User(s,user.getPassword(),
                AuthorityUtils.commaSeparatedStringToAuthorityList(""));
    }
}

7.编写Security配置,登录成功的处理器


@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private LoginSuccessHandler loginSuccessHandler;

    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder(){
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //配置自定义登录逻辑
        auth.userDetailsService(userDetailsService);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //配置放行url
        http.authorizeRequests()
                .antMatchers("/swagger-ui.html","/swagger-resources/**","/webjars/**","/*/api-docs"
                        ,"/login","/logout").permitAll()
                .anyRequest().authenticated()               //配置其它url要验证
                .and()
                .formLogin()                                //配置登录相关
                .successHandler(loginSuccessHandler)  //配置登录成功的处理器
                .failureHandler((req,resp,auth) -> {        //配置登录失败的处理器
                    ResponseResult.write(resp, ResponseResult.error(ResponseStatus.LOGIN_ERROR));
                })
                .and()
                .exceptionHandling()
                .authenticationEntryPoint((req,resp,auth) ->{ //配置拦截未登录请求的处理
                    ResponseResult.write(resp,ResponseResult.error(ResponseStatus.AUTHENTICATE_ERROR));
                })
                .and()
                .logout()
                .logoutSuccessHandler((req,resp,auth) ->{     //配置登出处理器
                    ResponseResult.write(resp,ResponseResult.ok("注销成功"));
                })
                .clearAuthentication(true)                     //清除验证缓存
                .and()
                .csrf()
                .disable()                                    //关闭csrf保护
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS); //不使用session

    }
}
@Slf4j
@Component
public class LoginSuccessHandler implements AuthenticationSuccessHandler {

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        //获得用户名
        User user = (User) authentication.getPrincipal();
        //将用户名生成jwt token
        String token = JwtUtil.generateToken(user.getUsername(), RsaUtil.privateKey, JwtUtil.EXPIRE_MINUTES);
        //将token 发送给前端
        UserTokenVO userTokenVo = new UserTokenVO(user.getUsername(),token);
        ResponseResult.write(response,ResponseResult.ok(userTokenVo));
        log.info("user:{}  token:{}",user.getUsername() , token);
    }

}

8.在网关配置用户的路由

server:
  port: 9999
eureka:
  client:
    fetch-registry: true
    register-with-eureka: true
    serviceUrl:
      defaultZone: http://127.0.0.1:8888/eureka/

# 网关配置
spring:
  application:
    name: mysql-gateway-server
  cloud:
    gateway:
      routes:
      - id: order-server-route	#订单路由
        uri: lb://mysql-order-server  #订单服务名称
        predicates: #断言
        - Path=/orderlist/**,/order/**  #匹配路径

      - id: product-server-route	#商品路由
        uri: lb://mysql-product-server  #商品服务名称
        predicates: #断言
        - Path=/productlist/**,/product/**  #匹配路径

      - id: user-server-route	#用户路由
        uri: lb://mysql-user-service  #用户服务名称
        predicates: #断言
        - Path=/login,/logout,/user/**  #匹配路径

      globalcors:
        cors-configurations: # 跨域配置
          '[/**]': # 匹配所有路径
            allowed-origins: # 允许的域名(前端启动端口)
              - "http://localhost:8080"
            allowed-headers: "*" # 允许的请求头
            allowed-methods: "*" # 允许的方法
            allow-credentials: true # 是否携带cookie

user:
  white-list: # 白名单
    - /login
    - /logout

9.在gateway中编写WhiteListConfig(开发放行接口的白名单)

@Data
@Configuration
@ConfigurationProperties(prefix = "user")
public class WhiteListConfig {

    //放行白名单
    private List<String> whiteList;
}

10.编写网关过滤器(验证用户token)

/**
 * 用户验证过滤器
 * @author Charon
 */
@Slf4j
@Component
public class AuthenticationFilter implements GlobalFilter, Ordered {

    @Autowired
    private WhiteListConfig whiteListConfig;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //获得请求和响应对象
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();
        //对白名单中的地址放行
        List<String> whiteList = whiteListConfig.getWhiteList();
        for(String str : whiteList){
            if(request.getURI().getPath().contains(str)){
                log.info("白名单,放行{}",request.getURI().getPath());
                return chain.filter(exchange);
            }
        }
        //获得请求头中Authorization token信息
        String token = request.getHeaders().getFirst("Authorization");
        try{
            //解析token
            String username = JwtUtil.getUsernameFromToken(token, RsaUtil.publicKey);
            log.info("{}解析成功,放行{}",username,request.getURI().getPath());
            return chain.filter(exchange);
        }catch (Exception ex){
            log.error("token解析失败",ex);
            //返回验证失败的响应信息
            response.setStatusCode(HttpStatus.UNAUTHORIZED);

            DataBuffer wrap = response.bufferFactory().wrap("验证错误,需要登录".getBytes());
            return response.writeWith(Mono.just(wrap));
        }
    }

    @Override
    public int getOrder() {
        return 0;
    }
}

使用springboot+eureka+feign+gateway+vue实现单点登录后对商品、订单进行CRUD,具体代码详见gitee仓库地址
在这里插入图片描述

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐