简体   繁体   English

Spring Boot 安全无法禁用 CSRF 保护

[英]Spring Boot security can not disable CSRF protection

My Spring Boot REST API is protected by a Keycloak instance.我的 Spring Boot REST API 受 Keycloak 实例保护。 Since the CSRF protection only allows GET and POST, I want to disable it.由于CSRF保护只允许GET和POST,我想禁用它。 However, my approach does not seem to work since the REST API will return HTTP status 403 for any request with an origin that differs from http://localhost:8080 .但是,我的方法似乎不起作用,因为 REST API 将针对源不同于http://localhost:8080 的任何请求返回 HTTP 状态 403。 Here is how I configure my security:以下是我配置安全性的方法:

package de.longnguyen.security;

import de.longnguyen.controller.KeycloakController;
import org.keycloak.adapters.springboot.KeycloakSpringBootConfigResolver;
import org.keycloak.adapters.springsecurity.KeycloakSecurityComponents;
import org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationProvider;
import org.keycloak.adapters.springsecurity.config.KeycloakWebSecurityConfigurerAdapter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.authority.mapping.SimpleAuthorityMapper;
import org.springframework.security.core.session.SessionRegistryImpl;
import org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy;
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
import org.springframework.security.web.session.SessionManagementFilter;


@Configuration
@EnableWebSecurity
@ComponentScan(basePackageClasses = KeycloakSecurityComponents.class)
public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
    private static final String[] ALLOWED = new String[]{"/", "/static/**", "/v2/api-docs", "/swagger*/**", "/webjars/**"};

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) {
        KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
        keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
        auth.authenticationProvider(keycloakAuthenticationProvider);
    }

    @Bean
    public KeycloakSpringBootConfigResolver KeycloakConfigResolver() {
        return new KeycloakSpringBootConfigResolver();
    }

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

    @Override
    public void configure(WebSecurity web) throws Exception {
        super.configure(web);
        web.ignoring()
                .antMatchers(ALLOWED);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        super.configure(http);
        http
                .cors()
                .and()
                .authorizeRequests()
                .antMatchers(ALLOWED).permitAll()
                .anyRequest()
                .authenticated()
                .and()
                .addFilterBefore(new CustomCorsFilter(), SessionManagementFilter.class)
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .csrf().disable();
    }
}

A request with Origin of http://localhost:8080 will works: Origin 为http://localhost:8080 的请求将起作用:

在此处输入图片说明

However the exact same request with an origin of http://localhost:3000 will not work:但是,源为http://localhost:3000的完全相同的请求将不起作用: 在此处输入图片说明

EDIT:编辑:

This is how My CustomCorsFilter looks like:这就是我的 CustomCorsFilter 的样子:

package de.longnguyen.security;

import lombok.extern.slf4j.Slf4j;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;

@Slf4j
public class CustomCorsFilter implements Filter {

    public void destroy() {
    }

    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        if (req instanceof HttpServletRequest && res instanceof HttpServletResponse) {
            HttpServletRequest request = (HttpServletRequest) req;
            HttpServletResponse response = (HttpServletResponse) res;
            response.setHeader("Access-Control-Allow-Origin", request.getHeader("Origin"));
            response.setHeader("Vary", "Origin");
            response.setHeader("Access-Control-Max-Age", "3600");
            response.setHeader("Access-Control-Allow-Credentials", "true");
            response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE, PUT");
            response.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, X-CSRF-TOKEN");
        }
        chain.doFilter(req, res);
    }

    public void init(FilterConfig filterConfig) {
    }
}

Edit edit:编辑编辑:

This is how my logs look like:这是我的日志的样子:

2020-03-26 23:08:45.758 DEBUG 27582 --- [nio-8080-exec-2] o.s.s.w.u.matcher.AntPathRequestMatcher  : Checking match of request : '/api/v1/keycloak/delete'; against '/'
2020-03-26 23:08:45.758 DEBUG 27582 --- [nio-8080-exec-2] o.s.s.w.u.matcher.AntPathRequestMatcher  : Checking match of request : '/api/v1/keycloak/delete'; against '/static/**'
2020-03-26 23:08:45.758 DEBUG 27582 --- [nio-8080-exec-2] o.s.s.w.u.matcher.AntPathRequestMatcher  : Checking match of request : '/api/v1/keycloak/delete'; against '/v2/api-docs'
2020-03-26 23:08:45.758 DEBUG 27582 --- [nio-8080-exec-2] o.s.s.w.u.matcher.AntPathRequestMatcher  : Checking match of request : '/api/v1/keycloak/delete'; against '/swagger*/**'
2020-03-26 23:08:45.758 DEBUG 27582 --- [nio-8080-exec-2] o.s.s.w.u.matcher.AntPathRequestMatcher  : Checking match of request : '/api/v1/keycloak/delete'; against '/webjars/**'
2020-03-26 23:08:45.758 DEBUG 27582 --- [nio-8080-exec-2] o.s.security.web.FilterChainProxy        : /api/v1/keycloak/delete at position 1 of 17 in additional filter chain; firing Filter: 'WebAsyncManagerIntegrationFilter'
2020-03-26 23:08:45.758 DEBUG 27582 --- [nio-8080-exec-2] o.s.security.web.FilterChainProxy        : /api/v1/keycloak/delete at position 2 of 17 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter'
2020-03-26 23:08:45.758 DEBUG 27582 --- [nio-8080-exec-2] o.s.security.web.FilterChainProxy        : /api/v1/keycloak/delete at position 3 of 17 in additional filter chain; firing Filter: 'HeaderWriterFilter'
2020-03-26 23:08:45.758 DEBUG 27582 --- [nio-8080-exec-2] o.s.security.web.FilterChainProxy        : /api/v1/keycloak/delete at position 4 of 17 in additional filter chain; firing Filter: 'CorsFilter'
2020-03-26 23:08:45.759 DEBUG 27582 --- [nio-8080-exec-2] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to public org.springframework.http.ResponseEntity<java.lang.String> de.longnguyen.controller.KeycloakController.delete()
2020-03-26 23:08:45.760 DEBUG 27582 --- [nio-8080-exec-2] o.s.web.cors.DefaultCorsProcessor        : Reject: HTTP 'DELETE' is not allowed
2020-03-26 23:08:45.761 DEBUG 27582 --- [nio-8080-exec-2] o.s.s.w.header.writers.HstsHeaderWriter  : Not injecting HSTS header since it did not match the requestMatcher org.springframework.security.web.header.writers.HstsHeaderWriter$SecureRequestMatcher@3fffd0ea
2020-03-26 23:08:45.761 DEBUG 27582 --- [nio-8080-exec-2] s.s.w.c.SecurityContextPersistenceFilter : SecurityContextHolder now cleared, as request processing completed

You can try changing your method to the following.您可以尝试将您的方法更改为以下内容。

@Override
    public void configure(HttpSecurity http) throws Exception {
        http
            .csrf()
            .disable()
            .headers()
            .frameOptions()
            .disable()
        .and()
...
}

and instead of adding .addFilterBefore(new CustomCorsFilter(), you can create a Bean in your config like而不是添加.addFilterBefore(new CustomCorsFilter(),你可以在你的配置中创建一个 Bean,比如

@Bean
CorsConfigurationSource corsConfigurationSource() {
    CorsConfiguration configuration = new CorsConfiguration();
    configuration.setAllowedOrigins(Collections.singletonList("*"));
    configuration.setAllowedMethods(Arrays.asList("GET", "POST", "OPTIONS", "DELETE", "PUT", "PATCH"));
    configuration.setAllowedHeaders(Arrays.asList("X-Requested-With", "Origin", "Content-Type", "Accept", "Authorization"));
    configuration.setAllowCredentials(true);
    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    source.registerCorsConfiguration("/**", configuration);
    return source;
}

And possibly the same for the SessionManagementFilter. SessionManagementFilter 可能也是如此。

@LongNguyen that is incorrect. @LongNguyen 那是不正确的。 Whenever you send a cross domain request from the browser, the browser sends a preflight request to the server to read Access-Conrol-Allow-Origin header.每当您从浏览器发送跨域请求时,浏览器都会向服务器发送预检请求以读取 Access-Conrol-Allow-Origin 标头。 If no response headers are set from the server, the browser do not send your actual request.如果服务器未设置响应标头,则浏览器不会发送您的实际请求。 The trick people use is they leave the server alone and implement a CORS middleware on the client side.人们使用的技巧是他们不理会服务器并在客户端实现 CORS 中间件。 Like a HTTPInterceptor in an angular application which adds a Access-Control-Allow-Origin= * response header and tricks the browser in believing the header came from the server.就像 Angular 应用程序中的 HTTPInterceptor,它添加了一个 Access-Control-Allow-Origin= * 响应标头并欺骗浏览器相信该标头来自服务器。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM