spring security 中的csrf 漏洞保护

一、简介

    CSRF(Cross-Site Request Forgery 跨站请求伪造),也可称为一键式攻击(one-click-attack)通常缩写为 CSRF 或者 XSRF。

CSRF 攻击是一种挟持用户在当前已登录的浏览器上发送恶意请求的攻击方法。相对于XSS利用用户对指定网站的信任,CSRF则是利用网站对用户网页浏览器的信任。简单来说,CSRF是致击者通过一些技术手段欺骗用户的浏览器,去访问一个用户曾经认证过的网站并执行恶意请求,例如发送邮件、发消息、甚至财产操作(如转账和购买商品) 。由于客户端浏览器)已经在该网站上认证过,所以该网站会认为是真正用户在操作而执行请求(实际上这个并非用户的本意)。

 举个例子:

       假设张三现在登录了某银行的网站准备完成一项转账操作,转账的链接如下:

https: //bank .xxx .com/withdraw?account=张三&amount=1000&for=李四 可以看到,这个链接是想从张三这个账户下转账 1000 元到李四 账户下,假设张三 没有注销登录该银行的网站,就在同一个浏览器新的选项卡中打开了一个危险网站,这个危险网站中有一幅图片很很有诱惑力,你立马点开了,代码如下:

 网<img src="https ://bank.xxx.com/withdraw?account=张三&amount=10008for=黑客">

一旦用户打开了这个网站,这个图片链接中的请求就会自动发送出去。由于是同一个浏览器并且用户尚未注销登录,所以该请求会自动携带上对应的有效的 Cookie 信息,进而完成一次转账操作。这就是跨站请求伪造。

二、csrf 防御

     CSRF攻击的根源在于浏览器默认的身份验证机制(自动携带当前网站的Cookie信息),这种机制虽然可以保证请求时来自用户的某个浏览器,但是无法确保这请求是用户授权发送。攻击者和用户发送的请求一模一样,这意味着我们没有办法去直接拒绝这里的某一个请求。如果能在合法清求中额外携带一个攻击者无法获取的参数,就可以成功区分出两种不同的请求进而直接拒绝掉恶意请求。在 SpringSecurity 中就提供了这种机制来防御 CSRF 攻击,这种机制我们称之为 令牌同步模式。

2.1 令牌同步模式

    这是目前主流的CSRF 攻击防御方案。具体的操作方式就是在每一个 HTTP 请求中,除了默认自动携带的 Cookie 参数之外,再提供一个安全的、随机生成的宇符串,我们称之为 CSRF令牌。这个 CSRF 令牌由服务端生成,生成后在 HtpSession 中保存一份。当前端请求到达后,将请求携带的 CSRF 令牌信息和服务端中保存的令牌进行对比,如果两者不相等,则拒绝掉该 HITTP 请求。

注意:考虑到会有一些外部站点链接到我们的网站,所以我们要求请求是幂等的,这样对子HEAD、OPTIONS、TRACE 等方法就没有必要使用 CSRF 令牌了,强行使用可能会导致令牌泄露!

2.2 开启csrf 防御

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .anyRequest().authenticated()
                .and()
                .formLogin() // 开启表单认证
                .and()
                .csrf();// 开启csrf 攻击
    }
}

2.2.1 当我们打开csrf 防御后效果


   初看登录页面倒是没有什么不同,我们需要右键查看源代码你就会发现不同点了;表单中多了<input name="_csrf" type="hidden" value="b634774a-6295-4d1c-9d32-9c639a25cb76" />


2.2.2 自定义表单

    我们看到,在我们自定义的表单中,没有加_csrf标签,当我们访问 index页面时候,我们查看源码,默认给我们加上了 Input 这个隐藏标签

<h2> 欢迎进入首页 </h2>
<form role="form" class="form-horizontal" th:action="@{/hello}" method="post">
    <div class="form-group">
        <label class="control-label col-sm-2">输入框</label>
        <div class="col-sm-8">
            <input type="text" class="form-control" name="loginId" placeholder="测试">
        </div>
    </div>

    <div class="form-group">
        <div class="col-sm-offset-2 col-sm-10">
            <button type="submit" class="btn btn-default">提交</button>
        </div>
    </div>
</form>

三、传统web开发使用csrf

   开启CSRF防御后会自动在提交的表单中加入如下代码,如果不能自动加入,需要在开启之后手动加入如下代码,并随着请求提交。获取服务端令牌方式如下:

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

3.1 后端配置

@Controller
public class HelloController {



    @RequestMapping("hello")
    @ResponseBody
    public String hello() {
        System.out.println("hello===========>");
        return "hello ";
    }



    @RequestMapping("index.html")
    public String index() {
        return "index ";
    }
}

3.2 前端页面

<form role="form" class="form-horizontal" th:action="@{/hello}" method="post">
    <div class="form-group">
        <label class="control-label col-sm-2">输入框</label>
        <div class="col-sm-8">
            <input type="text" class="form-control" name="loginId" placeholder="测试">
        </div>
        <input type="hidden" th:name="${_csrf.parameterName}"  th:value="${_csrf.token}" />
    </div>

    <div class="form-group">
        <div class="col-sm-offset-2 col-sm-10">
            <button type="submit" class="btn btn-default">提交</button>
        </div>
    </div>
</form>

3.3 查看效果

     我们看到,当我们手动写了一个hidden后,页面又给我们自动添加了一个,默认会给我们自动生成一个,如果不能自动生成,那么就需要手动写入_csrf 了;

3.4 源码查看

  org.springframework.security.config.annotation.web.configurers.CsrfConfigurer#configure 入口启动类

org.springframework.security.web.csrf.CsrfFilter#doFilterInternal  实际真正的过滤规则

org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository#loadToken  至于为什么是这么,我们看默认的配置

从当前session中获取一个属性为 HttpSessionCsrfTokenRepository.CSRF_TOKEN

当在session中获取不到的时候,会重新生成一个token并且放到session以及request请求中

   进行 Token对比,先从header中获取X-CSRF-TOKEN,如果请求头没有的话,就从请求参数中获取一个叫_csrf 参数,然后做对比,如果对比失败,则抛出异常


四、前后端分离使用csrf

   前后端分离开发时,只需要将生成 csrf 放入到cookie 中,并在请求时获取 cookie 中令牌信息进行提交即可。

4.1 修改 CSRF 存入 Cookie

  .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());//将令牌保存到 cookie 允许前端在cookie中获取

@Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .anyRequest().authenticated()// 所有请求都需要认证
                .and()
                .formLogin()// 表单配置
                .and().exceptionHandling()
                .authenticationEntryPoint(((request, response, authException) -> {
                    // 判断是否有登录
                    Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
                    if (authentication == null) {
                        WebRespUtils.writeJson(response,"请认证后再来请求接口");
                    } else {
                        WebRespUtils.writeJson(response,authException.getLocalizedMessage());
                    }
                }))
                .and()
                .csrf()
                .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());


        //替换掉UsernamePasswordAuthenticationFilter
        http.addFilterAt(loginVerifyImgFilter(), UsernamePasswordAuthenticationFilter.class);

4.2 登录后查看cookie效果

  第一请求的时候,失败了,因为没有携带cookie参数;

这个时候我们将该参数放到请求头header中再试试,注意请求header的值为:X-XSRF-TOKEN

4.2.1 这一部分对应源码

4.3  这个时候我不带token访问

我们直接访问 hello接口 ,我们看到直接报403 了,因为没有携带这个 csrf token参数

带上请求参数,不用header尝试,可以看到也可以成功

4.4 登录过滤器代码

public class LoginFilter extends UsernamePasswordAuthenticationFilter {


    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        if (!request.getMethod().equals("POST")) {
            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
        }

        if (request.getContentType().equalsIgnoreCase(MediaType.APPLICATION_JSON_VALUE) || request.getContentType().equalsIgnoreCase(MediaType.APPLICATION_JSON_UTF8_VALUE)) {
            Map<String,String> userMap = null;
            try {
                // 用户信息
                userMap = new ObjectMapper().readValue(request.getInputStream(), Map.class);
            } catch (Exception e) {
                e.printStackTrace();
            }
            // 获取图形验证码
            String username = userMap.get(getUsernameParameter());
            String password = userMap.get(getPasswordParameter());

            UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
            // Allow subclasses to set the "details" property
            setDetails(request, authRequest);
            return this.getAuthenticationManager().authenticate(authRequest);
        }
        return super.attemptAuthentication(request,response);
    }

}

  • 14
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Spring Security提供了防止跨站点请求伪造(CSRF)攻击的机制。在Spring Security使用CSRF可以通过以下步骤实现: 1. 在HTML表单添加CSRF令牌。 2. 配置Spring Security以启用CSRF保护。 3. 配置CSRF令牌在表单提交时如何发送到服务器。 下面是一个基本的示例: 1. 在HTML表单添加CSRF令牌 在表单添加CSRF令牌可以防止攻击者向服务器发送伪造的请求。可以使用Spring Security的标签库来添加CSRF令牌。以下是一个示例: ```html <form method="post" action="/process-form"> <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" /> <!-- 其他表单元素 --> <button type="submit">Submit</button> </form> ``` 在这个例子,我们使用了隐藏的输入字段来存储CSRF令牌,并使用Spring Security的EL表达式来生成令牌名称和值。 2. 配置Spring Security以启用CSRF保护 要启用Spring SecurityCSRF保护,需要配置一个CsrfTokenRepository。以下是一个示例: ```java @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .csrf() .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()); } } ``` 在这个例子,我们使用了CookieCsrfTokenRepository作为CsrfTokenRepository。这个仓库将生成一个CSRF令牌,并将其存储在一个cookie,这个cookie会在每个请求发送回服务器。 3. 配置CSRF令牌在表单提交时如何发送到服务器 当表单被提交时,需要将CSRF令牌发送回服务器。可以使用以下代码片段来从页面的隐藏字段提取CSRF令牌,并在表单提交时将其发送回服务器: ```javascript $(function () { var csrfToken = $("meta[name='_csrf']").attr("content"); var csrfHeader = $("meta[name='_csrf_header']").attr("content"); $(document).ajaxSend(function(e, xhr, options) { xhr.setRequestHeader(csrfHeader, csrfToken); }); }); ``` 在这个例子,我们使用了jQuery来提取页面CSRF令牌,并将其作为请求头发送回服务器。 以上是一个基本的Spring Security使用CSRF的示例。如果需要更高级的配置,可以参考Spring Security的官方文档。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

星空寻流年

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值