【问题标题】:Allow OPTIONS HTTP Method for oauth/token request允许 OPTIONS HTTP 方法用于 oauth/token 请求
【发布时间】:2014-09-27 23:32:01
【问题描述】:

我正在尝试为我的 Angular 应用程序启用 oauth2 令牌获取。我的配置工作正常(所有请求的身份验证都正常工作,令牌获取也工作正常)但是有一个问题。

CORS 请求要求在 GET 之前将 OPTIONS 请求发送到服务器。更糟糕的是,该请求不包含任何身份验证标头。 我希望此请求始终以 200 状态返回,而无需在服务器上进行任何身份验证。可能吗?也许我错过了什么

我的 spring 安全配置:

@Configuration
@EnableWebSecurity
@EnableAuthorizationServer
public class SecurityConfig extends WebSecurityConfigurerAdapter {

private static final Logger log = LoggerFactory.getLogger(SecurityConfig.class);

@Inject
private UserService userService;

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

@Bean
public DefaultTokenServices tokenServices() {
    DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
    defaultTokenServices.setTokenStore(tokenStore());
    return defaultTokenServices;
}

@Bean
public WebResponseExceptionTranslator webResponseExceptionTranslator() {
    return new DefaultWebResponseExceptionTranslator() {

        @Override
        public ResponseEntity<OAuth2Exception> translate(Exception e) throws Exception {
            ResponseEntity<OAuth2Exception> responseEntity = super.translate(e);
            OAuth2Exception body = responseEntity.getBody();
            HttpHeaders headers = new HttpHeaders();
            headers.setAll(responseEntity.getHeaders().toSingleValueMap());
            headers.set("Access-Control-Allow-Origin", "*");
            headers.set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE, PUT");
            headers.set("Access-Control-Max-Age", "3600");
            headers.set("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
            return new ResponseEntity<>(body, headers, responseEntity.getStatusCode());
        }

    };
}

@Bean
public AuthorizationServerConfigurer authorizationServerConfigurer() {
    return new AuthorizationServerConfigurer() {

        @Override
        public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
            OAuth2AuthenticationEntryPoint oAuth2AuthenticationEntryPoint = new OAuth2AuthenticationEntryPoint();
            oAuth2AuthenticationEntryPoint.setExceptionTranslator(webResponseExceptionTranslator());
            security.authenticationEntryPoint(oAuth2AuthenticationEntryPoint);
        }

        @Override
        public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
            clients.inMemory()
                    .withClient("secret-client")
                    .secret("secret")
                    .authorizedGrantTypes("password", "authorization_code", "refresh_token", "implicit")
                    .authorities("ROLE_LOGIN")
                    .scopes("read", "write", "trust")
                    .accessTokenValiditySeconds(60 * 60 * 12); // 12 hours
        }

        @Override
        public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
            endpoints.tokenServices(tokenServices());
            endpoints.authenticationManager(authenticationManager());
        }
    };
}

@Override
protected AuthenticationManager authenticationManager() throws Exception {
    return new AuthenticationManager() {

        @Override
        public Authentication authenticate(Authentication authentication) throws AuthenticationException {
            log.warn("FIX ME: REMOVE AFTER DEBUG!!!!!!!!!!!!");                
            log.debug("authenticate: " + authentication.getPrincipal() + ":" + authentication.getCredentials());
            final Collection<GrantedAuthority> authorities = new ArrayList<>();
            WomarUser user = userService.findUser(authentication.getPrincipal().toString(), authentication.getCredentials().toString());
            for (UserRole userRole : user.getRoles()) {
                authorities.add(new SimpleGrantedAuthority(userRole.getName()));

            }
            return new UsernamePasswordAuthenticationToken(user.getLogin(), user.getPassword(), authorities);
        }

    };
}

@Bean
public OAuth2AuthenticationManager auth2AuthenticationManager() {
    OAuth2AuthenticationManager oAuth2AuthenticationManager = new OAuth2AuthenticationManager();
    oAuth2AuthenticationManager.setTokenServices(tokenServices());
    return oAuth2AuthenticationManager;
}

@Bean
public OAuth2AuthenticationProcessingFilter auth2AuthenticationProcessingFilter() throws Exception {
    OAuth2AuthenticationProcessingFilter oAuth2AuthenticationProcessingFilter = new OAuth2AuthenticationProcessingFilter();
    oAuth2AuthenticationProcessingFilter.setAuthenticationManager(auth2AuthenticationManager());
    return oAuth2AuthenticationProcessingFilter;
}

@Override
protected void configure(HttpSecurity http) throws Exception {

    OAuth2AuthenticationEntryPoint oAuth2AuthenticationEntryPoint = new OAuth2AuthenticationEntryPoint();
    oAuth2AuthenticationEntryPoint.setRealmName("realmName");
    oAuth2AuthenticationEntryPoint.setTypeName("Basic");
    oAuth2AuthenticationEntryPoint.setExceptionTranslator(webResponseExceptionTranslator());
    http
            .antMatcher("/**").httpBasic()
            .authenticationEntryPoint(oAuth2AuthenticationEntryPoint)
            .and().addFilterBefore(auth2AuthenticationProcessingFilter(), BasicAuthenticationFilter.class)
            .authorizeRequests()
            .antMatchers("/rest/womar/admin/**").hasRole("ADMIN")
            .antMatchers("/rest/womar/**").hasRole("USER");
}

}

角度请求:

var config = {
params: {
    grant_type: 'password',
    username: login,
    password: password

},
headers: {
    Authorization: 'Basic ' + Base64.encode('secret-client' + ':' + 'secret')
}
};
$http.get("http://localhost:8080/oauth/token", config)
    .success(function(data, status) {
        $log.log('success');
        $log.log(data);
        $log.log(status);
    })
    .error(function(data, status) {
        $log.log('error');
        $log.log(data);
        $log.log(status);
    });

【问题讨论】:

    标签: spring oauth spring-security oauth-2.0 cors


    【解决方案1】:

    我只是添加

    @Order(Ordered.HIGHEST_PRECEDENCE)
    

    public class OAuth2SecurityConfig extends WebSecurityConfigurerAdapter {....}
    

    并配置spring的支持

    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
      CorsConfiguration configuration = new CorsConfiguration();
      configuration.setAllowedOrigins(Arrays.asList("*"));
      configuration.setAllowedMethods(Arrays.asList("*"));
      configuration.setAllowedHeaders(Arrays.asList("*"));
    
      UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
      source.registerCorsConfiguration("/**", configuration);
      return source;
    }
    

    为我工作。

    【讨论】:

      【解决方案2】:

      以下适用于 Spring Boot 2。否则它不会选择其他 CORS 配置。

      @Configuration
      @EnableAuthorizationServer
      public class OAuth2AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
      
          // this is a Spring ConfigurationProperty use any way to get the CORS values
          @Autowired
          private CorsProperties corsProperties;
      
          // other things
          //...
      
          @Override
          public void configure(
                  AuthorizationServerEndpointsConfigurer endpoints) {
              endpoints
                      .tokenStore(tokenStore())
                      .authenticationManager(authenticationManager);
              if (corsProperties.getAllowedOrigins() != null) {
                  Map<String, CorsConfiguration> corsConfigMap = new HashMap<>();
                  Arrays.asList(corsProperties.getAllowedOrigins().split(",")).stream()
                          .filter(StringUtils::isNotBlank).forEach(s -> {
                      CorsConfiguration config = new CorsConfiguration();
                      config.setAllowCredentials(true);
                      config.addAllowedOrigin(s.trim());
                      if (corsProperties.getAllowedMethods() != null) {
                          config.setAllowedMethods(Arrays.asList(corsProperties.getAllowedMethods().split(",")));
                      }
                      if (corsProperties.getAllowedHeaders() != null) {
                          config.setAllowedHeaders(Arrays.asList(corsProperties.getAllowedHeaders().split(",")));
                      }
                      // here the /oauth/token is used
                      corsConfigMap.put("/oauth/token", config);
                  });
                  endpoints.getFrameworkEndpointHandlerMapping()
                          .setCorsConfigurations(corsConfigMap);
              }
          }
      
      
      }
      

      另外还有已经提到的 OPTIONS 请求的允许:

      @Order(-1)
      @Configuration
      public class MyWebSecurity extends WebSecurityConfigurerAdapter {
         @Override
         protected void configure(HttpSecurity http) throws Exception {
             http
                authorizeRequests()
                  .antMatchers("/**/oauth/token").permitAll()
                  .and().httpBasic().realmName(securityRealm)
                  // would throw a 403 otherwise
                  .and().csrf().disable()
                  // optional, but with a token a sesion is not needed anymore
      .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
         }
      }
      

      【讨论】:

        【解决方案3】:

        @EnableAuthorizationServer 正在以 0 顺序为 /oauth/token/oauth/token_key 等端点添加 http 安全配置。因此,您应该为 /oauth/token 端点定义一个 http 安全规则,仅用于 OPTIONS http 方法处于更高的顺序。

        类似这样的:

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

        【讨论】:

        • 您好,您可以查看我的 github 存储库,在那里我使用 angular 和 spring 进行了一些实验性工作。我的令牌处理在那里工作正常。 github.com/idursun/spring-and-angular
        • 我遇到了同样的问题,但这个解决方案确实产生了另一个问题。我的问题的解决方案是停止处理请求的过滤器链,因此 AuthServer 根本看不到 OPTIONS 预检请求!这里是你可以看到我的解决方案:stackoverflow.com/questions/30632200/…
        • 嗨有同样的问题。即使这个解决方案没有授权/允许对 oauth/token 的 OPTIONS 请求
        • 看起来拦截 OPTIONS 处理的正确方法是使用 requestMatchersHttpSecurity 方法。这样我们就不会像authorizeRequests() 中的过滤器那样影响其他请求:protected void configure(HttpSecurity http) throws Exception { http .requestMatchers() .antMatchers(HttpMethod.OPTIONS, "/oauth/token") .and()。 cors() .and().csrf().disable(); }
        • 有趣的是, AuthorizationServerSecurityConfigurer.configure(AuthorizationServerSecurityConfigurer) 应该允许我们配置对该端点的访问,但是它不区分 HTTP 方法。
        【解决方案4】:

        Spring-Boot 1.4.7.RELEASE 存在同样的问题

        我的WebSecurityConfigurerAdapter 使用的是SecurityProperties.ACCESS_OVERRIDE_ORDER,所以选择的答案不起作用。

        @Configuration
        @EnableWebSecurity
        @EnableGlobalMethodSecurity(prePostEnabled = true)
        @Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
        public class AuthServerSecurityConfig extends WebSecurityConfigurerAdapter 
        

        因此,我按照前面的顺序添加了以下过滤器配置:

          @Bean
          public FilterRegistrationBean corsFilter() {
            FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(corsConfigurationSource()));
            bean.setOrder(SecurityProperties.DEFAULT_FILTER_ORDER);
            return bean;
          }
        
          @Bean
          public CorsConfigurationSource corsConfigurationSource() {
            UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
            CorsConfiguration config = new CorsConfiguration();
            config.setAllowCredentials(true);
            config.addAllowedOrigin("*");
            config.addAllowedHeader("*");
            config.addAllowedMethod("*");
            source.registerCorsConfiguration("/**", config);
            return source;
          }
        

        它完成了工作。

        注意:使用带有@Order(SecurityProperties.DEFAULT_FILTER_ORDER) 注释的javax.servlet.Filter bean 可以获得等效结果,如下所示:

        @Component
        @Order(SecurityProperties.DEFAULT_FILTER_ORDER)
        public class CorsFilter implements Filter {
        
          @Override
          public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
            final 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-Allow-Headers" , "Authorization, Content-Type"     );
            response.setHeader("Access-Control-Max-Age"       , "3600"                            );
        
            if("OPTIONS".equalsIgnoreCase(((HttpServletRequest) req).getMethod())) {
              response.setStatus(HttpServletResponse.SC_OK);
            }
            else {
              chain.doFilter(req, res);
            }
          }
          // ...
        }
        

        【讨论】:

        • 这个解决方案对我有用。我添加了 corsFilter() 和 corsConfigurationSource() bean。只是为了好玩,我尝试了 CorsFilter 类,但没有奏效。非常感谢!
        【解决方案5】:

        我使用的是 idursun 提出的解决方案。 OPTION 调用开始工作,但 Access-Control-Allow-Origin 仍然存在问题。

        这个过滤器实现确实对我有用:

        Standalone Spring OAuth2 JWT Authorization Server + CORS

        【讨论】:

        • 同意,上述提议也使我的自定义 AuthenticationProvider 未注册。无论如何,CORS 过滤器迟早需要实施,也可以在那里处理 OPTIONS 问题。
        猜你喜欢
        • 2016-05-20
        • 2019-05-15
        • 2015-01-24
        • 2018-02-06
        • 2012-09-09
        • 2015-10-06
        • 1970-01-01
        • 1970-01-01
        • 2017-07-04
        相关资源
        最近更新 更多