spring security 处理CSRF

csrf概念

英文全称叫做: cross-site request forgery,翻译过来叫做跨站请求伪造。spring security默认情况下是开启了csrf保护的。所谓的CSRF一般会在用户做了某个动作之后附加了一些额外动作。

在这里插入图片描述

csrf工作机制

往往是利用用户在某个系统中已经登录过,其具有一些服务器操作资源的权限,攻击者利用伪造的链接或其它骗用户完成某个操作,而这个操作会偷偷和该用户可操作的系统交互。

一个简单的示例图

在这里插入图片描述

如何解决

根本问题在于,我们必须要确定这个行为是不是由本身客户端发出的,而并非其它站点间接请求的,所以解决方案是建立一个信任机制,服务端必须和客户端有一个数据项来确认合法性。

在这里插入图片描述

spring security的解决方案

spring security通过过滤器CsrfFilter来解决这个问题 。

1 对于请求先加载得到CsrfToken对象

CsrfToken csrfToken = this.tokenRepository.loadToken(request);

实现上常见的方式两个

  • 基于Cookie
  • 基于Session
    在这里插入图片描述
    虽然上图写有4个,但是第三个是一个委托模式,第四个是用于测试的。

2 看token的缺失性

boolean missingToken = (csrfToken == null);

如果csrfToken对象是null,缺失性为true。

if (missingToken) {
	csrfToken = this.tokenRepository.generateToken(request);
	this.tokenRepository.saveToken(csrfToken, request, response);
}

如果缺失,这个token对象重新复制,生成一个token。并且将其保存。

3 保存数据

request.setAttribute(CsrfToken.class.getName(), csrfToken);
request.setAttribute(csrfToken.getParameterName(), csrfToken);

然后再请求中存储了两个数据。

4 匹配

if (!this.requireCsrfProtectionMatcher.matches(request)) {
if (this.logger.isTraceEnabled()) {
this.logger.trace("Did not protect against CSRF since request did not match "
		+ this.requireCsrfProtectionMatcher);
}
filterChain.doFilter(request, response);
return;
}

如果请求能够匹配到才能走里面的过滤器。

5 拿真正的token

String actualToken = request.getHeader(csrfToken.getHeaderName());
if (actualToken == null) {
actualToken = request.getParameter(csrfToken.getParameterName());
}
if (!csrfToken.getToken().equals(actualToken)) {
this.logger.debug(
	LogMessage.of(() -> "Invalid CSRF token found for " + UrlUtils.buildFullRequestUrl(request)));
AccessDeniedException exception = (!missingToken) ? new InvalidCsrfTokenException(csrfToken, actualToken)
	: new MissingCsrfTokenException(actualToken);
this.accessDeniedHandler.handle(request, response, exception);
return;
}
filterChain.doFilter(request, response);
  1. 从头部信息中获取实际token
  2. 如果没有,再从参数中拿token
  3. 如果实际的token和用户当前带过来的token不同,访问拒绝,给出异常
  4. 如果比较相等,继续下一个过滤器

CsrfTokenRepository接口

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. 生成token
  2. 存储token
  3. 加载token

写读(制造)

它的作用就是生成服务端的token,并保存,将来再取出来和客户端发过来的实际token比较。

在这里插入图片描述

实践

我们来定义一个简单的端点

package com.qiudaozhang.springsecurity.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {

    @GetMapping("hello")
    public String getHello () {
        return "get hello";
    }

    @PostMapping("hello")
    public String postHello () {
        return "post";
    }
}

基础配置

package com.qiudaozhang.springsecurity.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configuration
public class ProjectConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .anyRequest()
                .permitAll();
    }
}

现在get请求

curl -XGET http://localhost:8080/hello

在这里插入图片描述

post请求

curl -XPOST http://localhost:8080/hello

在这里插入图片描述
发现失败了。

404,被禁止,没有这个权限。

解决禁止问题

当前我们为了获取到这个token,我们想办法记录一下他,由于这个是CsrfFilter生成的,所以我们可以设计一个过滤器在它支持将它查出来。

在这里插入图片描述
通过这个类我们知道,它存储头部用的是X-XSRF-TOKEN,在参数中用的_csrf,所以我们的过滤器只需要在一次访问后去拿去即可。

自定义过滤器

获取token信息

package com.qiudaozhang.springsecurity.filter;

import org.springframework.security.web.csrf.CsrfToken;

import javax.servlet.*;
import java.io.IOException;

public class CsrfTokenLoggerFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        Object csrf = request.getAttribute("_csrf");
        CsrfToken csrfToken = (CsrfToken) csrf;
        System.out.println("token :" + csrfToken.getToken());
        chain.doFilter(request,response);
    }
}
其它步骤

【拿 SessionID】

首先我们发起一次get请求拿到 SessionID
在这里插入图片描述

【拿token】
token :9d6c99c7-6703-490e-9e5b-40edb32351dd

【重新post请求】

curl -X POST  http://localhost:8080/hello -H "Cookie:  JSESSIONID=B6E44E07AFE17C5F44D7EB871828F18F" -H "X-CSRF-TOKEN:9d6c99c7-6703-490e-9e5b-40edb32351dd"

在这里插入图片描述
这就成功了。

表单提交保护

too 未完待续

源码

https://github.com/qiudaozhang/spring-security-csrf01

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

singkingcho

有帮助?

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值