Cross-Origin Resource Sharing with Spring Security

后端 未结 8 1556
暖寄归人
暖寄归人 2020-12-01 04:39

I\'m trying to make CORS play nicely with Spring Security but it\'s not complying. I made the changes described in this article and changing this line in applicationCo

相关标签:
8条回答
  • 2020-12-01 04:55

    well This is my code working very well and perfect for me: I spent two days working on it and understanding spring security so I hope you accept it as the answer, lol

     public class CorsFilter extends OncePerRequestFilter  {
        static final String ORIGIN = "Origin";
    
        @Override
        protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
                throws ServletException, IOException {
            System.out.println(request.getHeader(ORIGIN));
            System.out.println(request.getMethod());
            if (request.getHeader(ORIGIN).equals("null")) {
                String origin = request.getHeader(ORIGIN);
                response.setHeader("Access-Control-Allow-Origin", "*");//* or origin as u prefer
                response.setHeader("Access-Control-Allow-Credentials", "true");
               response.setHeader("Access-Control-Allow-Headers",
                        request.getHeader("Access-Control-Request-Headers"));
            }
            if (request.getMethod().equals("OPTIONS")) {
                try {
                    response.getWriter().print("OK");
                    response.getWriter().flush();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }else{
            filterChain.doFilter(request, response);
            }
        }
    }
    

    well then you need to also set your filter to be invoked:

    <security:http use-expressions="true" .... >
         ...
         //your other configs
        <security:custom-filter ref="corsHandler" after="PRE_AUTH_FILTER"/> // this goes to your filter
    </security:http>
    

    Well and you need a bean for the custom filter you created:

    <bean id="corsHandler" class="mobilebackbone.mesoft.config.CorsFilter" />
    
    0 讨论(0)
  • 2020-12-01 05:01

    Mostly,the OPTIONS request dont carry cookie for the authentication of the spring security.
    To resovle that,can modify configuration of spring security to allow OPTIONS request without authentication.
    I research a lot and get two solutions:
    1.Using Java config with spring security configuration,

    @Override
    protected void configure(HttpSecurity http) throws Exception
    {
        http
        .csrf().disable()
        .authorizeRequests()
        .antMatchers(HttpMethod.OPTIONS,"/path/to/allow").permitAll()//allow CORS option calls
        .antMatchers("/resources/**").permitAll()
        .anyRequest().authenticated()
        .and()
        .formLogin()
        .and()
        .httpBasic();
    }
    

    2.Using XML(note. cant not write "POST,GET"):

    <http auto-config="true">
        <intercept-url pattern="/client/edit" access="isAuthenticated" method="GET" />
        <intercept-url pattern="/client/edit" access="hasRole('EDITOR')" method="POST" />
        <intercept-url pattern="/client/edit" access="hasRole('EDITOR')" method="GET" />
    </http>
    

    On the end,there is the source for the solution... :)

    0 讨论(0)
  • 2020-12-01 05:10

    For me, the problem was that the OPTIONS preflight check failed authentication, because the credentials weren't passed on that call.

    This works for me:

    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.autoconfigure.security.SecurityProperties;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.core.annotation.Order;
    import org.springframework.data.web.config.EnableSpringDataWebSupport;
    import org.springframework.http.HttpHeaders;
    import org.springframework.http.HttpMethod;
    import org.springframework.scheduling.annotation.EnableAsync;
    import org.springframework.scheduling.annotation.EnableScheduling;
    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.configuration.WebSecurityConfigurerAdapter;
    import org.springframework.security.core.AuthenticationException;
    import org.springframework.security.core.userdetails.UserDetailsService;
    import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
    import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint;
    
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    
    @Configuration
    @EnableAsync
    @EnableScheduling
    @EnableSpringDataWebSupport
    @Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
    class SecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Autowired
        private UserDetailsService userDetailsService;
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
    
            http.csrf().disable()
                    .httpBasic().and()
                    .authorizeRequests()
                    .anyRequest().authenticated()
                    .and().anonymous().disable()
                    .exceptionHandling().authenticationEntryPoint(new BasicAuthenticationEntryPoint() {
                @Override
                public void commence(final HttpServletRequest request, final HttpServletResponse response, final AuthenticationException authException) throws IOException, ServletException {
                    if(HttpMethod.OPTIONS.matches(request.getMethod())){
                        response.setStatus(HttpServletResponse.SC_OK);
                        response.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, request.getHeader(HttpHeaders.ORIGIN));
                        response.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, request.getHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS));
                        response.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, request.getHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD));
                        response.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
                    }else{
                        response.sendError(HttpServletResponse.SC_UNAUTHORIZED, authException.getMessage());
                    }
                }
            });
    
        }
    
        @Override
        public void configure(AuthenticationManagerBuilder auth) throws Exception {
            auth
                    .userDetailsService(userDetailsService)
                    .passwordEncoder(new BCryptPasswordEncoder());
        }
    }
    

    The relevant part being:

    .exceptionHandling().authenticationEntryPoint(new BasicAuthenticationEntryPoint() {
                @Override
                public void commence(final HttpServletRequest request, final HttpServletResponse response, final AuthenticationException authException) throws IOException, ServletException {
                    if(HttpMethod.OPTIONS.matches(request.getMethod())){
                        response.setStatus(HttpServletResponse.SC_OK);
                        response.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, request.getHeader(HttpHeaders.ORIGIN));
                        response.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, request.getHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS));
                        response.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, request.getHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD));
                        response.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
                    }else{
                        response.sendError(HttpServletResponse.SC_UNAUTHORIZED, authException.getMessage());
                    }
                }
            });
    

    That fixes the OPTIONS preflight issue. What happens here is when you receive a call and authentication fails, you check if it's an OPTIONS call and if it is, just let it pass and let it do everything it wants to do. This essentially disables all browser-side preflight checking, but normal crossdomain policy still applies.

    When you're using the latest version of Spring, you can use the code below to allow cross origin requests globally (for all your controllers):

    import org.springframework.web.servlet.config.annotation.CorsRegistry;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
    
    @Component
    public class WebMvcConfigurer extends WebMvcConfigurerAdapter {
        @Override
        public void addCorsMappings(CorsRegistry registry) {
            registry.addMapping("/**").allowedOrigins("http://localhost:3000");
        }
    }
    

    Note that this is rarely a good idea to just hard code it like this. In a few companies I've worked for, the allowed origins were configurable through an admin portal, so on development environments you would be able to add all the origins you need.

    0 讨论(0)
  • 2020-12-01 05:10

    In my case, response.getWriter().flush() was't working

    Changed the code as below and it started working

    public void doFilter(ServletRequest request, ServletResponse res, FilterChain chain)
            throws IOException, ServletException {
    
        LOGGER.info("Start API::CORSFilter");
        HttpServletRequest oRequest = (HttpServletRequest) request;
        HttpServletResponse response = (HttpServletResponse) res;
        response.setHeader("Access-Control-Allow-Origin", "*");
        response.setHeader("Access-Control-Allow-Methods", "POST,PUT, GET, OPTIONS, DELETE");
        response.setHeader("Access-Control-Max-Age", "3600");
        response.setHeader("Access-Control-Allow-Headers",
                " Origin, X-Requested-With, Content-Type, Accept,AUTH-TOKEN");
        if (oRequest.getMethod().equals("OPTIONS")) {
            response.flushBuffer();
        } else {
            chain.doFilter(request, response);
        }
    }
    
    0 讨论(0)
  • 2020-12-01 05:12

    Since main part of question is about unathorized CORS POST-request to login point I immediately point you to step 2.

    But regarding to answers count this is the most relevant question to Spring Security CORS request. So I will describe more elegant solution for configuring CORS with Spring Security. Because except rare situations it is not necessary to create filters/interceptors/… to put anything in response. We will do that declaratively by Spring. Since Spring Framework 4.2 we have CORS-stuff like filter, processor, etc out-of-the-box. And some links to read 1 2.

    Let's go:

    1. prepare CORS configuration source.

    It can be done in different ways:

    • as global Spring MVC CORS config (in configuration classes like WebMvcConfigurerAdapter)

      ...
          @Override
          public void addCorsMappings(CorsRegistry registry) {
              registry.addMapping("/**")
                  .allowedOrigins("*")
                  ...
          }
      
    • as separate corsConfigurationSource bean

      @Bean
      CorsConfigurationSource corsConfigurationSource() {
          CorsConfiguration config = new CorsConfiguration();
          config.applyPermitDefaultValues();
      
          UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
          source.registerCorsConfiguration("/**", config);
      }
      
    • as external class (which can be used via constructor or autowired as a component)

      // @Component // <- for autowiring
      class CorsConfig extends UrlBasedCorsConfigurationSource {
      
          CorsConfig() {
              orsConfiguration config = new CorsConfiguration();
              config.applyPermitDefaultValues(); // <- frequantly used values
      
              this.registerCorsConfiguration("/**", config);
          }
      }
      

    2. enable CORS support with the defined configuration

    We will enable CORS support in Spring Security classes like WebSecurityConfigurerAdapter. Be sure that corsConfigurationSource is accessible for this support. Else provide it via @Resource autowiring or set explicitly (see in example). Also we let unauthorized access to some endpoints like login:

        ...
        // @Resource // <- for autowired solution
        // CorseConfigurationSource corsConfig;
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.cors();
    
            // or autowiring
            // http.cors().configurationSource(corsConfig);
    
            // or direct set
            // http.cors().configurationSource(new CorsConfig());
    
            http.authorizeRequests()
                    .antMatchers("/login").permitAll() // without this line login point will be unaccessible for authorized access
                    .antMatchers("/*").hasAnyAuthority(Authority.all()); // <- all other security stuff
        }
    

    3. customize CORS config

    If base config works then we can customize mappings, origins, etc. Even add several configurations for different mappings. For example, I explicitly declare all CORS parameters and let UrlPathHelper to not trim my servlet path:

    class RestCorsConfig extends UrlBasedCorsConfigurationSource {
    
        RestCorsConfig() {
            this.setCorsConfigurations(Collections.singletonMap("/**", corsConfig()));
            this.setAlwaysUseFullPath(true);
        }
    
        private static CorsConfiguration corsConfig() {
            CorsConfiguration config = new CorsConfiguration();
            config.addAllowedHeader("*");
            config.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE"));
            config.setAllowCredentials(true);
            config.addAllowedOrigin("*");
            config.setMaxAge(3600L);
            return config;
        }
    }
    

    4. troubleshooting

    To debug my problem I was tracing org.springframework.web.filter.CorsFilter#doFilterInternal method. And I saw that CorsConfiguration search returns null because Spring MVC global CORS configuration was unseen by Spring Security. So I used solution with direct usage of external class:

    http.cors().configurationSource(corsConfig);
    
    0 讨论(0)
  • 2020-12-01 05:13

    I was able to do this by extending UsernamePasswordAuthenticationFilter... my code is in Groovy, hope that's OK:

    public class CorsAwareAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
        static final String ORIGIN = 'Origin'
    
        @Override
        public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response){
            if (request.getHeader(ORIGIN)) {
                String origin = request.getHeader(ORIGIN)
                response.addHeader('Access-Control-Allow-Origin', origin)
                response.addHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE')
                response.addHeader('Access-Control-Allow-Credentials', 'true')
                response.addHeader('Access-Control-Allow-Headers',
                        request.getHeader('Access-Control-Request-Headers'))
            }
            if (request.method == 'OPTIONS') {
                response.writer.print('OK')
                response.writer.flush()
                return
            }
            return super.attemptAuthentication(request, response)
        }
    }
    

    The important bits above:

    • Only add CORS headers to response if CORS request detected
    • Respond to pre-flight OPTIONS request with a simple non-empty 200 response, which also contains the CORS headers.

    You need to declare this bean in your Spring configuration. There are many articles showing how to do this so I won't copy that here.

    In my own implementation I use an origin domain whitelist as I am allowing CORS for internal developer access only. The above is a simplified version of what I am doing so may need tweaking but this should give you the general idea.

    0 讨论(0)
提交回复
热议问题