Spring Security 6 POST 请求未经permitAll()授权

问题描述 投票:0回答:3

我正在使用 Spring Boot 3、Spring Security 6。我的安全配置无法正常工作。我有 2 条路径,任何请求都应该被允许,而对于其他所有请求,都需要进行身份验证。

GET
POST
方法都适用于那些需要身份验证的人。

对于具有

permitAll()
的人,只有
GET
要求工作。对于
POST
,我收到 401 未经授权。

我负责 CSRF,无论如何,我希望所有

POST
请求都能工作,而不仅仅是那些具有身份验证的请求。

在 Postman 上,我选择了

POST
,无身份验证,放置 RAW 正文并选择 JSON。我真的不知道为什么它不起作用。

这是我的代码:

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
        return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
    }

 
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http, KeycloakLogoutHandler keycloakLogoutHandler) throws Exception {

        CookieCsrfTokenRepository tokenRepository = CookieCsrfTokenRepository.withHttpOnlyFalse();
        XorCsrfTokenRequestAttributeHandler delegate = new XorCsrfTokenRequestAttributeHandler();
        // set the name of the attribute the CsrfToken will be populated on
        delegate.setCsrfRequestAttributeName("_csrf");
        // Use only the handle() method of XorCsrfTokenRequestAttributeHandler and the
        // default implementation of resolveCsrfTokenValue() from CsrfTokenRequestHandler
        CsrfTokenRequestHandler requestHandler = delegate::handle;
    
        http
                .authorizeHttpRequests().requestMatchers("/firstpath/**", "/secondpath/**", "/error/**").permitAll().and()
                .authorizeHttpRequests().anyRequest().authenticated().and()
                .oauth2ResourceServer(oauth2 -> oauth2.jwt());
        http.oauth2Login()
                .and()
                .logout()
                .addLogoutHandler(keycloakLogoutHandler)
                .logoutSuccessUrl("/");
        http.csrf((csrf) -> csrf
                .csrfTokenRepository(tokenRepository)
                .csrfTokenRequestHandler(requestHandler));
        return http.build();
    }
}
@Slf4j
@RestController
@RequestMapping("/firstpath")
public class NameitController {

    @PostMapping(value = "path", produces = WSConstants.JSON_MEDIATYPE)
    @ResponseBody
    public ResponseEntity saveMyObject(@RequestBody ObjectDTO dto) {
        [...] //my code
    }
}

我也尝试过

http.authorizeHttpRequests().requestMatchers(HttpMethod.POST, "/firstpath/path").permitAll()
,但没有用。

编辑:它仍然与 CSRF 保护有关,因为当我累了

http.csrf().disable();
时,一切都工作正常。但我还是想要CSRF保护,好像token不是用
permitAll()
发送的?...

Edit2:添加 Spring Security 日志后:

spring spring-boot spring-security
3个回答
5
投票

在您的邮递员中,我没有看到 X-XSRF-TOKEN 标头。如果您从 cookie 中获取 XSRF 令牌后没有将其发送回服务器,您可能会按照答案末尾所示的方式进行操作,因为它是 旨在防止 的方式之一 CSRF 攻击 并且只能这样工作。在像 Angular 这样的框架中,我们可以从 Spring Boot 服务器获取它作为 Cookie 并将其作为标头发送回来,以区分访问相同 URL 的恶意站点,因为此类站点在浏览器内部无法访问与我们的真实域关联的 Cookie将其作为标题发送回来。

这是一个简单的工作项目,它使用 spring security 6 和 crsf 令牌以及邮递员测试(如果有帮助的话)。它使用 InMemoryUserDetailsManager、NoOpPasswordEncoder(不建议用于生产)和基本身份验证。

安全配置:

import java.util.function.Supplier;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.CsrfConfigurer;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
import org.springframework.security.web.csrf.CsrfToken;
import org.springframework.security.web.csrf.CsrfTokenRequestHandler;
import org.springframework.security.web.csrf.XorCsrfTokenRequestAttributeHandler;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

@Configuration
public class ProjectSecurityConfig {
    @Bean
    SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
        XorCsrfTokenRequestAttributeHandler delegate = new XorCsrfTokenRequestAttributeHandler();
        delegate.setCsrfRequestAttributeName("_csrf");
        CsrfTokenRequestHandler requestHandler = new CsrfTokenRequestHandler() {
            @Override
            public void handle(HttpServletRequest request, HttpServletResponse response,
                    Supplier<CsrfToken> csrfToken) {
                delegate.handle(request, response, csrfToken);
            }
        };
        return http
                .cors().disable() // disabled cors for simplicity in this example in case of testing through a ui
                .authorizeHttpRequests()
                .requestMatchers("/error").permitAll()
                .anyRequest().authenticated()
                .and()
                .csrf()
                .csrfTokenRequestHandler(requestHandler)
                .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
                .and().formLogin()
                .and().httpBasic()
                .and().build();
    }

    @Bean
    InMemoryUserDetailsManager userDetailsService() {
        UserDetails admin = User.withUsername("admin").password("pass").authorities("admin").build();
        return new InMemoryUserDetailsManager(admin);
    }

    @Bean
    PasswordEncoder passwordEncoder() {
        return NoOpPasswordEncoder.getInstance();
    }
}

控制器:

import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import com.learning.entity.DataObject;

@RestController
public class TestController {
    @PostMapping("/post")
    public String post(@RequestBody DataObject dataObject) {
        return "succesfull post";
    }
}

数据对象模型:

public class DataObject {
    private String data;

    public String getData() {
        return data;
    }

    public void setData(String data) {
        this.data = data;
    }
}

应用程序属性:

logging.level.org.springframework.security.web.csrf=TRACE

pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.0.1</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.learning</groupId>
    <artifactId>spring-security-3-csrf-example</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>spring-security-3-csrf-example</name>
    <description>spring learning</description>
    <properties>
        <java.version>17</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

在邮递员中使用CSRF令牌进行测试:

首先添加基本身份验证凭据-

添加数据对象json body-

向服务器发送模拟请求以获取 XSRF Cookie

使用此 Cookie 值作为名称为“X-XSRF-TOKEN”的标头-

测试它-

注意:- 从版本 6 开始,Spring Security 默认情况下不会创建用于基本身份验证的会话,因此在此示例中不会返回会话的 Cookie。

更新:-

这里有一篇文章,介绍了通过邮递员发送 XSRF-TOKEN 的更复杂的方法,正如 @OctaviaAdler 在评论中指出的那样。 TLDR,以防链接断开:- 在 postman 中创建一个环境并在其中添加变量“xsrf-token”。在请求中,添加标头 X-XSRF-TOKEN,并将值设置为“{{xsrf-token}}”(双花括号中的环境变量名称,不带引号)。然后在“测试”选项卡中添加以下脚本 -

var xsrfCookie = postman.getResponseCookie("XSRF-TOKEN");
postman.setEnvironmentVariable("xsrf-token", xsrfCookie.value);

0
投票

来自 spring 文档:spring security 6.2.3

Spring Security 在调试和跟踪级别提供所有安全相关事件的全面日志记录。这在调试应用程序时非常有用,因为出于安全措施,Spring Security 不会在响应正文中添加任何有关请求被拒绝原因的详细信息。如果您遇到 401 或 403 错误,您很可能会找到一条日志消息来帮助您了解发生了什么情况。

让我们考虑一个示例,其中用户尝试在没有 CSRF 令牌的情况下向启用了 CSRF 保护的资源发出 POST 请求。如果没有日志,用户将看到 403 错误,并且没有解释请求被拒绝的原因。 但是,如果您为 Spring Security 启用日志记录,您将看到如下日志消息:(logging.level.org.springframework.安全=跟踪 )


-1
投票

您在安全配置中定义的顺序请查看如下顺序

  `@Override
   protected void configure(HttpSecurity http) throws Exception {
   http
        .authorizeRequests()
        .antMatchers(HttpMethod.POST, "/api/auth/**")
        .permitAll()
        .antMatchers("/",
                "/favicon.ico",
                "/**/*.png",
                "/**/*.gif",
                "/**/*.svg",
                "/**/*.jpg",
                "/**/*.html",
                "/**/*.css",
                "/**/*.js")
        .permitAll()                   
        .anyRequest()
        .authenticated()
        .and()
        .cors()
        .and()
        .exceptionHandling()
        .authenticationEntryPoint(this.jwtAuthenticationEntryPoint)
        .and()
        .sessionManagement()
        .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
        .and()
        .csrf()
        .disable();

// Add our custom JWT security filter
http.addFilterBefore(jwtAuthenticationFilter(), 
 UsernamePasswordAuthenticationFilter.class);

 }`
© www.soinside.com 2019 - 2024. All rights reserved.