我在使用Spring Security时遇到了一些困难。基本上,我分别有/auth/signup
和/auth/login
用于创建帐户和获取jwt令牌。
两者都可以通过Insomnia / POSTman 在本地完美运行,但是在从React.js前端向/auth/login
发送到fetch()
的请求时,要么返回CORS预检请求错误或给出 no body 的“不透明”或“ cors” 200 OK响应。后者没有用,因为我需要令牌。令人烦恼的是它可以治疗失眠症。
这是本地的javascript通过fetch()
触发时响应的样子。没有正文,也没有标题。与Insomnia或POSTman一样,那里有标头和正文。
在过去的几周中,我尝试了很多不同的事情,包括:
addCorsMappings()
的暗示中添加自定义WebMvcConfigurer
呼叫@Component
会覆盖doFiler()
,并有一系列setHeader()
调用。corsConfigurer()
作为@Bean
Bean
中的另一个WebSecurityConfigurerAdapter
返回CorsConfigurationSource
事实是,我不知道其中哪一个是最新的,请更正要使用的。我已经阅读了很多没有帮助的内容(主要是堆栈溢出文章和教程)。只是粘贴代码片段而没有完全了解它是否有效或如何工作,这感觉太错误了。如果有人能描述配置CORS以便与React的fetch()
调用一起使用的正确方法,而不仅仅是POSTman / Insomia的话,我将不胜感激。
我将尽可能多地包含源代码,但此处将排除诸如用户类之类的内容。据我了解,在大多数情况下,它们是相当标准的。
WebMvcConfig.java
:
@Configuration
@EnableWebMvc
public class WebMvcConfig implements WebMvcConfigurer {
private final long MAX_AGE_SECS = 360;
@Override
public void addCorsMappings(CorsRegistry registry){
registry.addMapping("/**")
.allowedOrigins("*")
.allowedHeaders("Content-Type", "Access-Control-Allow-Origin", "Access-Control-Allow-Headers", "Authorization", "X-Requested-With", "requestId", "Correlation-Id")
.exposedHeaders("Content-Type", "Access-Control-Allow-Origin", "Access-Control-Allow-Headers", "Authorization", "X-Requested-With", "requestId", "Correlation-Id")
.allowedMethods("HEAD", "OPTIONS", "GET", "POST", "PUT", "PATCH", "DELETE")
.maxAge(MAX_AGE_SECS);
}
}
SimpleCORSFilter.java
:
@Component
public class SimpleCORSFilter implements Filter {
public SimpleCORSFilter() {
}
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
response.setHeader("Access-Control-Allow-Origin", request.getHeader("Origin"));
response.setHeader("Access-Control-Allow-Credentials", "true");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "origin, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Authorization, requestId, Correlation-Id, Content-Type, Accept, X-Requested-With, remember-me");
chain.doFilter(req, res);
}
@Override
public void init(FilterConfig filterConfig) {
}
@Override
public void destroy() {
}
}
CorsConfig.java
:
@Configuration
public class CorsConfig {
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurerAdapter() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedMethods("OPTIONS", "GET", "POST", "PUT", "DELETE").allowedOrigins("*")
.allowedHeaders("*");
}
};
}
}
现在这是SecurityConfig.java
类,它使用了WebSecurityConfigurerAdapter
。注意configure(HttpSecurity http)
和CorsConfigurationSource corsConfigurationSource()
方法。
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(
securedEnabled = true,
jsr250Enabled = true,
prePostEnabled = true
)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
CustomUserDetailsService customUserDetailsService;
@Autowired
private JwtAuthenticationEntryPoint unauthorizedHandler;
@Bean
public JwtAuthenticationFilter jwtAuthenticationFilter() {
return new JwtAuthenticationFilter();
}
@Override
public void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
authenticationManagerBuilder.
userDetailsService(customUserDetailsService)
.passwordEncoder(passwordEncoder());
}
@Bean(BeanIds.AUTHENTICATION_MANAGER)
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
protected void configure(HttpSecurity http) throws Exception {
http
.cors()
.and()
.csrf()
.disable()
.authorizeRequests()
.and()
.exceptionHandling()
.authenticationEntryPoint(unauthorizedHandler)
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/",
"/favicon.ico",
"/**/*.png",
"/**/*.gif",
"/**/*.svg",
"/**/*.jpg",
"/**/*.html",
"/**/*.css",
"/**/*.js")
.permitAll()
.antMatchers("/auth/**")
.permitAll()
.antMatchers("/user/checkUsernameAvailability", "/user/checkEmailAvailability")
.permitAll()
.antMatchers(HttpMethod.GET, "/polls/**", "/users/**")
.permitAll()
.anyRequest()
.authenticated();
// Add our custom JWT security filter
http.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
}
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("*"));
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"));
configuration.setAllowedHeaders(Arrays.asList("authorization", "content-type", "x-auth-token", "Content-Type", "Access-Control-Allow-Origin", "Access-Control-Allow-Headers", "Authorization", "X-Requested-With", "requestId", "Correlation-Id"));
configuration.setExposedHeaders(Arrays.asList("x-auth-token"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
我在/auth
控制器中有两条路线:
@CrossOrigin
@PostMapping(path = "/login", produces = { "application/json" }, consumes = { "application/json" })
public ResponseEntity<?> authenticateUser(@Valid @RequestBody LoginRequest loginRequest){
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(loginRequest.getUsernameOrEmail(),
loginRequest.getPassword())
);
SecurityContextHolder.getContext().setAuthentication(authentication);
String jwt = tokenProvider.generateToken(authentication);
return ResponseEntity.ok().body(new JwtAuthenticationResponse(jwt));
}
@CrossOrigin
@PostMapping(path = "/signup", produces = { "application/json" }, consumes = { "application/json" })
public ResponseEntity<?> registerUser(@Valid @RequestBody SignUpRequest signUpRequest){
if (userClient.existsByUsername(signUpRequest.getUsername())) {
return new ResponseEntity(new ApiResponse(false, "Username already being used!"),HttpStatus.BAD_REQUEST);
}
AppUser user = new AppUser(signUpRequest.getUsername(), signUpRequest.getPassword(), "ADMIN");
user.setPassword(passwordEncoder.encode(user.getPassword()));
AppUser result = userClient.save(user);
URI location = ServletUriComponentsBuilder.fromCurrentContextPath().path("/users/{useranme}").buildAndExpand(result.getUsername()).toUri();
return ResponseEntity.created(location).body(new ApiResponse(true, "Success: User registered"));
}
最后,javascript调用:
fetch(`${config.apiUrl}/auth/login`, {
method: 'POST',
mode: 'cors',
headers: { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', 'accept':'*/*' },
body: JSON.stringify({ "usernameOrEmail": username, "password": password })
})
答案 0 :(得分:2)
已经晚了,但是问题在于我不是.json()
在客户端的请求中。 Facepalm
答案 1 :(得分:0)
我认为您错过了组件扫描注释器。 以下代码对我有用。
@Configuration
@EnableWebMvc
@ComponentScan
public class MainAppConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedOrigins("*");
}
}
答案 2 :(得分:0)
我正在将CrosFilter与React前端一起使用。应用CrosFilter之后,我没有遇到任何问题。尝试使用CrosFilter。找到以下示例。
@Bean
public CorsFilter corsFilter() {
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
final CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowCredentials(true);
configuration.addAllowedOrigin("*");
configuration.addAllowedHeader("*");
configuration.addAllowedMethod("OPTIONS");
configuration.addAllowedMethod("HEAD");
configuration.addAllowedMethod("GET");
configuration.addAllowedMethod("PUT");
configuration.addAllowedMethod("POST");
configuration.addAllowedMethod("DELETE");
configuration.addAllowedMethod("PATCH");
source.registerCorsConfiguration("/**", configuration);
return new CorsFilter(source);
}