使用 Spring 引导获取 Post 403 Forbidden(VueJS 和 Axios 前端)

Getting a Post 403 Forbidden with Spring Boot (VueJS and Axios Frontend)

我一直遇到 CORS 问题,我尝试了 一切 我能在 Stack Overflow 上找到的东西,基本上我在 Google 上找到的任何东西没有运气。

所以我的后端有用户身份验证,我的前端有一个登录页面。我用 Axios 连接了登录页面,这样我就可以发出 post 请求并尝试登录,但我一直收到 "Preflight request" 之类的错误,所以我修复了这个问题,然后我开始收到 "Post 403 Forbidden" 错误。

它看起来像这样:

POST http://localhost:8080/api/v1/login/ 403 (Forbidden)

即使尝试使用 Postman 登录也不起作用,所以显然有问题。将 posting class 下面的文件

在我的后端,我有一个名为 WebSecurityConfig 的 class,它处理所有 CORS 内容:

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

    @Autowired
    private UserDetailsServiceImpl userDetailsService;

    @Bean
    public WebMvcConfigurer corsConfigurer() {
        return new WebMvcConfigurerAdapter() {
            @Override
            public void addCorsMappings(CorsRegistry registry) {
                registry.addMapping("/**")
                        .allowedMethods("GET", "POST", "HEAD", "PUT", "DELETE", "OPTIONS");
            }
        };
    }

    @Bean
    public CorsFilter corsFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowCredentials(true);
        config.addAllowedOrigin("*");  // TODO: lock down before deploying
        config.addAllowedHeader("*");
        config.addExposedHeader(HttpHeaders.AUTHORIZATION);
        config.addAllowedMethod("*");
        source.registerCorsConfiguration("/**", config);
        return new CorsFilter(source);
    }


    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.headers().frameOptions().disable();
        http
                .cors()
                .and()
                .csrf().disable().authorizeRequests()
                .antMatchers("/").permitAll()
                .antMatchers("/h2/**").permitAll()
                .antMatchers(HttpMethod.POST, "/api/v1/login").permitAll()
                .anyRequest().authenticated()
                .and()
                // We filter the api/login requests
                .addFilterBefore(new JWTLoginFilter("/api/v1/login", authenticationManager()),
                        UsernamePasswordAuthenticationFilter.class);
        // And filter other requests to check the presence of JWT in header
        //.addFilterBefore(new JWTAuthenticationFilter(),
        //       UsernamePasswordAuthenticationFilter.class);
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // Create a default account
        auth.userDetailsService(userDetailsService);
//        auth.inMemoryAuthentication()
//                .withUser("admin")
//                .password("password")
//                .roles("ADMIN");
    }
}

在我们用 VueJS 编写并使用 Axios 进行调用的前端

<script>
    import { mapActions } from 'vuex';
    import { required, username, minLength } from 'vuelidate/lib/validators';

    export default {
        data() {
            return {
                form: {
                    username: '',
                    password: ''
                },
                e1: true,
                response: ''
            }
        },
        validations: {
            form: {
                username: {
                    required
                },
                password: {
                    required
                }
            }
        },
        methods: {
            ...mapActions({
                setToken: 'setToken',
                setUser: 'setUser'
            }),
            login() {
                this.response = '';
                let req = {
                    "username": this.form.username,
                    "password": this.form.password
                };

                this.$http.post('/api/v1/login/', req)
                .then(response => {
                    if (response.status === 200) {
                        this.setToken(response.data.token);
                        this.setUser(response.data.user);

                        this.$router.push('/dashboard');
                    } else {
                        this.response = response.data.error.message;
                    }
                }, error => {
                    console.log(error);
                    this.response = 'Unable to connect to server.';
                });
            }
        }
    }
</script>

因此,当我通过 Chrome 的工具(网络)进行调试时,我注意到 OPTIONS 请求如下所示:

这是 POST 错误的图片:

这是另一个处理 OPTIONS 请求的 class(WebSecurityConfig 中引用的 JWTLoginFilter):

public class JWTLoginFilter extends AbstractAuthenticationProcessingFilter {

    public JWTLoginFilter(String url, AuthenticationManager authManager) {
        super(new AntPathRequestMatcher(url));
        setAuthenticationManager(authManager);

    }

    @Override
    public Authentication attemptAuthentication(
            HttpServletRequest req, HttpServletResponse res)
            throws AuthenticationException, IOException, ServletException {
        AccountCredentials creds = new ObjectMapper()
                .readValue(req.getInputStream(), AccountCredentials.class);
        if (CorsUtils.isPreFlightRequest(req)) {
            res.setStatus(HttpServletResponse.SC_OK);
            return null;

        }
        return getAuthenticationManager().authenticate(
                new UsernamePasswordAuthenticationToken(
                        creds.getUsername(),
                        creds.getPassword(),
                        Collections.emptyList()

                )
        );
    }

    @Override
    protected void successfulAuthentication(
            HttpServletRequest req,
            HttpServletResponse res, FilterChain chain,
            Authentication auth) throws IOException, ServletException {
        TokenAuthenticationService
                .addAuthentication(res, auth.getName());
    }
}

我遇到了同样的问题,GET 请求可以正常工作,但 POST 请求的回复状态为 403。

我发现对于我的情况,这是因为默认启用了 CSRF 保护。

确保这种情况的快速方法是禁用 CSRF:

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    // …

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // …
        http.csrf().disable();
        // …
    }

    // …

}

有关 Spring-Security 网站的更多信息。

请注意,禁用 CSRF 并不总是正确的答案,因为它是出于安全目的。

除少数特殊情况外,您不应根据 Spring 安全文档禁用 CSRF。此代码会将 CSRF header 放入 VUE。我用了 vue-resource.

//This token is from Thymeleaf JS generation.
var csrftoken = [[${_csrf.token}]]; 

console.log('csrf - ' + csrftoken) ;

Vue.http.headers.common['X-CSRF-TOKEN'] = csrftoken;

希望对您有所帮助。

配置axios时,只需指定header即可一劳永逸:

import axios from "axios";

const CSRF_TOKEN = document.cookie.match(new RegExp(`XSRF-TOKEN=([^;]+)`))[1];
const instance = axios.create({
  headers: { "X-XSRF-TOKEN": CSRF_TOKEN }
});
export const AXIOS = instance;

然后(这里我假设你使用 SpringBoot 2.0.0,虽然它也应该在 SpringBoot 1.4.x 之后工作)在你的 Spring启动应用程序,您应该添加以下安全配置。

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            // CSRF Token
            .csrf()
                .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
           // you can chain other configs here
    }

}

这样,Spring 将 return 令牌作为响应中的 cookie(我假设您首先执行 GET),您将在 AXIOS 配置中读取它文件。

Axios 默认会正确处理 X-XSRF-TOKEN。

所以唯一的行动就是配置服务器,就像冉阿让解释的那样:

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            // CSRF Token
            .csrf()
                .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
           // you can chain other configs here
    }

}

Axios 会自动在请求中发送正确的令牌 headers,因此无需更改 front-end。