我在CORS配置方面遇到了麻烦 . 我已经在YT上关注了一些教程和视频,我在这里寻求帮助,在StackOverflow上 - 但没有任何帮助我 . 我有两个后端项目(第一个 - 带有OAuth2和JWT的Spring Boot 1.5.9,第二个 - 带有Spring Security 5基本身份验证的Spring Boot 2.0.0 M7),我的朋友使用React作为前端 . 在两者中我都有同样的问题 - 当我们想要登录时,服务器响应具有401 HTTP状态并显示以下消息: Failed to load http://localhost:8080/oauth/token: Response for preflight has invalid HTTP status code 401

Case 1 (Spring Boot 1.5.9 OAuth2 JWT)

在这个项目中,我有

扩展WebSecurityConfigurerAdapter的SecurityConfig类我的AuthorizationServerConfigurerAdapter的实现和ResourceServerConfig的实现

我的ResourceServerConfig类如下所示:

@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

private static final String RESOURCES_IDS = "ResourceId";
private static final String SECURITY_REALM = "Spring Boot Realm";

private final TokenStore tokenStore;

@Autowired
public ResourceServerConfig(TokenStore tokenStore) {
    this.tokenStore = tokenStore;
}

@Override
public void configure(ResourceServerSecurityConfigurer resources) {
    resources.resourceId(RESOURCES_IDS)
            .tokenStore(tokenStore);
}

@Override
public void configure(HttpSecurity http) throws Exception {
    http
            .sessionManagement()
            .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            .and()
            .authorizeRequests()
            .antMatchers(REGISTER_USER + "/**").permitAll()
            .antMatchers(HttpMethod.GET, GEOTAGS_PATH + "/**").permitAll()
            .antMatchers("/api/**").authenticated()
            .anyRequest().permitAll()
            .and()
            .httpBasic()
            .realmName(SECURITY_REALM)
            .and()
            .csrf().disable().cors();
    }
}

AuthorizationServerConfig:

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

private final TokenStore tokenStore;
private final JwtAccessTokenConverter accessTokenConverter;
private final AuthenticationManager authenticationManager;
private final DataSource dataSource;

@Autowired
public AuthorizationServerConfig(TokenStore tokenStore,
                                 JwtAccessTokenConverter accessTokenConverter,
                                 AuthenticationManager authenticationManager,
                                 @Qualifier("customDatasource") DataSource dataSource) {
    this.tokenStore = tokenStore;
    this.accessTokenConverter = accessTokenConverter;
    this.authenticationManager = authenticationManager;
    this.dataSource = dataSource;
}

@Override
public void configure(ClientDetailsServiceConfigurer configurer) throws Exception {
    configurer
            .jdbc(dataSource);
}

@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
    final TokenEnhancerChain enhancerChain = new TokenEnhancerChain();
    enhancerChain.setTokenEnhancers(Collections.singletonList(accessTokenConverter));
    endpoints.tokenStore(tokenStore)
            .accessTokenConverter(accessTokenConverter)
            .tokenEnhancer(enhancerChain)
            .authenticationManager(authenticationManager);
  }
}

最后是SecurityConfig:

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

private static final String SIGNING_KEY = "Gz73RSOADKDFXzONqg3q";

private UserDetailsService customUserDetailsService;
private DataSource dataSource;

@Autowired
public void setCustomUserDetailsService(UserDetailsService customUserDetailsService) {
    this.customUserDetailsService = customUserDetailsService;
}

@Autowired
public void setDataSource(@Qualifier("customDatasource") DataSource dataSource) {
    this.dataSource = dataSource;
}

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.userDetailsService(customUserDetailsService).passwordEncoder(passwordEncoder());
}

@Bean
public PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
}

@Bean
public JwtAccessTokenConverter accessTokenConverter() {
    JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
    converter.setSigningKey(SIGNING_KEY);
    return converter;
}

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

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

@Bean
public CorsConfigurationSource corsConfigurationSource() {
    final CorsConfiguration configuration = new CorsConfiguration();
    configuration.setAllowedOrigins(Collections.singletonList("*"));
    configuration.setAllowedMethods(Collections.singletonList("*"));
    configuration.setAllowedHeaders(Collections.singletonList("*"));
    final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    source.registerCorsConfiguration("/**", configuration);

    return source;
   }

}

正如您所看到的,我使用CorsConfigurationSource Bean,但是当我们运行不同来源的javascript代码时:

fetch('http://localhost:8080/oauth/token', {
method: 'POST',
headers: {
    'Content-Type': 'application/x-www-form-urlencoded',
    'Authorization': `Basic ${window.btoa('eTaxiClientId:secret')}`
},
body: `username=${encodeURIComponent('Admin')}&password=${encodeURIComponent('pass')}&grant_type=password`
});

服务器响应,我在开头描述的消息 .

我也试过使用我的WebMvcConfigurerAdapter实现与重写的addCorsMappings方法,我试图允许所有HttpMethod.OPTIONS但浏览器总是发送OPTIONS请求与401代码匹配 .

Case 2 (Spring Boot 2)

在这个项目中,我尝试使用基本的Spring Security授权系统 - 没有OAuth2和JWT . 我的配置是:

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

private final MyUserDetailsService userDetailsService;
private final DataSource dataSource;

public SecurityConfig(MyUserDetailsService userDetailsService,
                      @Qualifier("customDatasource") DataSource dataSource) {
    this.userDetailsService = userDetailsService;
    this.dataSource = dataSource;
}

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

    http
            .servletApi().and()
            .authorizeRequests()
            .antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
            .antMatchers("/api/1").hasAuthority("USER")
            .antMatchers("/api/2").hasAuthority("ADMIN")
            .antMatchers("/api/3").hasAuthority("GUEST")
            .and()
            .anonymous().principal("guest").authorities("GUEST")
            .and()
            .formLogin().permitAll()
            .successHandler(new CustomAuthenticationSuccessHandler())
            .failureHandler(new CustomAuthenticationFailureHandler())
            .and()
            .logout()
            .logoutSuccessUrl("/login")
            .invalidateHttpSession(true)
            .deleteCookies("JSESSIONID")
            .and()
            .exceptionHandling()
            .and()
            .rememberMe().rememberMeParameter("remember-me").tokenRepository(tokenRepository())
            .and()
            .headers()
            .and()
            .sessionManagement()
            .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            .and()
            .httpBasic()
            .and()
            .cors()
            .and()
            .csrf().disable();
 //                .csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
}

@Override
protected void configure(AuthenticationManagerBuilder auth) {
    auth.authenticationProvider(authenticationProvider());
}

@Bean
public DaoAuthenticationProvider authenticationProvider() {
    DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
    authProvider.setUserDetailsService(userDetailsService);
    authProvider.setPasswordEncoder(passwordEncoder());
    return authProvider;
}

@Bean
public PasswordEncoder passwordEncoder() {
    return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}

@Bean
public PersistentTokenRepository tokenRepository() {
    JdbcTokenRepositoryImpl jdbcTokenRepositoryImpl = new JdbcTokenRepositoryImpl();
    jdbcTokenRepositoryImpl.setDataSource(dataSource);
    return jdbcTokenRepositoryImpl;
}

@Bean
public CorsConfigurationSource corsConfigurationSource() {
    final CorsConfiguration configuration = new CorsConfiguration();
    configuration.setAllowedHeaders(Collections.singletonList("*"));
    configuration.setAllowedOrigins(Collections.singletonList("*"));
    configuration.setAllowedMethods(Collections.singletonList("*"));
    final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    source.registerCorsConfiguration("/**", configuration);

    return source;
}

}

在这种情况下,我们在JavaScript应用程序中运行以下代码:

const body = `username=${encodeURIComponent('admin')}&password=${encodeURIComponent('pass')}`;

const hashedCredentials = btoa('admin:pass');

return axios.post(`${process.env.REACT_APP_API_URL}/login`, body, {
    headers: {
        'Content-Type': 'application/x-www-form-urlencoded',
        // test oauth credentials
        Authorization: `Basic ${hashedCredentials}`
    }
})
    .then(() => {
    localStorage.setItem('user', hashedCredentials);
})
.then(() => {
    const userCredentials = localStorage.getItem('user');

return axios.get(`${process.env.REACT_APP_API_URL}/api/2`, null, {
    headers: {
        Authorization: `Basic ${userCredentials}`
    }
});
}).then(res => console.log(res)).catch(err => console.log(err));

OPTIONS请求传递,我们有状态200响应,但您可以看到我们尝试将用户重定向到受保护的 endpoints . 但是,Spring Security不会在屏幕上显示消息,而是重定向到登录页面 .

Summary

有人有这样的问题吗?有人看到我们做错了吗?为什么我们两个项目都有问题?用户身份验证是更好(更安全)的方法吗?

我希望我没有忘记描述一些事情,我期待着回应 . 干杯!