如何让 Spring Security 使用 OAUTH2 响应 Pre-Flight CORS 请求

Posted

技术标签:

【中文标题】如何让 Spring Security 使用 OAUTH2 响应 Pre-Flight CORS 请求【英文标题】:How to get Spring Security to respond to Pre-Flight CORS request with OAUTH2 【发布时间】:2016-12-24 10:17:59 【问题描述】:

我不是专业的 Java 程序员。我正在为我们的实验室构建一个应用程序。它最初是一个带有 JSP 文件的 Spring MVC 应用程序。它现在已迁移到使用 OAUTH2 作为独立身份验证和授权服务器的 Spring REST API。我正在使用 mysql 数据库来存储我的数据和用户。如果我使用 Postman 访问服务器,我可以成功获得一个 access_token。但是,如果我使用我设置的 Angular 2 客户端,我无法通过客户端发送到服务器的飞行前 OPTIONS 请求。我已经尝试了几种不同的方法来配置 CORS 过滤器,但我无法实现该过滤器,并且我总是得到一个为飞行前请求返回的 401 代码。我已尝试在 Spring 博客 https://spring.io/blog/2015/06/08/cors-support-in-spring-framework 中实施建议的解决方案,但尚未奏效。

在我的 pom.xml 文件中,我使用的是这些版本的 Spring

Spring Framework - 4.3.2.RELEASE
Spring Security  - 4.1.2.RELEASE
Spring Security OAUTH2 - 2.0.10.RELEASE

我在尝试使用 FilterRegistrationBean 时还包含 Spring Boot 1.4.0

这是我现在的代码。

WebConfig Class:
@EnableWebMvc
@Configuration
@ComponentScan(basePackages="eng.lab")
@Import(ApplicationContext.class)
@PropertySource("classpath:application.properties")
public class WebConfig extends WebMvcConfigurerAdapter

/*  @Override
    public void addCorsMappings(CorsRegistry registry) 
        registry.addMapping("/**");
    */


SecurityConfig 类:

@Configuration
@EnableWebSecurity
@PropertySource("classpath:application.properties")
public class SecurityConfig extends WebSecurityConfigurerAdapter 

    @Autowired
    DataSource dataSource;


     @Autowired
     public void configAuthentication(AuthenticationManagerBuilder auth) throws Exception 
       auth.jdbcAuthentication().dataSource(dataSource)
      .usersByUsernameQuery(
       "select username,password, enabled from user where username=?")
      .authoritiesByUsernameQuery(
       "select u.username, r.role from User u, Role r where r.id=u.roleId and u.userName=?");
       


     @Autowired
     private ClientDetailsService clientDetailsService;


            @Override
            protected void configure(HttpSecurity http) throws Exception 
                http
                .csrf().disable()
                .anonymous().disable()
                .authorizeRequests()
                .antMatchers(HttpMethod.OPTIONS, "/oauth/token").permitAll()
                //.requestMatchers(CorsUtils::isCorsRequest).permitAll()
                .antMatchers("/oauth/token").permitAll();
           

            @Override
            @Bean
            public AuthenticationManager authenticationManagerBean() throws Exception 
                return super.authenticationManagerBean();
            


            @Bean
            public TokenStore tokenStore() 
                return new InMemoryTokenStore();
            

            @Bean
            @Autowired
            public TokenStoreUserApprovalHandler userApprovalHandler(TokenStore tokenStore)
                TokenStoreUserApprovalHandler handler = new TokenStoreUserApprovalHandler();
                handler.setTokenStore(tokenStore);
                handler.setRequestFactory(new DefaultOAuth2RequestFactory(clientDetailsService));
                handler.setClientDetailsService(clientDetailsService);
                return handler;
            

            @Bean
            @Autowired
            public ApprovalStore approvalStore(TokenStore tokenStore) throws Exception 
                TokenApprovalStore store = new TokenApprovalStore();
                store.setTokenStore(tokenStore);
                return store;
            

        

MethodSecurityConfig 类:

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true, proxyTargetClass = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration 

    @SuppressWarnings("unused")
    @Autowired
    private SecurityConfig securityConfig;

    @Override
    protected MethodSecurityExpressionHandler createExpressionHandler() 
        return new OAuth2MethodSecurityExpressionHandler();
    

WebAppInitializer 类:

public class WebAppInitializer implements WebApplicationInitializer

    @Override
    public void onStartup(ServletContext container) throws ServletException 

        AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
        rootContext.register(WebConfig.class);

        container.addListener(new ContextLoaderListener(rootContext));

        DelegatingFilterProxy filter = new DelegatingFilterProxy("springSecurityFilterChain");

        DispatcherServlet dispatcherServlet = new DispatcherServlet(rootContext);

        ServletRegistration.Dynamic registration = container.addServlet("dispatcherServlet", dispatcherServlet);

        container.addFilter("springSecurityFilterChain", filter).addMappingForUrlPatterns(null,  false, "/*");

        registration.setLoadOnStartup(1);
        registration.addMapping("/");

    

SimpleCorsFilter 类:

@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class SimpleCorsFilter implements Filter 

    public SimpleCorsFilter() 
    

    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException 
        HttpServletResponse response = (HttpServletResponse) res;
        HttpServletRequest request = (HttpServletRequest) req;
        response.setHeader("Access-Control-Allow-Origin", "*");
        response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
        response.setHeader("Access-Control-Max-Age", "3600");
        response.setHeader("Access-Control-Allow-Headers", "x-requested-with, authorization");

        if ("OPTIONS".equalsIgnoreCase(request.getMethod())) 
            response.setStatus(HttpServletResponse.SC_OK);
         else 
            chain.doFilter(req, res);
        
    

    @Override
    public void init(FilterConfig filterConfig) 
    

    @Override
    public void destroy() 
    

如果我包含第二个 WebSecurityConfig 类,我会得到一个 403 代码。

MyWebSecurity 类:

@Configuration
@EnableWebSecurity
@Order(-1)
public class MyWebSecurity extends WebSecurityConfigurerAdapter 
   @Override
   protected void configure(HttpSecurity http) throws Exception 
       http
          .authorizeRequests()
          .antMatchers(HttpMethod.OPTIONS, "/oauth/token").permitAll();
   

除了等待修复 DATAREST-573 错误之外,关于如何克服飞行前问题的任何建议?

更新:我尝试了 Benjamin 的建议,但不确定我是否正确实施了解决方案。我已经按如下方式编辑了我的 WebConfig 类,但飞行前仍然失败。过滤器仍未应用。

package eng.lab.config;


import javax.servlet.Filter;

import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.PropertySource;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

@EnableWebMvc
@Configuration
@ComponentScan(basePackages="eng.lab")
@Import(ApplicationContext.class)
@PropertySource("classpath:application.properties")
public class WebConfig extends WebMvcConfigurerAdapter

    //more custome rule beans

    @Bean
    public FilterRegistrationBean simpleCORSFilterRegistration() 

        FilterRegistrationBean registration = new FilterRegistrationBean();
        registration.setFilter(new SimpleCorsFilter());
        registration.addUrlPatterns("/*");
        //registration.addInitParameter("paramName", "paramValue");
        registration.setName("simpleCorsFilter");
        registration.setOrder(0);
        return registration;
     

    @Bean(name = "simpleCorsFilter")
    public Filter simpleCorsFilter() 
        return new SimpleCorsFilter();
    

/*  @Override
    public void addCorsMappings(CorsRegistry registry) 
        registry.addMapping("/**");
    */


【问题讨论】:

你的 SimpleCorsFilter 是 javax.servlet.Filter 吗? 是的。我使用了这个 import import javax.servlet.Filter; 我认为这个类中的代码没有被执行。你能确认一下吗? 【参考方案1】:

您的 CORS 过滤器未注册。由于这是一个标准的 javax.servlet.Filter 实例,您需要针对 FilterRegistrationBean 注册它或在您的 web.xml 文件中声明它。

您可以参考这个 SO 问题了解更多详情:How to add a filter class in Spring Boot?

【讨论】:

以上是关于如何让 Spring Security 使用 OAUTH2 响应 Pre-Flight CORS 请求的主要内容,如果未能解决你的问题,请参考以下文章

如何让 Spring Security 使用 OAUTH2 响应 Pre-Flight CORS 请求

spring security - 如何让另一个用户注销

Spring Security:如何让用户打开任何页面的监听器?

如何让spring security不拦截第三方的对接方法

如何通过 REST API 为 Spring-social 和 spring-security 设置登录服务?

Spring Security 使用非spring默认的登陆页面,是如何跳转到登陆成功页面的?