Spring Auth + JWT登录响应200,但正文和标头丢失

时间:2018-12-22 10:51:39

标签: java spring cors jwt fetch

我在使用Spring Security时遇到了一些困难。基本上,我分别有/auth/signup/auth/login用于创建帐户和获取jwt令牌。

两者都可以通过Insomnia / POSTman 在本地完美运行,但是在从React.js前端向/auth/login发送到fetch()的请求时,要么返回CORS预检请求错误或给出 no body 的“不透明”或“ cors” 200 OK响应。后者没有用,因为我需要令牌。令人烦恼的是它可以治疗失眠症。

这是本地的javascript通过fetch()触发时响应的样子。没有正文,也没有标题。与Insomnia或POSTman一样,那里有标头和正文。

enter image description here

在过去的几周中,我尝试了很多不同的事情,包括:

  • addCorsMappings()的暗示中添加自定义WebMvcConfigurer呼叫
  • CORS过滤器@Component会覆盖doFiler(),并有一系列setHeader()调用。
  • CORS配置配置类,其中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 })
        })

3 个答案:

答案 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);
}