Spring Boot与Django在处理CSRF(跨站请求伪造)防护时,走的是两条截然不同的技术路线。这背后反映的其实是Java生态与Python生态在Web安全设计哲学上的根本差异。Spring Boot默认不开启CSRF防护,需要开发者显式配置;Django则默认全局开启,中间件直接拦截所有非安全方法的请求。这两种选择没有绝对的好坏,但直接决定了开发者在项目初期就要面对的安全架构决策。
CSRF攻击的本质与防护前提CSRF攻击利用的是浏览器自动携带Cookie的机制。用户在登录银行系统后,浏览器保存了会话Cookie。攻击者诱导用户点击一个恶意链接,该链接向银行系统发起转账请求。浏览器会自动附上之前保存的Cookie,银行系统看到有效凭证,便执行了转账操作。整个攻击过程中,攻击者根本不需要知道用户的Cookie内容,只需要构造一个合法请求即可。
防护的核心逻辑很简单:确保请求是从你自己的前端页面发起的,而不是从第三方站点伪造的。实现这个目标的手段主要有三种:同步令牌模式(Synchronizer Token Pattern)、Cookie到Header令牌模式、以及双重提交Cookie模式。Spring Boot和Django都采用了令牌校验作为主要手段,但在具体实现细节上差异明显。
Spring Boot的CSRF防护机制:显式配置与灵活控制Spring Security从4.0版本开始默认开启CSRF防护,但很多Spring Boot项目在实际开发中会手动关闭它,尤其是在前后端分离架构下。这是因为Spring Security默认使用HttpSession来存储CSRF令牌,而前后端分离时后端通常是无状态的。Spring Security提供了CookieCsrfTokenRepository作为替代方案,将CSRF令牌存储在Cookie中。
Spring Security的CSRF防护核心是CsrfFilter,这个过滤器拦截所有请求,对POST、PUT、DELETE、PATCH等状态变更方法进行令牌校验。默认配置下,Spring Security使用HttpSessionCsrfTokenRepository存储令牌。每次请求时,服务端生成一个随机令牌存入Session,同时通过_csrf请求属性暴露给视图层。Thymeleaf模板引擎可以自动在表单中插入隐藏域:
<form method="post" action="/transfer">
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}"/>
<!-- 其他表单字段 -->
</form>
对于前后端分离的项目,需要切换到Cookie模式。配置方式是在SecurityConfig中显式声明:
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.and()
.authorizeRequests()
.anyRequest().authenticated();
}
}
这里有一个关键细节:withHttpOnlyFalse()设置。CSRF令牌需要被前端JavaScript读取并放入请求头,所以Cookie的HttpOnly属性必须设为false。这带来了一个安全权衡:Cookie可被JavaScript读取,意味着XSS攻击可以窃取CSRF令牌。Spring Security的官方文档明确指出,这种方案在XSS漏洞存在时防护效果会大打折扣。
Spring Security还支持从请求头读取CSRF令牌。默认情况下,CsrfFilter会检查名为X-CSRF-TOKEN的请求头。前端需要从Cookie中读取令牌,然后设置到每个Ajax请求的头中。这种模式要求前端框架配合,比如在Axios中配置全局拦截器:
axios.defaults.xsrfCookieName = 'XSRF-TOKEN'; axios.defaults.xsrfHeaderName = 'X-CSRF-TOKEN';
Spring Security的CSRF防护还有一个容易被忽略的特性:SameSite Cookie属性。从Spring Security 5.1开始,可以配置SameSite属性为Strict或Lax。Lax模式允许顶级导航的GET请求携带Cookie,但阻止跨站POST请求携带Cookie。这实际上从浏览器层面削弱了CSRF攻击的基础。配置方式是在CookieSerializer中设置:
@Bean
public CookieSerializer cookieSerializer() {
DefaultCookieSerializer serializer = new DefaultCookieSerializer();
serializer.setSameSite("Lax");
return serializer;
}
Django的CSRF防护机制:默认开启与深度集成
Django的CSRF防护是开箱即用的。只要在settings.py的MIDDLEWARE中保留django.middleware.csrf.CsrfViewMiddleware,整个项目的所有POST、PUT、DELETE请求都会自动接受CSRF校验。Django的设计哲学是安全默认值,开发者不需要做任何额外配置就能获得基础防护。
Django的令牌生成机制比Spring Security更复杂。它使用了一个基于HMAC的签名方案:CSRF令牌由两部分组成,一部分是随机密钥,另一部分是基于该密钥和用户会话信息计算出的签名。这种设计使得令牌与用户会话绑定,即使攻击者获取了令牌,如果不在对应的会话中使用也是无效的。
在模板层面,Django提供了{% csrf_token %}标签,自动生成隐藏域。对于Ajax请求,Django的官方推荐做法是从Cookie中读取csrftoken,然后设置到X-CSRFToken请求头中。Django的CSRF中间件会同时检查POST数据中的csrfmiddlewaretoken字段和X-CSRFToken请求头。
Django的CSRF中间件有一个精妙的校验豁免机制。通过@csrf_exempt装饰器可以豁免单个视图,通过@csrf_protect可以在全局豁免时单独保护某个视图。更灵活的是,Django支持在settings.py中通过CSRF_TRUSTED_ORIGINS配置可信源列表。当请求的Origin或Referer头匹配可信源时,CSRF校验会被跳过。这在跨域场景下特别有用:
CSRF_TRUSTED_ORIGINS = [
'https://frontend.example.com',
'https://api.example.com',
]
Django从4.0版本开始,对CSRF_COOKIE_SAMESITE的默认值做了调整。之前默认是'Lax',现在更明确地推荐根据部署场景设置。如果前后端同域部署,Lax模式配合CSRF令牌已经足够安全。如果跨域部署,需要设置CSRF_COOKIE_SAMESITE为'None',同时必须设置CSRF_COOKIE_SECURE为True,确保Cookie只在HTTPS下传输。
Django的CSRF防护还有一个独特的特性:CSRF_USE_SESSIONS配置项。当设置为True时,CSRF令牌存储在服务端Session中而不是Cookie中。这彻底杜绝了CSRF令牌被前端JavaScript读取的可能性,代价是每个需要CSRF令牌的页面都必须有活跃的Session,增加了服务端存储压力。
核心差异:设计哲学决定实现路径Spring Boot和Django在CSRF防护上最根本的差异在于默认配置策略。Spring Security在Spring Boot 2.x版本中默认开启CSRF防护,但大量教程和实际项目会在SecurityConfig中显式关闭它。Django则把CSRF中间件放在默认的MIDDLEWARE列表中,移除它需要开发者主动操作。这种差异反映了两个社区对安全配置的不同态度:Java生态倾向于给开发者更多控制权,Python生态倾向于提供安全的默认行为。
在令牌存储方式上,Spring Security默认使用HttpSession,Django默认使用Cookie。HttpSession存储的优势是令牌不暴露给客户端脚本,但要求服务端维护会话状态。Cookie存储的优势是支持无状态后端,但令牌可被JavaScript读取。Django的CSRF_USE_SESSIONS配置提供了切换能力,Spring Security的CookieCsrfTokenRepository也提供了类似选择。
在令牌校验逻辑上,Django的HMAC签名方案比Spring Security的简单随机令牌更复杂。Django将CSRF令牌与会话信息绑定,即使令牌泄露,如果不在对应的会话中使用也是无效的。Spring Security的随机令牌与会话的关联是通过Session存储间接实现的,令牌本身不包含会话信息。Django的方案在理论上提供了更强的安全保障,但代价是实现复杂度更高。
在跨域场景支持上,两者都支持通过请求头传递CSRF令牌。Django的CSRF_TRUSTED_ORIGINS配置更直观,直接声明可信的前端域名。Spring Security则需要通过CORS配置和CSRF配置的组合来实现类似效果。Django的做法更符合"约定优于配置"的理念,Spring Security的做法给了开发者更细粒度的控制。
在SameSite Cookie属性的利用上,两者都提供了配置入口。但Django的文档对SameSite与CSRF防护的关系有更详细的说明,明确建议在支持SameSite的浏览器上,Lax模式已经可以防御大部分CSRF攻击。Spring Security的文档相对简略,更多依赖开发者自行理解SameSite的含义。
实际项目中的选择建议如果你的项目是传统的服务端渲染应用,Django的CSRF防护几乎是零配置的。模板标签自动处理令牌注入,中间件自动校验,开发者几乎感觉不到CSRF防护的存在。Spring Boot配合Thymeleaf也能达到类似效果,但需要确保SecurityConfig中CSRF防护没有被关闭。
如果是前后端分离的SPA应用,两者都需要额外的配置工作。Django的配置更集中,主要在settings.py中完成。Spring Boot的配置分散在SecurityConfig和前端代码中,需要前后端开发者协调。从工程实践角度看,Django的方案对全栈开发者更友好,Spring Boot的方案在大型团队中更容易实现职责分离。
如果后端是完全无状态的API服务,Spring Boot的CookieCsrfTokenRepository方案更成熟,文档和社区支持更丰富。Django的CSRF_USE_SESSIONS设置为True后需要Session支持,与无状态架构有冲突。在这种情况下,很多Django项目会选择依赖SameSite Cookie属性,同时将CSRF_COOKIE_SAMESITE设置为Lax,作为CSRF防护的主要手段。
从安全强度角度看,两种框架的CSRF防护都经过了充分的实战检验。真正影响安全性的往往不是框架本身,而是开发者的配置和使用方式。关闭CSRF防护、错误配置CORS、忽略SameSite属性设置,这些人为失误才是导致CSRF漏洞的主要原因。
