You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Spring Security下Ajax POST请求失败(GET正常),_csrf处理后参数为空

解决Spring Security下jQuery Ajax POST数据为null的问题

兄弟,太懂你折腾半天的痛苦了——你之前把CSRF令牌塞URL里,虽然绕开了Spring Security的拦截,但数据全变成null,核心问题是CSRF令牌的传递方式不对,再加上可能的参数绑定小问题,下面一步步给你搞定:

一、别再把CSRF令牌放URL里了!正确的传递姿势

Spring Security推荐把CSRF令牌放在请求头或者请求体里,URL传令牌不仅有安全风险,还容易干扰参数解析:

方式1:请求头传递(最推荐)

先在页面里通过模板引擎(比如Thymeleaf/JSP)渲染出CSRF的令牌和对应的请求头名称:

<!-- Thymeleaf写法,JSP同理,用${_csrf.token}即可 -->
<meta name="_csrf" content="${_csrf.token}"/>
<meta name="_csrf_header" content="${_csrf.headerName}"/>

然后在jQuery Ajax里设置请求头:

// 从meta标签里拿令牌和头信息
var csrfToken = $("meta[name='_csrf']").attr("content");
var csrfHeader = $("meta[name='_csrf_header']").attr("content");

$.ajax({
    type: "POST",
    url: "/your-api-path", // 这里不用加csrf参数!
    beforeSend: function(xhr) {
        // 把CSRF令牌放到请求头里
        xhr.setRequestHeader(csrfHeader, csrfToken);
    },
    data: {
        username: "testUser",
        email: "test@example.com"
    },
    success: function(res) {
        console.log("请求成了!返回数据:", res);
    },
    error: function(xhr, status, err) {
        console.error("凉了:", err);
    }
});

方式2:请求体一起传

如果不想用请求头,也可以把令牌和业务数据打包在data里:

var csrfToken = $("meta[name='_csrf']").attr("content");

$.ajax({
    type: "POST",
    url: "/your-api-path",
    data: {
        _csrf: csrfToken, // 直接把令牌放数据里
        username: "testUser",
        email: "test@example.com"
    },
    success: function(res) {
        console.log("搞定!");
    }
});

二、后端参数绑定要对应,不然数据肯定null

你说数据全是null,大概率是后端接收参数的方式和前端传的不匹配,分两种情况:

情况1:前端传表单格式(默认)

前端默认的contentTypeapplication/x-www-form-urlencoded,后端用@RequestParam接收单个参数,要保证参数名完全一致:

@PostMapping("/your-api-path")
public ResponseEntity<String> handleRequest(
    @RequestParam("username") String username,
    @RequestParam("email") String email
) {
    // 这里就能拿到正常的参数了
    return ResponseEntity.ok("收到数据:" + username + " | " + email);
}

情况2:前端传JSON格式

如果要传JSON,必须手动设置contentType,并且把数据转成JSON字符串:

$.ajax({
    type: "POST",
    url: "/your-api-path",
    contentType: "application/json", // 必须加这个!
    beforeSend: function(xhr) {
        xhr.setRequestHeader(csrfHeader, csrfToken);
    },
    data: JSON.stringify({ // 转成JSON字符串
        username: "testUser",
        email: "test@example.com"
    }),
    success: function(res) {
        console.log("JSON请求成功!");
    }
});

后端要用@RequestBody配合实体类接收,记得实体类要有对应属性的getter和setter:

// 先定义实体类
public class UserRequest {
    private String username;
    private String email;

    // 必须加getter和setter!
    public String getUsername() { return username; }
    public void setUsername(String username) { this.username = username; }
    public String getEmail() { return email; }
    public void setEmail(String email) { this.email = email; }
}

// 控制器方法
@PostMapping("/your-api-path")
public ResponseEntity<UserRequest> handleRequest(
    @RequestBody UserRequest request
) {
    // 这里request里的属性就有值了
    return ResponseEntity.ok(request);
}

三、踩过的坑给你提个醒

  1. 静态HTML页面怎么拿CSRF令牌?如果不用模板引擎,得写个简单的后端接口返回令牌信息,比如:
@GetMapping("/csrf-token")
public ResponseEntity<Map<String, String>> getCsrfToken(HttpServletRequest request) {
    CsrfToken token = (CsrfToken) request.getAttribute(CsrfToken.class.getName());
    Map<String, String> result = new HashMap<>();
    result.put("token", token.getToken());
    result.put("header", token.getHeaderName());
    return ResponseEntity.ok(result);
}

然后前端先调这个接口拿令牌,再发POST请求。

  1. 别漏了contentType:传JSON的时候一定要加,不然后端解析不了,参数全是null。

内容的提问来源于stack exchange,提问作者rob

火山引擎 最新活动