spring.security 禁止 curl post 请求
spring.security forbid curl post request
我用 SSM 框架开发了一个演示网站,并使用 spring.security 进行身份验证。我可以使用 post 请求登录网站并使用 get 请求获取数据。但是,我无法使用 post 请求添加数据。它总是被禁止的。如果禁用 CSRF,则可以。我尝试了以下方法,none 有效。
- 将“X-CSRF-TOKEN:CSRF 值”添加到 header。
- 将“_csrf:CSRF 值”添加到 header。
- 将 _csrf 添加到 post 请求 body。
那么如何使 post 请求在启用 CSRF 的情况下工作?另外,我不确定是否需要使用不同的 post 重新生成 CSRF 令牌。奇怪的是,登录 post 请求有效。
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();
}
}
curl 脚本
#!/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="\(.*\)".*$//' \
)
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, no-store, max-age=0, 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, no-store, max-age=0, 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
是否需要用不同的posts重新生成CSRF token
不,没有必要。
奇怪的是,登录 post 请求有效
令牌在登录后由spring.security重新生成。
那么如何使 post 请求在启用 CSRF 的情况下工作?
关键是如何获取登录后生成的新的CSRF token。一种可能的解决方案是将 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 }' 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 }' 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
我用 SSM 框架开发了一个演示网站,并使用 spring.security 进行身份验证。我可以使用 post 请求登录网站并使用 get 请求获取数据。但是,我无法使用 post 请求添加数据。它总是被禁止的。如果禁用 CSRF,则可以。我尝试了以下方法,none 有效。
- 将“X-CSRF-TOKEN:CSRF 值”添加到 header。
- 将“_csrf:CSRF 值”添加到 header。
- 将 _csrf 添加到 post 请求 body。
那么如何使 post 请求在启用 CSRF 的情况下工作?另外,我不确定是否需要使用不同的 post 重新生成 CSRF 令牌。奇怪的是,登录 post 请求有效。
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(); } }
curl 脚本
#!/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="\(.*\)".*$//' \ ) 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, no-store, max-age=0, 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, no-store, max-age=0, 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
是否需要用不同的posts重新生成CSRF token
不,没有必要。
奇怪的是,登录 post 请求有效
令牌在登录后由spring.security重新生成。
那么如何使 post 请求在启用 CSRF 的情况下工作?
关键是如何获取登录后生成的新的CSRF token。一种可能的解决方案是将 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 }' 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 }' 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