SpringSecurity学习笔记(十一)CSRF攻击以及CORS跨域

参考视频

什么是CSRF

CSRF:跨站请求伪造。也可称为一站式攻击。也可写作XSRF。
按照字面意思来理解,跨站请求伪造,意思就是说用户登录了A网站之后,会话没有过期,然后登录了B网站,这个时候B网站中的请求访问了A网站,这个时候A网站就会认为是合法的用户的请求,这个时候用户是无感知的,从而导致用户在A网站的账户出现安全问题。特别是在一些银行之类的网站上如果受到这种攻击,造成的损失是致命的。

CSRF防御

CSRF攻击的原因,就是用户的浏览器会携带cookie,然而服务器不会判断这个cookie是不是这个网站内部发送的,这样我们就没有办法拒绝这样的请求。如果能够在请求中携带一个攻击者无法携带的参数就可以避免CSRF攻击 ,SS中就提供了这样一种机制,称为令牌同步机制

就是在服务端会生成一个令牌,这个令牌在httpsession中也保存一份,任何一个请求都需要携带这个令牌,这样就可以验证用户的请求是不是站内的请求。

前后端不分离项目中的csrf

在没有开启csrf之前登录界面的代码,这和我们
在这里插入图片描述
开启了csrf之后的登录界面源代码
在这里插入图片描述

可以看到这里有一个_csrf的变量,值是一个后端生成特定的值。这个值在其他的网站中是无法获取的,所以就可以避免scrf攻击。

前后端分离系统的csrf防御

针对于前后端分离的系统,我们的value值不再保存在服务器端而是直接返回给前端,前端每次请求获取这个值进行提交。这个时候我们可能会像cookie也会被第三方网站获取之后发起请求,但是实际上我们的前端系统并不是直接传递cookie的值,而是对cookie进行一定的封装格式化之后传递到后端,具体格式化的方式我们项目的开发者是知道的,但是第三方网站是不知道的,这就实现了前后端分离系统的csrf防御。

后端配置

.csrf()
//将令牌保存到cookie中,允许cookie前端获取,这样前端就可以通过js获取cookie的值
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())

这里面涉及到一个类是CsrfFilter,它里面有一个方法

@Override
	protected void doFilterInternal(HttpServletRequest request,
			HttpServletResponse response, FilterChain filterChain)
					throws ServletException, IOException {
    
    
		request.setAttribute(HttpServletResponse.class.getName(), response);

//这里后端自己计算csrfToken 
		CsrfToken csrfToken = this.tokenRepository.loadToken(request);
		final boolean missingToken = csrfToken == null;
		if (missingToken) {
    
    
			csrfToken = this.tokenRepository.generateToken(request);
			this.tokenRepository.saveToken(csrfToken, request, response);
		}
		request.setAttribute(CsrfToken.class.getName(), csrfToken);
		request.setAttribute(csrfToken.getParameterName(), csrfToken);

		if (!this.requireCsrfProtectionMatcher.matches(request)) {
    
    
			filterChain.doFilter(request, response);
			return;
		}
//这里从前端获取csrfToken 
		String actualToken = request.getHeader(csrfToken.getHeaderName());
		if (actualToken == null) {
    
    
			actualToken = request.getParameter(csrfToken.getParameterName());
		}
		//最后比较是不是相同,如果相同就可以通过,如果不相同还会判断是否是幂等的请求,如果是幂等的请求也会直接通过。
		if (!csrfToken.getToken().equals(actualToken)) {
    
    
			if (this.logger.isDebugEnabled()) {
    
    
				this.logger.debug("Invalid CSRF token found for "
						+ UrlUtils.buildFullRequestUrl(request));
			}
			if (missingToken) {
    
    
				this.accessDeniedHandler.handle(request, response,
						new MissingCsrfTokenException(actualToken));
			}
			else {
    
    
				this.accessDeniedHandler.handle(request, response,
						new InvalidCsrfTokenException(csrfToken, actualToken));
			}
			return;
		}

		filterChain.doFilter(request, response);
	}

我们可以在CookieCsrfTokenRepository源码中看到如果是从请求头里面获取的话key就是

static final String DEFAULT_CSRF_HEADER_NAME = "X-XSRF-TOKEN";

如果是在请求参数中获取的话就是

static final String DEFAULT_CSRF_PARAMETER_NAME = "_csrf";

跨域

如果源地址与目标地址中,协议、主机、端口有任何一个不同都是跨域的。

对于简单的get请求来说,如果要发起一个跨域请求,则请求头会加入Host、Origin、Reference字段,如果服务器支持跨域则返回的响应头中会包含一个Access-Control-Allow-Origin的字段。这时候浏览器发现了这个字段就不会对跨域请求进行限制,这种是不需要进行预检请求的跨域。

对于不是get的请求,也就是非简单请求,会受限发起一个options的预检请求,请求头Origin告诉服务器当前页面所在的域,请求头Access-Control-Request-Methods告诉服务器即将发起的跨域请求所使用的方法,服务端对此进行判断,如果允许跨域则会给出特定的响应,浏览器进而发起真正的请求。

Spring解决跨域的方法

  • 使用@CrossOrigin注解,可以用在类和方法上。

编写前端页面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>

<script>

    axios.get("http://localhost:8080/public/cors");
</script>
<body>

</body>
</html>

后端接口

@GetMapping("/public/cors")
    @ResponseBody
    public String cors(){
    
    
        System.out.println("cors - test");
        return "cors test";
    }

此时打开前端页面
在这里插入图片描述
此时我在后端方法上加上@CrossOrigin注解重新访问前端页面,此时就不会报错了
在这里插入图片描述

  • 第二种方式,实现WebMvcConfigurer接口,重写addCorsMappings方法。这是基于SpringMvc的解决方案。这种方式相对于前面更加方便,不用每个接口都加注解。
@Override
    public void addCorsMappings(CorsRegistry registry) {
    
    

        registry.addMapping("/**")
                .allowedMethods("*")
                .allowedOrigins("*")
                .allowCredentials(false)
                .allowedHeaders("*")
                .maxAge(3600)
                .exposedHeaders("")
        ;
    }

注释掉原来方法上的注解然后访问
在这里插入图片描述

  • 第三种方式,springweb给我们提供了一个处理跨域问题的过滤器CorsFilter

注入一个bean

@Bean
    public FilterRegistrationBean<CorsFilter> corsFilter() {
    
    
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowCredentials(true);
        config.addAllowedOrigin("*");
        config.addAllowedHeader("*");
        config.addAllowedMethod("*");
        source.registerCorsConfiguration("/**", config);
        FilterRegistrationBean<CorsFilter> bean = new FilterRegistrationBean<>(new CorsFilter(source));
        bean.setOrder(0);
        return bean;
    }

在这里插入图片描述

SS中的CORS跨域处理

如果项目中引入了SS的依赖,就会导致我们前面三种解决跨域的方式中前两种失效,后面一种还需要看它过滤器的优先级是不是比ss中的过滤器的优先级高,是的话就可以生效,否则跨域处理失败。前两种都是基于拦截器处理的,拦截器是处理的很晚的,请求已经经过了过滤器、dispatcherservlet到达controller之前被拦截器处理,在这之前会经过ss的过滤器,所以前两种一定是会失效的。当然,是针对于非简单方式的请求,简单方式的请求依然会生效。
为什么呢?因为非简单请求的预检不会携带认证信息,所以预检请求会被ss的过滤器过滤出去,那么跨域就无法处理了。

var s = axios.get("http://localhost:8080/public/cors");

    let post = axios.post('http://localhost:8080/public/post/cors');

在ss中我们可以直接配置

	.cors()
    .configurationSource(configurationSource())
    .and()


CorsConfigurationSource configurationSource(){
    
    
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.setAllowedHeaders(Arrays.asList("*"));
        corsConfiguration.setAllowedMethods(Arrays.asList("*"));
        corsConfiguration.setAllowedOrigins(Arrays.asList("*"));
        corsConfiguration.setMaxAge(3600l);
        UrlBasedCorsConfigurationSource configurationSource = new UrlBasedCorsConfigurationSource();
        configurationSource.registerCorsConfiguration("/**",corsConfiguration);
        return configurationSource;
    }

猜你喜欢

转载自blog.csdn.net/qq_45401910/article/details/127201138