问题描述
我使用 SSM 框架开发了一个演示网站,并使用 spring.security 进行身份验证。我可以使用 post 请求登录该站点并使用 get 请求获取数据。但是,我无法使用 post 请求添加数据。它总是被禁止的。如果禁用CSRF,则可以。我尝试了以下方法,但都不起作用。
那么如何在启用 CSRF 的情况下使 post 请求工作?另外,我不确定是否需要使用不同的帖子重新生成 CSRF 令牌。奇怪的是,登录帖子请求有效。
-
application.properties
spring.datasource.url=jdbc:MysqL://localhost:3306/ssm?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai spring.datasource.username=root spring.datasource.password=123456 mybatis.mapper-locations=classpath:mybatis/*.xml spring.security.user.name=root spring.security.user.password=123456 debug=true
-
安全配置
@Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .anyRequest().authenticated() .and() .formLogin().permitAll(); } }
-
用户控制器
@RestController @RequestMapping("/rest/users") public class UserController { @Autowired private UserMapper userMapper; @PostMapping public void add(@RequestBody User user){ userMapper.add(user); } @GetMapping public List<User> getAll(){ return userMapper.findAll(); } }
-
卷曲脚本
#!/usr/bin/env bash host=192.168.44.109:8080 remote=http://${host} login=${remote}/login users=${remote}/rest/users/ csrf=$( \ curl --url ${login} -L -c cookie.txt 2>&1 \ |grep _csrf \ |sed 's/^.*value="\(.*\)".*$/\1/' \ ) echo "before login,csrf=${csrf}" curl --url ${login} -L -b cookie.txt -c cookie.txt -i \ -d "username=root&password=123456&_csrf=${csrf}" curl --url ${users} -L -b cookie.txt -v \ -H "Content-Type: application/json" \ -H "X-CSRF-Token: ${csrf}" \ -H "x-csrf-token: ${csrf}" \ -H "_csrf: ${csrf}" \ -d "{\"name\": \"name2\"}" rm -f cookie.txt
-
脚本输出
before login,csrf=6d7b2d7b-f9aa-4463-ad9b-468082df4d74 HTTP/1.1 302 Set-Cookie: JSESSIONID=D728694163DEEC78DDBC8869DC54C870; Path=/; HttpOnly X-Content-Type-Options: nosniff X-XSS-Protection: 1; mode=block Cache-Control: no-cache,no-store,max-age=0,must-revalidate Pragma: no-cache Expires: 0 x-frame-options: DENY Location: http://192.168.44.109:8080/ Content-Length: 0 Date: Sun,20 Jun 2021 11:05:52 GMT HTTP/1.1 200 vary: Origin vary: Access-Control-Request-Method vary: Access-Control-Request-Headers X-Content-Type-Options: nosniff X-XSS-Protection: 1; mode=block Cache-Control: no-cache,must-revalidate Pragma: no-cache Expires: 0 x-frame-options: DENY Content-Type: application/hal+json transfer-encoding: chunked Date: Sun,20 Jun 2021 11:05:52 GMT { "_links" : { "profile" : { "href" : "http://192.168.44.109:8080/profile" } } }* Trying 192.168.44.109... * TCP_NODELAY set * Connected to 192.168.44.109 (192.168.44.109) port 8080 (#0) > POST /rest/users/ HTTP/1.1 > Host: 192.168.44.109:8080 > User-Agent: curl/7.64.1 > Accept: */* > Cookie: JSESSIONID=D728694163DEEC78DDBC8869DC54C870 > Content-Type: application/json > X-CSRF-Token: 6d7b2d7b-f9aa-4463-ad9b-468082df4d74 > x-csrf-token: 6d7b2d7b-f9aa-4463-ad9b-468082df4d74 > _csrf: 6d7b2d7b-f9aa-4463-ad9b-468082df4d74 > Content-Length: 17 > * upload completely sent off: 17 out of 17 bytes < HTTP/1.1 403 < X-Content-Type-Options: nosniff < X-XSS-Protection: 1; mode=block < Cache-Control: no-cache,must-revalidate < Pragma: no-cache < Expires: 0 < x-frame-options: DENY < Content-Type: application/json < transfer-encoding: chunked < Date: Sun,20 Jun 2021 11:05:52 GMT < * Connection #0 to host 192.168.44.109 left intact {"timestamp":"2021-06-20T11:05:52.540+00:00","status":403,"error":"Forbidden","message":"","path":"/rest/users/"}* Closing connection 0
解决方法
CSRF token 是否需要重新生成不同的帖子
不,没有必要。
奇怪的是登录帖子请求有效
登录后由spring.security重新生成token。
那么如何在启用 CSRF 的情况下使发布请求有效?
关键是如何获取登录后生成的新CSRF令牌。一种可能的解决方案是将 CSRF 令牌保存在 cookie 中。并且 SecurityConfig 应更改为:
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.and()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin().permitAll();
}
}
然后,可以通过 awk '/XSRF-TOKEN/{print $7}' cookie
从 cookie 中获取 csrf 令牌,并使用 X-XSRF-TOKEN 请求头发送到服务器。完整脚本改为:
#!/usr/bin/env bash
host=192.168.44.109:8080
remote=http://${host}
login=${remote}/login
users=${remote}/rest/users/
csrf(){
awk '/XSRF-TOKEN/{print $7}' cookie
}
curl ${login} -c cookie 1> /dev/null 2>/dev/null
echo "before login,csrf=$(csrf)"
curl ${login} -d "username=root&password=123456&_csrf=$(csrf)" -b cookie -c cookie -L -i
echo "after login,csrf=$(csrf)"
name=$(date '+%Y%m%d-%H:%M:%S')
curl ${users} -H "X-XSRF-TOKEN: $(csrf)" -H 'Content-Type: application/json' -d "{\"name\": \"${name}\"}" -b cookie -L -v
rm -f cookie