文章目录
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.通过所有过滤器才能访问到需要的服务- 1
- 2
- 3
- 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/** - 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
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- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
6.Gateway过滤器
从过滤器生命周期(影响时机点)的角度来说,主要有两个pre和post:
| 生命周期时机点 | 作用 |
|---|---|
| pre | 这种过滤器在请求被路由之前调用。我们可利用这种过滤器实现身份验证、在集群中选择请求的微服务、记录调试信息等。 |
| post | 这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准的 HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等。 |
从过滤器类型的角度,Spring Cloud GateWay的过滤器分为GateWayFilter和GlobalFilter两种
| 过滤器类型 | 影响范围 |
|---|---|
| GateWayFilter | 应用到单个路由上 |
| GlobalFilter | 应用到所有的路由上 |
7.使用Gateway实现
单点登录(Single Sign On),简称为 SSO,在分布式架构项目中,只需要在一个节点进行登录验证,就能够在其它的所有相关节点实现访问。
实现方案:
- JWT+Gateway方案
- OAuth2方案
- 共享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>- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
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/- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
4.配置数据源和mybatis-plus
#数据库配置spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driverspring.datasource.url=jdbc:mysql://localhost:3306/feigin_user?serverTimezone=UTCspring.datasource.username=rootspring.datasource.password=rootmybatis-plus.type-aliases-package=com.blb.comment.entitymybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImplmybatis-plus.configuration.map-underscore-to-camel-case=truemybatis-plus.mapper-locations=classpath:mapper/*.xml- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
5.编写entity、mapper、service
略…
6.编写UserDetailsService实现类
@Servicepublic 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("")); }}- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
7.编写Security配置,登录成功的处理器
@Configurationpublic 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 }}- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
@Slf4j@Componentpublic 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); }}- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
8.在网关配置用户的路由
server: port: 9999eureka: 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 # 是否携带cookieuser: white-list: # 白名单 - /login - /logout- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
9.在gateway中编写WhiteListConfig(开发放行接口的白名单)
@Data@Configuration@ConfigurationProperties(prefix = "user")public class WhiteListConfig { //放行白名单 private List<String> whiteList;}- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
10.编写网关过滤器(验证用户token)
/** * 用户验证过滤器 * @author Charon */@Slf4j@Componentpublic 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; }}- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
使用springboot+eureka+feign+gateway+vue实现单点登录后对商品、订单进行CRUD,具体代码详见