SpringBoot之Zuul服务

发布于:2024-05-05 ⋅ 阅读:(20) ⋅ 点赞:(0)

概述

Spring Cloud Netflix zuul组件是微服务架构中的网关组件,其核心功能是路由和过滤,目前公司业务就是基于Zuul搭建的网关服务,本文主要内容:Zuul的执行流程、Zuul过滤实现、Zuul安全配置

Zuul服务

Zuul执行流程

  • 接收请求
    • 客户端发送请求到Zuul服务器(前置经过Nginx反向代理)
  • Pre-filters
    • 请求首先通过Pre类型的过滤器,这些过滤器可以进行安全检查、身份验证、日志记录、选择路由等操作。
  • 路由转发Routing
    • 如果Pre-filter通过请求将被路由到具体的微服务实例上。Zuul 可以根据配置的路由规则将请求转发到不同的后端服务。
  • 服务调用
    • Zuul通过Fegin进行客户端负载均衡,选择一个服务实例进行调用
  • Post-filters
    • 服务处理完成后,响应会返回并经过 Post 类型的过滤器,这些过滤器可以修改返回的响应头、收集统计信息等。最终Zuul将响应发送回客户端
  • Error-filters
    • 如果在任何阶段发生错误,请求将被转发到 Error 类型的过滤器,进行错误处理后经过Post-filters,最终Zuul将响应发送回客户端

Zuul过滤器

pre-filter

前置过滤器在请求被路由到源服务器之前执行,通常继承ZuulFilter抽象类。实际项目中我们使用前置过滤器对于400到500的状态错误码进行过滤,进而对于从cas认证服务中获取token的特定请求进行错误日志的记录

package test.gateway.filter;

import org.springframework.cloud.netflix.zuul.util.ZuulRuntimeException;
import test.common.util.R;
import com.google.gson.Gson;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;

/**
 * @Description: 捕获400到500错误码,否则不执行该过滤器
 */
public class ErrorStatusCodeZuulFilter extends ZuulFilter {
	
	protected static String GET_TOKEN_URI =  "/cas/getToken";
	
    @Override
    public String filterType() {
        return "post";
    }

    @Override
    public int filterOrder() {
        return -1; // Needs to run before SendResponseFilter which has filterOrder == 0
    }

    @Override
    public boolean shouldFilter() {    		
    		int statusCode = Integer.parseInt(RequestContext.getCurrentContext().get("responseStatusCode").toString());
    		return statusCode >= 400 && statusCode <= 500;
    }

	@Override
	public Object run() {
		RequestContext ctx = RequestContext.getCurrentContext();
		int statusCode = Integer.parseInt( ctx.get("responseStatusCode").toString());	
		String uri = ctx.get("requestURI").toString();
		if (uri != null && uri.equals(GET_TOKEN_URI)) { //获取token请求失败
			R<String>  res = new R<>();		
			res.setCode(statusCode);
			res.setData(null);
			res.setMsg("Account or password error");
			ctx.setResponseBody(new Gson().toJson(res));
			return null;
		}
		try {
			throw new Exception();
		} catch (Throwable t) {	    		
			throw new ZuulRuntimeException(new ZuulException(t, statusCode, t.getMessage()));
		}
	}
}
routing-filter

处理将请求发送到源服务器的逻辑

post-filter

后置过滤器在请求被路由后执行,可以对响应进行修改或添加额外的HTTP等。

error-filter

当请求处理过程中发生错误时,这些过滤器会被调用,同时最终也会调用后置post类型过滤器返回 

扩展-OncePerRequestFilter

OncePerRequestFilter是Spring Framework中的一个接口,它属于Servlet过滤器(Filter)的范畴,但不是Zuul中定义的特定过滤器类型。在Spring MVC中,OncePerRequestFilter确保每个请求只被处理一次。它通过同步代码块来保证,即使在支持异步处理的 Spring MVC 应用程序中,请求也不会被重复处理。项目中我们通过自定义过滤器(继承OncePerRequestFilter)处理访问公共接口且未携带token的请求,该过滤器也属于前置的过滤器,因为它通常用于在请求进入其他阶段之前执行某些操作。

Zuul安全配置

自定义WebSecurityConfig

在Spring Security的上下文中,WebSecurityConfig类通常用于配置Spring Security的安全策略。此类继承自WebSecurityConfigurerAdapter,WebSecurityConfig可以用来定义Zuul网关的安全设置,通过提供了一种自定义安全配置的方法实现,实际项目中我们对zuul进行的http请求相关安全配置

package test.zuul.config;
import ...

/**
 * @Description: Zuul http configuration
 */
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter { // 定义Zuul网关的安全设置
	private final static Logger logger = LoggerFactory.getLogger(WebSecurityConfig.class);
	
	@Autowired
    private FilterIgnorePropertiesConfig filterIgnorePropertiesConfig;
	
	@Autowired
	private FindByIndexNameSessionRepository<? extends Session> sessionRepository;
	
	@Autowired
	private RedisOperationsSessionRepository redisOperationsSessionRepository; 
	
	@Autowired
	private AuthenticationServiceFeign authenticationService;
	
    @Override
    public void configure(HttpSecurity http) throws Exception {
    		http
    			.csrf()
    			.disable(); //禁用csrf
    		http
    			.headers()
    			.frameOptions()
    			.disable();
    		ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry =
	    	http
	    		.antMatcher("/**") // 允许所有请求
		        .authorizeRequests();
			filterIgnorePropertiesConfig.getUrls().forEach(url -> registry.antMatchers(url).permitAll());
			registry
		    	.anyRequest()
		        .authenticated();
		    http
		        .exceptionHandling().authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login/idpbg"))	        
		        .and()
		        .logout().logoutUrl("/logout").addLogoutHandler(new LogoutHandler() { //登出后处理逻辑
					@Override
					public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {					
						if(authentication != null) {
							String indexName = authentication.getName();
							// 查询用户的 Session 信息      
							Map<String, ? extends Session> userSessions = sessionRepository.findByIndexNameAndIndexValue(FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME, indexName);
							
							// 删除当前用户相关session
							List<String> sessionIds = new ArrayList<>(userSessions.keySet());
							for (String session : sessionIds) {
								redisOperationsSessionRepository.deleteById(session);
							}
							//删除系统当前在线用户信息...
						}
						Cookie cookie = new Cookie();
						//重置cookie...
						response.addCookie(cookie);
					}
				}).logoutSuccessHandler(logoutSuccessHandler()).oauth2Login().and().oauth2Client();	
    }
    
    private LogoutSuccessHandler logoutSuccessHandler() { //登出成功后处理逻辑
		return new LogoutSuccessHandler() {
			@Override
			public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
					throws IOException, ServletException {
				if(authentication != null) {
					if(authentication.getDetails() instanceof OAuth2AuthenticationDetails) {
						// 刪除JWT
						......
					}
				}
				new SecurityContextLogoutHandler().logout(request, null, null);
				try {
					response.sendRedirect("/"); //重定向到登录页面
					}
		        } catch (IOException e) {
		            e.printStackTrace();
		        }
			}
		};
	}
}


网站公告

今日签到

点亮在社区的每一天
去签到