Day244.Springsecurity解决跨域问题、CSRF跨域攻击防护、JWT集群应用方案 -springsecurity-jwt-oauth2

1.解决跨域访问的问题

一、CORS简述

要说明CORS(Cross Origin Resourse-Sharing) 跨站资源共享,就必须先说同源策略。长话短说,同源策略就是向服务端发起请求的时候,以下三项必须与当前浏览器应用一致:域名、端口、协议。用白话说:就是你的应用发送请求不能访问别人的资源,否则浏览器就会限制你。

当然也有例外,如:img、srcipt、iframe等资源引用的HTML标签不受同源策略的限制。

Day244.Springsecurity解决跨域问题、CSRF跨域攻击防护、JWT集群应用方案   -springsecurity-jwt-oauth2

但是我们实际开发中又经常会跨站访问,比如前后端分离的应用是分开部署的,在浏览器看来是两个域。所以同源策略是用来禁止跨域访问的,CORS正好相反是根据自己的需求与规则,有限的开放部分资源的共享。

二、Spring-CORS规则基础配置

想在Spring或Spring Boot的web环境下实现跨域资源共享,主要有三种实现方式:

  • @CrossOrigin注解,这个注解是作用于Controller类或者请求方法上的,实现局部接口的跨域资源共享。
  • 实现WebMvcConfigurer接口addCorsMappings方法,实现全局配置的跨域资源共享。
  • 注入CorsFilter过滤器,实现全局配置的跨域资源共享。推荐使用。

这三种实现方式在我的另外一篇文章《SpringBoot解决跨域访问的问题》中已经介绍过,这里就不多做说明了。

三、Spring Security 中的配置CORS

当我们的应用使用了Spring Security之后,我们会发现上面的配置方法全部失效。此时需要在spring security的WebSecurityConfigurerAdapter中的configure(HttpSecurity http)配置方法,加上http.cors()配置,第二小节中的配置才会生效。

public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.cors().and()
        ...
    }
}

另外Spring Security为我们提供了一种新的CORS规则的配置方法:CorsConfigurationSource 。使用这种方法实现的效果等同于注入一个CorsFilter过滤器。

@Bean
CorsConfigurationSource corsConfigurationSource() {
    CorsConfiguration configuration = new CorsConfiguration();
    configuration.setAllowedOrigins(Arrays.asList("http://localhost:8888"));
    configuration.setAllowedMethods(Arrays.asList("GET","POST"));
    configuration.applyPermitDefaultValues();
    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    source.registerCorsConfiguration("/**", configuration);
    return source;
}

四、深入了解

Spring 里那么多种 CORS 的配置方式,到底有什么区别

五、前端测试代码

请将如下代码,放入与jwt-server不同域的应用下面,进行测试。

  • jwt-server的端口这里是8889
  • 使用jwt令牌进行访问
window.onload = function () {
    var headers = {};
    headers['JWTHeaderName'] = "<这里替换为jwt令牌>";
    $.ajax({
        url: 'http://localhost:8889/hello',
        type: "POST",
        headers: headers,
        success: function (data) {
            alert("跨域请求配置成功")
        },
        error: function (data) {
            alert("跨域请求配置失败")
        }
    });
}

2.CSRF跨站攻击防护

一、什么是CSRF

他只防御POST/DELETE/PUT的请求方式,针对GET请求不防御

很多朋友在学习Spring Security的时候,会将CORS(跨站资源共享)和CSRF(跨站请求伪造)弄混,以为二者是一回事。其实不是,先解释一下:

  • CORS(跨站资源共享)是局部打破同源策略的限制,使在一定规则下HTTP请求可以突破浏览器限制,实现跨站访问。
  • CSRF是一种网络攻击方式,也可以说是一种安全漏洞,这种安全漏洞在web开发中广泛存在。我们要需要堵上这个漏洞。

当我们使用Spring Security的时候,这种CSRF漏洞默认的被防御掉了。但是你会发现在跨域请求的情况下,我们的POST、DELETE、PUT等HTTP请求方式失效了。所以在笔者之前的文章中,我们使用http.csrf.disable()暂时关闭掉了CSRF的防御功能,但是这样是不安全的,那么怎么样才是正确的做法呢?就是本文需要向大家介绍的内容。


二、CSRF的攻击方式

通常的CSRF攻击方式如下:

  • 你登录了网站A,攻击者向你的网站A账户发送留言、或者伪造嵌入页面,带有危险操作链接。
  • 当你在登录状态下点击了攻击者的连接,因此该链接对你网站A的账户进行了操作。
  • 这个操作是你在网站A中主动发出的,并且也是针对网站A的HTTP链接请求,同源策略无法限制该请求。

如果你不小心点击的连接,是针对网站的数据操作,如:转出货币,你的钱就被转走了。因为点击"链接"的请求是HTTP的GET请求,所以正规的开发人员的做法是不要使用GET方法进行数据操作,只使用GET方法进行数据查询。

三、如何防御CSRF攻击

  • 为系统中的每一个连接请求加上一个token,这个token是随机的,服务端对该token进行验证。破坏者在留言或者伪造嵌入页面的时候,无法预先判断CSRF token的值是什么,所以当服务端校验CSRF token的时候也就无法通过。所以这种方法在一定程度上是靠谱的。
  • 但是如果你的电脑中毒,网络信息被劫持使用token的方法仍然不安全。所以没有绝对的安全,道高一次魔高一丈。作为开发者,我们就做到我们应该做到的。
  • 跳转提示:当用户不小心点击了第三方连接,合格的应用应该提示用户相关的风险!由用户自己确认是否真的要跳转或者执行第三方连接,或者就干脆不让非可信连接在留言区等地方存在。

四、Spring Security的CSRF token攻击防护

首先,我们要先开启防护功能,在用户登陆操作之后,生成的CSRF Token就保存在cookies中。

public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //开启csrf防御
        http.csrf()
            //使用cookie去存csrf令牌,允许浏览器当前cookie可以被js脚本读取
            .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
			//忽略登入认证的请求,/authentication 登录认证的请求
            .ignoringAntMatchers("/authentication");
        .and()
        ...
    }
}
  • 使用CookieCsrfTokenRepository生成CSRF Token放入cookie,并设置cookie的HttpOnly=false,允许读取该cookie。这样非浏览器等无法自动维护cookie的客户端可以读取cookie中的CSRF Token,以供后续资源请求中使用。
  • 使用ignoringAntMatchers开放一些不需要进行CSRF防护的访问路径,比如:登录授权。

至此,我们生成了CSRF token保存在了cookies中,浏览器向服务端发送的HTTP请求,都要将CSRF token带上,服务端校验通过才能正确的响应。这个校验的过程并不需要我们自己写代码实现,Spring Security会自动处理。但是我们需要关注前端代码,如何正确的携带CSRF token。


五、前端请求携带CSRF Token的方式

  • 默认情况下,CookieCsrfTokenRepository会向cookies中写入一个key为XSRF-TOKEN的cookie。
  • CookieCsrfTokenRepository在跨站防御验证的过程中,可以从HTTP Header中读取 X-XSRF-TOKEN或者从HTTP参数中读取_csrf,作为跨站防御验证的令牌.

注意:这里是XSRF-TOKENX-XSRF-TOKEN,没有写错。而不是CSRF-TOKENX-CSRF-TOKEN

在thymeleaf模板中可以使用如下方式,在发送HTTP请求的时候携带CSRF Token。如果是前后端分离的应用,或者其他模板引擎,酌情从cookies中获取CSRF Toekn。

5.1.在Header中携带CSRF token

var headers = {};
headers['X-XSRF-TOKEN'] = "${_csrf.token}";
$.ajax({    
    headers: headers,    
});

5.2.直接作为参数提交。

$.ajax({    
    data: {      
       "_csrf": "${_csrf.token}"        
    }
});

5.3.form表单的隐藏字段

<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}">

  • 请求授权认证返回XSRF-TOKEN

Day244.Springsecurity解决跨域问题、CSRF跨域攻击防护、JWT集群应用方案   -springsecurity-jwt-oauth2

  • 再次请求的时候携带时,X-XSRF-TOKEN等于上面的值

Day244.Springsecurity解决跨域问题、CSRF跨域攻击防护、JWT集群应用方案   -springsecurity-jwt-oauth2


3.JWT集群应用方案

一、回顾JWT授权与验证流程

在我们之前实现的JWT应用中,登录认证的Controller和令牌验证的Filter是在同一个应用中的。

Day244.Springsecurity解决跨域问题、CSRF跨域攻击防护、JWT集群应用方案   -springsecurity-jwt-oauth2

要想使用JWT访问资源需要

  • 先使用用户名和密码,去Controller换取JWT令牌
  • 然后才能进行资源的访问,资源接口的前端由一个"JWT验证Filter"负责校验令牌和授权访问。

二、集群应用

那我们可以思考一个问题,如果上面的应用部署两份形成集群应用,也就是“应用A”和“应用B”,代码是同一套代码。如果认证过程是在“应用A”获取的JWT令牌,可以访问“应用B”的接口资源么?(如下图)

Day244.Springsecurity解决跨域问题、CSRF跨域攻击防护、JWT集群应用方案   -springsecurity-jwt-oauth2

答案是:可以。因为两个应用中没有在内存(session)中保存中保存任何的状态信息,所有的信息都是去数据库里面现加载的。所以只要这两个应用,使用同一个数据库、同一套授权数据、同一个用于签名和解签的secret。就可以实现“应用A”的认证、在“应用B”中被承认。
那么另外一个问题来了,对于上面的集群应用,“应用A”和“应用B”实际上是一份代码部署两份。如果“应用A”和“应用B”是真正的两套代码的部署结果呢?答案仍然是可以。前提是你的认证Controller代码和鉴权Filter代码的实现逻辑是一样的、校验规则是一样的。使用同一个数据库、同一套授权数据、同一个用于签名和解签的secret。所以JWT服务端应用可以很容易的扩展。

  • 条件要求

Day244.Springsecurity解决跨域问题、CSRF跨域攻击防护、JWT集群应用方案   -springsecurity-jwt-oauth2

满足以上就可以在A服务器上鉴权授权返回jwt令牌,然后去携带jwt令牌,去B服务器请求访问,通过授权

三、独立的授权服务

基于JWT的这种无状态的灵活性,它很容易实现应用横向扩展。只要具备以下条件任何JWT的应用都可以整合为一个应用集群。

  • 认证Controller代码统一
  • 鉴权Filter代码统一、校验规则是一样的。
  • 使用同一套授权数据
  • 同一个用于签名和解签的secret。

基于这个条件前提,我们完全可以把认证Controller代码单独抽取出来,形成“认证服务器”。如下图所示:

Day244.Springsecurity解决跨域问题、CSRF跨域攻击防护、JWT集群应用方案   -springsecurity-jwt-oauth2

或者我们还可以进一步把所有的Jwt验证鉴权Filter代码单独抽取出来,形成“服务网关”,放在接口资源的前端。当然“服务网关”的功能不只是鉴权、还要有请求转发的功能。

最后剩下的一系列的“接口资源”,实际上就是我们常说的“资源服务器”。


上一篇:解决Windows 环境中 Python:pip安装出现Fatal error in launcher: Unable to create process using '"


下一篇:Python/Django 中 CSRF 问题