POST oauth 令牌最初发送没有数据的 OPTIONS 请求/headers 并失败并返回 401

POST oauth token sends initially OPTIONS request without data / headers and fails with 401

我在使用 spring 从反应 axios 启动后端获取 oauth 令牌时遇到问题:

async login() {
const tokenurl = 'http://localhost:8080/oauth/token';
const data = {
  grant_type: 'password',
  username: this.state.email,
  password: this.state.password,
  scope: 'write'
};
var headers = {
  headers: {'Authorization' : 'Basic ' + btoa('client:secret') ,
  'Content-Type': 'application/x-www-form-urlencoded'}
}
axios.post(tokenurl,data,headers)

第一个没有 headers / 数据的 OPTION 请求被发送并且 401 失败。

Access is denied (user is anonymous)
org.springframework.security.access.AccessDeniedException: Access is denied. 

这是我在后端收到的请求,header/数据已删除。

[OPTIONS /oauth/token HTTP/1.1
Host: localhost:8080
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:75.0) Gecko/20100101 Firefox/75.0
Accept: */*
Accept-Language: de,en-US;q=0.7,en;q=0.3
Accept-Encoding: gzip, deflate
Access-Control-Request-Method: POST
Access-Control-Request-Headers: authorization
Referer: http://localhost:3000/signin/
Origin: http://localhost:3000
Connection: keep-alive

选项请求/post 令牌在 curl 和 Postman 中成功运行并获得新的访问令牌。

curl -X POST -u "client:secret" -d "grant_type=password&username=tata@tata.com&password=test&scope=write" -H "origin:http://localhost:3000" -H "Access-Control-Request-Headers:authorization" http://localhost:8080/oauth/token

curl -X OPTIONS -u "client:secret" -d "grant_type=password&username=tata@tata.com&password=test&scope=write" -H "origin:http://localhost:3000" -H "Access-Control-Request-Headers:authorization" http://localhost:8080/oauth/token

我注意到在 curl OPTIONS 请求中删除 -u "client:secret" 会导致与 axios 相同的错误。

Spring 引导后端安全和 oauth2.0 配置:

网络安全配置:

    @Configuration
    @EnableWebSecurity
    @EnableGlobalMethodSecurity(prePostEnabled = true)
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
    http.cors().configurationSource(corsConfigurationSource()).and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().httpBasic().realmName(securityRealm).and().csrf().disable();
    }

    @Bean(name="CorsSource")
    public CorsConfigurationSource corsConfigurationSource() {
    CorsConfiguration configuration = new CorsConfiguration();
    configuration.applyPermitDefaultValues();    configuration.addAllowedMethod(HttpMethod.OPTIONS);
    configuration.addAllowedOrigin("http://localhost:3000");
    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    source.registerCorsConfiguration("/**", configuration);
    return source;
    }
}

资源配置:

    @Configuration
    @EnableResourceServer
    public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
    @Override
    public void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests().antMatchers(HttpMethod.POST,"/api/**").hasRole("PROVIDER").antMatchers(HttpMethod.GET, "/api/**").hasRole("CLIENT").antMatchers("/admin/**").hasRole("ADMIN");
    }
}

授权服务器配置

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Override
public void configure(ClientDetailsServiceConfigurer configurer) throws Exception {
configurer.inMemory().withClient(clientId).secret(passwordEncoder.encode(clientSecret)).authorizedGrantTypes(grantType).scopes(scopeRead,scopeWrite).resourceIds(esourceIds); //ressourceIds:api,admin
    }
}

你知道我该如何解决这个问题吗?

谢谢:)

经过大量搜索后,我找到了解决 spring 启动后端问题的解决方法,事实上,启用 authorizationServer 后,框架将配置权限以访问 class AuthorizationServerEndpointsConfiguration 并且没有将 OPTION 方法列入白名单,请参阅 https://github.com/spring-projects/spring-security-oauth/issues/330

修复 401 问题: 1. 在 spring 安全配置中,我添加了:

// ignore spring security preflight request for oauth (OPTIONS)
    @Override
    public void configure(WebSecurity web) throws Exception {
        // TODO Auto-generated method stub
         web.ignoring().antMatchers(HttpMethod.OPTIONS, "/oauth/**");
}

我添加了一个 cors 过滤器,如果方法是 OPTIONS,默认情况下 return OK。 (为避免任何冲突,我在 ScurityConfig 中删除了 CorsConfigurationSource 和 http.cors() 的使用,请参阅我的问题中的实现)。过滤器被排序为 HIGHEST_PRECEDENCE 并且它应该是每个请求的第一个执行。

@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
@WebFilter("/*")
public class CorsFilter implements Filter {

    public CorsFilter() {
    }

    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
            throws IOException, ServletException {
        final HttpServletResponse response = (HttpServletResponse) res;
        final HttpServletRequest request = (HttpServletRequest) req;
        response.setHeader("Access-Control-Allow-Origin", "*");
        response.setHeader("Access-Control-Allow-Methods", "POST, PUT, GET, OPTIONS, DELETE");
        response.setHeader("Access-Control-Allow-Headers", "*");
        response.setHeader("Access-Control-Max-Age", "3600");
        if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
            response.setStatus(HttpServletResponse.SC_OK);
        } else {
            chain.doFilter(req, res);
        }
    }
}

这个变通办法解决了我的问题,但它不是一个完美的解决方案,我会很高兴得到一些其他的解决方案。