我正在使用 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 日志后:
在您的邮递员中,我没有看到 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);
来自 spring 文档:spring security 6.2.3
Spring Security 在调试和跟踪级别提供所有安全相关事件的全面日志记录。这在调试应用程序时非常有用,因为出于安全措施,Spring Security 不会在响应正文中添加任何有关请求被拒绝原因的详细信息。如果您遇到 401 或 403 错误,您很可能会找到一条日志消息来帮助您了解发生了什么情况。
让我们考虑一个示例,其中用户尝试在没有 CSRF 令牌的情况下向启用了 CSRF 保护的资源发出 POST 请求。如果没有日志,用户将看到 403 错误,并且没有解释请求被拒绝的原因。 但是,如果您为 Spring Security 启用日志记录,您将看到如下日志消息:(logging.level.org.springframework.安全=跟踪 )
您在安全配置中定义的顺序请查看如下顺序
`@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);
}`