spring.security 禁止 curl 发布请求

Posted

技术标签:

【中文标题】spring.security 禁止 curl 发布请求【英文标题】:spring.security forbid curl post request 【发布时间】:2021-09-04 08:59:24 【问题描述】:

我使用 SSM 框架开发了一个演示网站,并使用 spring.security 进行身份验证。我可以使用 post 请求登录网站并使用 get 请求获取数据。但是,我无法使用发布请求添加数据。它总是被禁止的。如果 CSRF 被禁用,就可以了。我尝试了以下方法,它们都不起作用。

    将“X-CSRF-TOKEN:CSRF 值”添加到标题中。 将“_csrf:CSRF 值”添加到标题中。 将 _csrf 添加到发布请求正文。

那么如何在启用 CSRF 的情况下使发布请求工作?另外,我不确定是否需要使用不同的帖子重新生成 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, 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

【问题讨论】:

【参考方案1】:

是否需要用不同的帖子重新生成 CSRF 令牌

不,没有必要。

奇怪的是,登录帖子请求有效

登录后由spring.security重新生成token。

那么如何在启用 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 $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

【讨论】:

以上是关于spring.security 禁止 curl 发布请求的主要内容,如果未能解决你的问题,请参考以下文章

AJAX / Spring MVC - 没有 Spring Security 的 403 禁止错误

Grails Spring Security REST得到403禁止

为啥应用程序看不到 Spring Security 中的角色(禁止)

spring security - 针对不同 CURL 模式的多重身份验证

Oauth 2 + Spring Security - 出现 403 禁止错误

即使禁用 csrf,Spring security 403 禁止错误也会不断发生