package jnpf.filter; import cn.dev33.satoken.context.SaHolder; import cn.dev33.satoken.context.model.SaRequest; import cn.dev33.satoken.context.model.SaResponse; import cn.dev33.satoken.filter.SaFilterAuthStrategy; import cn.dev33.satoken.router.SaRouter; import jnpf.constant.GlobalConst; import jnpf.consts.AuthConsts; import jnpf.properties.MvcSecurityProperties; import jnpf.properties.SecurityProperties; import jnpf.util.Constants; import jnpf.util.ReactorUtil; import jnpf.util.UserProvider; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.cloud.gateway.filter.headers.HttpHeadersFilter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.Order; import org.springframework.http.HttpStatus; import org.springframework.util.ObjectUtils; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.reactive.CorsConfigurationSource; import org.springframework.web.cors.reactive.CorsWebFilter; import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource; import java.net.URI; /** * mvc配置 * * @author JNPF开发平台组 * @version V5.0.0 * @copyright 引迈信息技术有限公司(https://www.jnpfsoft.com) * @date 2025-01-21 */ @Slf4j @Configuration(proxyBeanMethods = false) public class MvcSecurityConfig { private static final String DOMAIN_FORMAT = "%s://%s"; @Autowired private MvcSecurityProperties mvcSecurityProperties; @Autowired private SecurityProperties securityProperties; /** * Header过滤器, 为请求添加 Id-Token * @return */ @Bean public HttpHeadersFilter getMyHeadersFilter(){ return (input, exchange) -> { if(securityProperties.isEnableInnerAuth() || securityProperties.isEnablePreAuth()){ input.add(AuthConsts.INNER_GATEWAY_TOKEN_KEY, UserProvider.getInnerAuthToken()); } input.add(GlobalConst.HEADER_HOST, input.getHost().toString()); return input; }; } @Bean public GlobalFilter getContextGatewayFilterForReactor(){ return new ContextGatewayFilterForReactor(); } @Bean public CorsWebFilter corsFilter() { UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); CorsConfiguration config = new CorsConfiguration(); // 允许发送凭据 config.setAllowCredentials(true); //允许任意域名跨域访问接口 config.setAllowedOrigins(mvcSecurityProperties.getCors().getAllowedOrigins()); config.setAllowedOriginPatterns(mvcSecurityProperties.getCors().getAllowedOriginPatterns()); // 允许所有头部信息 config.setAllowedHeaders(mvcSecurityProperties.getCors().getAllowedHeaders()); // 允许所有请求方法 config.setAllowedMethods(mvcSecurityProperties.getCors().getAllowedMethods()); // 应用于所有路径 source.registerCorsConfiguration("/**", config); return new MyCorsFilter(source); } @Bean @ConditionalOnMissingBean public SaFilterAuthStrategy defaultBeforeAuthStrategy() { CorsConfiguration csrfConfiguration; if(!mvcSecurityProperties.getCsrfOrigins().isEmpty() || !mvcSecurityProperties.getCsrfOriginsPatterns().isEmpty()) { csrfConfiguration = new CorsConfiguration(); csrfConfiguration.setAllowedOrigins(mvcSecurityProperties.getCsrfOrigins()); csrfConfiguration.setAllowedOriginPatterns(mvcSecurityProperties.getCsrfOriginsPatterns()); } else { csrfConfiguration = null; } return obj -> { SaRequest request = SaHolder.getRequest(); // ---------- 设置跨域响应头 ---------- SaResponse response = SaHolder.getResponse(); if(!ObjectUtils.isEmpty(mvcSecurityProperties.getHeaders().getServerName())){ response.setServer(mvcSecurityProperties.getHeaders().getServerName()); } if(!ObjectUtils.isEmpty(mvcSecurityProperties.getHeaders().getXFrameOptions()) && !MvcSecurityProperties.XFrameOptionsMode.DISABLED.equals(mvcSecurityProperties.getHeaders().getXFrameOptions())){ response.setHeader(MvcSecurityProperties.HEADER_XFRAME_OPTIONS, mvcSecurityProperties.getHeaders().getXFrameOptions().getMode()); } if(!ObjectUtils.isEmpty(mvcSecurityProperties.getHeaders().getXXssProtection()) && !MvcSecurityProperties.XXssProtectionMode.DISABLED.equals(mvcSecurityProperties.getHeaders().getXXssProtection())){ response.setHeader(MvcSecurityProperties.HEADER_XSS_PROTECTION, mvcSecurityProperties.getHeaders().getXXssProtection().getMode()); } if(!ObjectUtils.isEmpty(mvcSecurityProperties.getHeaders().getXContentTypeOptions()) && !MvcSecurityProperties.XContentTypeOptions.DISABLED.equals(mvcSecurityProperties.getHeaders().getXContentTypeOptions())){ response.setHeader(MvcSecurityProperties.HEADER_Content_Type_Options, mvcSecurityProperties.getHeaders().getXContentTypeOptions().getMode()); } if(csrfConfiguration != null){ String referer = request.getHeader("referer"); if(!ObjectUtils.isEmpty(referer)) { URI uri = URI.create(referer); String refererDomain = String.format(DOMAIN_FORMAT, uri.getScheme(), uri.getAuthority()); String allowOrign = csrfConfiguration.checkOrigin(refererDomain); if(ObjectUtils.isEmpty(allowOrign)){ log.error("Reject CSRF Request: {}, {}, {}, {}", request.getRequestPath(), referer, ReactorUtil.getIpAddr(request), request.getHeader(Constants.AUTHORIZATION)); response.setStatus(HttpStatus.FORBIDDEN.value()); SaRouter.back("Invalid CSRF Request"); } } } }; } @Order(-110) public static class MyCorsFilter extends CorsWebFilter { public MyCorsFilter(CorsConfigurationSource configSource) { super(configSource); } } }