我遇到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
案例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代码匹配。
案例2(春季启动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响应,但您可以看到我们尝试将用户重定向到受保护的端点。但是,Spring Security不会在屏幕上显示消息,而是重定向到登录页面。
摘要
有人有这样的问题吗?有人看到我们做错了吗?为什么我们两个项目都有问题?用户身份验证是更好(更安全)的方法吗?
我希望我没有忘记描述一些事情,我期待着回应。干杯!