• SpringSecurity源码学习五:跨域与跨站请求伪造


    什么是跨域

    跨域是指在网络中,当一个网页的资源(如字体、脚本或样式表)尝试从不同的域名、端口或协议请求数据时,会遇到安全限制问题。这是由于浏览器的同源策略所导致的。同源策略要求网页只能从同一域名下加载资源,而跨域请求则违反了这个策略。

    为了解决跨域问题,可以采取一些方法,如使用JSONP、CORS、代理服务器等。JSONP是通过动态创建

    总结起来,跨域是指在网络中由于浏览器的同源策略而限制了不同域名、端口或协议之间的资源请求。通过使用适当的跨域解决方案,可以允许跨域请求并获取所需的数据。一般情况下我们呢都是使用CORS(跨域资源共享)来解决跨域问题。

    springboot是怎么解决跨域问题

    在Java中,可以使用CORS(跨域资源共享)来解决跨域问题。下面是一个Java代码示例,演示如何在Spring Boot应用程序中配置CORS。

    首先,在Spring Boot应用程序的配置类中添加以下代码:

    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.servlet.config.annotation.CorsRegistry;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
     @Configuration
    public class CorsConfig implements WebMvcConfigurer {
        @Override
        public void addCorsMappings(CorsRegistry registry) {
            registry.addMapping("/**")
                    .allowedOrigins("http://example.com")  // 允许的跨域请求来源
                    .allowedMethods("GET", "POST")  // 允许的请求方法
                    .allowedHeaders("Origin", "Content-Type", "Accept")  // 允许的请求头
                    .allowCredentials(true);  // 允许发送身份凭证
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    在上述代码中,我们通过 addMapping() 方法指定了允许跨域请求的路径,使用 allowedOrigins() 方法指定了允许的跨域请求来源,使用 allowedMethods() 方法指定了允许的请求方法,使用 allowedHeaders() 方法指定了允许的请求头。最后,通过 allowCredentials(true) 方法允许发送身份凭证(如Cookie)。

    以上只是个代码示例,实际使用时要根据具体情况做响应配置。

    在springSecurity中怎么解决跨域问题

    在Spring Security中,你可以使用 CorsConfigurationSource 接口来配置CORS(跨域资源共享)策略。下面是一个Java代码示例,演示如何在Spring Security中配置CORS。
    首先,在你的Spring Security配置类中添加以下代码:

    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.cors.CorsConfiguration;
    import org.springframework.web.cors.CorsConfigurationSource;
    import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
     @Configuration
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
         @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.cors();
            // ...
        }
         @Bean
        public CorsConfigurationSource corsConfigurationSource() {
            CorsConfiguration configuration = new CorsConfiguration();
            configuration.setAllowedOrigins(Arrays.asList("http://localhost:8080"));
            configuration.setAllowedMethods(Arrays.asList("GET", "POST"));
            configuration.setAllowedHeaders(Arrays.asList("Authorization", "Cache-Control", "Content-Type"));
            UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
            source.registerCorsConfiguration("/**", configuration);
            return source;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    在上述代码示例中,我们通过调用 http.cors()> 方法来允许跨域请求。然后,我们定义了一个 CorsConfigurationSource 的Bean,用于配置CORS策略。在这个Bean中,我们设置了允许的跨域请求来源、允许的请求方法和请求头。

    最后,我们使用 UrlBasedCorsConfigurationSource 类来注册CORS配置,并将其应用于所有的请求路径( /** )。

    CORS源码

    源码入口:

    	public CorsConfigurer<HttpSecurity> cors() throws Exception {
    		return getOrApply(new CorsConfigurer<>());
    	}
    
    	private <C extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity>> C getOrApply(C configurer)
    			throws Exception {
    		C existingConfig = (C) getConfigurer(configurer.getClass());
    		if (existingConfig != null) {
    			return existingConfig;
    		}
    		//执行configurer
    		return apply(configurer);
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    可以看到最终就是获取一个CorsConfigurer类,我们看CorsConfigurer类的逻辑。主要看CorsConfigurer类的configure()方法

    	@Override
    	public void configure(H http) {
    		ApplicationContext context = http.getSharedObject(ApplicationContext.class);
    		//获取corsFilter过滤器
    		CorsFilter corsFilter = getCorsFilter(context);
    		Assert.state(corsFilter != null, () -> "Please configure either a " + CORS_FILTER_BEAN_NAME + " bean or a "
    				+ CORS_CONFIGURATION_SOURCE_BEAN_NAME + "bean.");
    		//把corsFilter过滤器添加到过滤器链中
    		http.addFilter(corsFilter);
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    获取corsFilter过滤器并把它添加到过滤器链中。看getCorsFilter(context)方法

    	private CorsFilter getCorsFilter(ApplicationContext context) {
    		if (this.configurationSource != null) {
    			return new CorsFilter(this.configurationSource);
    		}
    		//corsFilter过滤器bean是否存在
    		boolean containsCorsFilter = context.containsBeanDefinition(CORS_FILTER_BEAN_NAME);
    		if (containsCorsFilter) {
    			return context.getBean(CORS_FILTER_BEAN_NAME, CorsFilter.class);
    		}
    		//corsConfigurationSource对象是否存在
    		boolean containsCorsSource = context.containsBean(CORS_CONFIGURATION_SOURCE_BEAN_NAME);
    		if (containsCorsSource) {
    			//把corsConfigurationSource对象塞进CorsFilter过滤器中
    			CorsConfigurationSource configurationSource = context.getBean(CORS_CONFIGURATION_SOURCE_BEAN_NAME,
    					CorsConfigurationSource.class);
    			return new CorsFilter(configurationSource);
    		}
    		boolean mvcPresent = ClassUtils.isPresent(HANDLER_MAPPING_INTROSPECTOR, context.getClassLoader());
    		if (mvcPresent) {
    			return MvcCorsFilter.getMvcCorsFilter(context);
    		}
    		return null;
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    可以看到,对应cors处理有两种情况,

    1. 没有配置corsConfigurationSource对象,直接使用默认的corsFilter过滤器
    2. 配置corsConfigurationSource对象,把配置corsConfigurationSource对象添加到默认的过滤器。

    最后是调用的springWeb的CorsFilter,并把此过滤器加入到SpringSecurity过滤器链中。

    跨域请求伪造CSRF

    跨域请求伪造(Cross-Site Request Forgery,CSRF)是一种安全漏洞,攻击者利用该漏洞通过伪造请求来执行未经授权的操作。在CSRF攻击中,攻击者诱使受害者在已登录的状态下访问恶意网站,从而触发受害者在其他网站上的操作,如转账、更改密码等。

    在前后端分离的架构中,可以使用CSRF令牌(CSRF Token)来验证请求的合法性。下面是一个使用Spring Security防止CSRF攻击的Java代码示例:

    1. 配置Spring Security启用CSRF保护:
    @Configuration
    @EnableWebSecurity
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
         @Override
        protected void configure(HttpSecurity http) throws Exception {
            http
                // 其他Spring Security配置
                .and()
                .csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    1. 在前端页面中设置CSRF令牌:
    html
    <html>
    <body>
        <form method="post" action="/submit">
            <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" />
            <!-- 其他表单字段 -->
            <button type="submit">提交</button>
        </form>
    </body>
    </html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    在上述示例中,首先通过 csrf() 方法启用了CSRF保护,并使用 CookieCsrfTokenRepository 来存储CSRF令牌。然后,在前端页面的表单中,通过 ${_csrf.parameterName} 和 ${_csrf.token} 获取CSRF令牌,并将其作为隐藏字段传递给服务器。

    这样,在每次提交表单时,CSRF令牌会被包含在请求中,服务器会验证请求中的CSRF令牌是否与服务器生成的令牌匹配,从而防止CSRF攻击的发生。

    CSRF源码

    代码入口:

    	public CsrfConfigurer<HttpSecurity> csrf() throws Exception {
    		ApplicationContext context = getContext();
    		return getOrApply(new CsrfConfigurer<>(context));
    	}
    
    	private <C extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity>> C getOrApply(C configurer)
    			throws Exception {
    		C existingConfig = (C) getConfigurer(configurer.getClass());
    		if (existingConfig != null) {
    			return existingConfig;
    		}
    		//执行configurer
    		return apply(configurer);
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    主要是看CsrfConfigurer这个类

    public final class CsrfConfigurer<H extends HttpSecurityBuilder<H>>
    		extends org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer<CsrfConfigurer<H>, H> {
    
    	//初始化CsrfTokenRepository,默认为LazyCsrfTokenRepository
    	private CsrfTokenRepository csrfTokenRepository = new LazyCsrfTokenRepository(new HttpSessionCsrfTokenRepository());
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    默认是使用LazyCsrfTokenRepository,并使用HttpSessionCsrfTokenRepository初始化。接下俩看CsrfConfigurer的configure方法。

    	@Override
    	public void configure(H http) {
    		//初始化CsrfFilter过滤器
    		CsrfFilter filter = new CsrfFilter(this.csrfTokenRepository);
    		RequestMatcher requireCsrfProtectionMatcher = getRequireCsrfProtectionMatcher();
    		if (requireCsrfProtectionMatcher != null) {
    			filter.setRequireCsrfProtectionMatcher(requireCsrfProtectionMatcher);
    		}
    		AccessDeniedHandler accessDeniedHandler = createAccessDeniedHandler(http);
    		if (accessDeniedHandler != null) {
    			filter.setAccessDeniedHandler(accessDeniedHandler);
    		}
    		org.springframework.security.config.annotation.web.configurers.LogoutConfigurer<H> logoutConfigurer = http.getConfigurer(org.springframework.security.config.annotation.web.configurers.LogoutConfigurer.class);
    		if (logoutConfigurer != null) {
    			logoutConfigurer.addLogoutHandler(new CsrfLogoutHandler(this.csrfTokenRepository));
    		}
    		org.springframework.security.config.annotation.web.configurers.SessionManagementConfigurer<H> sessionConfigurer = http.getConfigurer(org.springframework.security.config.annotation.web.configurers.SessionManagementConfigurer.class);
    		if (sessionConfigurer != null) {
    			sessionConfigurer.addSessionAuthenticationStrategy(getSessionAuthenticationStrategy());
    		}
    		filter = postProcess(filter);
    		//加载到过滤器链中
    		http.addFilter(filter);
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    这段很简单,初始化CsrfFilter,并把CsrfFilter添加到过滤器链中。

    在LazyCsrfTokenRepository中,就是对CsrfToken的操作

    public interface CsrfTokenRepository {
    
    	/**
    	 * Generates a {@link CsrfToken}
    	 * @param request the {@link HttpServletRequest} to use
    	 * @return the {@link CsrfToken} that was generated. Cannot be null.
    	 */
    	CsrfToken generateToken(HttpServletRequest request);
    
    	/**
    	 * Saves the {@link CsrfToken} using the {@link HttpServletRequest} and
    	 * {@link HttpServletResponse}. If the {@link CsrfToken} is null, it is the same as
    	 * deleting it.
    	 * @param token the {@link CsrfToken} to save or null to delete
    	 * @param request the {@link HttpServletRequest} to use
    	 * @param response the {@link HttpServletResponse} to use
    	 */
    	void saveToken(CsrfToken token, HttpServletRequest request, HttpServletResponse response);
    
    	/**
    	 * Loads the expected {@link CsrfToken} from the {@link HttpServletRequest}
    	 * @param request the {@link HttpServletRequest} to use
    	 * @return the {@link CsrfToken} or null if none exists
    	 */
    	CsrfToken loadToken(HttpServletRequest request);
    
    }
    
    • 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

    具体的实现类就不展开说,感兴趣的可自行查看代码。

    总结

    1. 跨域问题:当前端应用和后端API不在同一个域下时,浏览器会限制跨域请求。为了解决跨域问题,可以在后端配置允许跨域请求的头信息,如Access-Control-Allow-Origin。
    2. CSRF保护:跨站请求伪造(CSRF)是一种安全漏洞,攻击者利用用户在其他网站上的身份信息发起恶意请求。为了防止CSRF攻击,可以在Spring Security中启用CSRF保护。通过生成和验证CSRF令牌,确保请求的合法性。
      • 在后端配置中启用CSRF保护,并指定CSRF令牌的存储方式,如CookieCsrfTokenRepository。
      • 在前端发起请求时,将CSRF令牌作为请求的参数或头信息的一部分发送给后端。
      • 后端服务器验证请求中的CSRF令牌是否与服务器生成的令牌匹配,以确保请求的合法性。
  • 相关阅读:
    PC辉光效果一切正常,安卓辉光却没效果、显示异常(爆闪、黑屏等)
    springboot基于JAVA的电影推荐系统的开发与实现毕业设计源码112306
    数据库之MVCC
    yolov5训练与模型量化
    10 项目沟通管理
    小样本分割:构建数据集Pascal-5i
    MySQL基础篇
    前端之JS设计模式
    sizeof函数的用法
    SwiftUI4.0在iOS 16中新添加的inner和drop阴影效果
  • 原文地址:https://blog.csdn.net/qq_27586963/article/details/133967637