使用 Spring Boot(VueJS 和 Axios 前端)禁止发布 403 帖子

Posted

技术标签:

【中文标题】使用 Spring Boot(VueJS 和 Axios 前端)禁止发布 403 帖子【英文标题】:Getting a Post 403 Forbidden with Spring Boot (VueJS and Axios Frontend) 【发布时间】:2018-02-11 01:31:40 【问题描述】:

我在使用 CORS 时遇到了问题,我尝试了 一切 我可以在 Stack Overflow 上找到的所有东西,基本上是我在 Google 上找到的所有东西,但都没有运气。

所以我的后端有用户身份验证,我的前端有一个登录页面。我用 Axios 连接了登录页面,所以我可以发出一个帖子请求并尝试登录,但我一直收到诸如“预检请求”之类的错误,所以我修复了这个问题,然后我开始收到“403 禁止发布”错误。

它看起来像这样:

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

即使尝试使用 Postman 登录也无法正常工作,因此显然有问题。将在下面发布课程文件

在我的后端,我有一个名为 WebSecurityConfig 的类,它处理所有 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 请求的类(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());
    

【问题讨论】:

【参考方案1】:

我遇到了同样的问题,即 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 并不总是正确的答案,因为它是出于安全目的。

【讨论】:

【参考方案2】:

您不应该按照 Spring Security 文档禁用 CSRF,除了少数特殊情况。此代码会将 CSRF 标头放入 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;

希望这会有所帮助。

【讨论】:

【参考方案3】:

配置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 Boot 应用程序中,您应该添加以下安全配置。

@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 会在响应中以 cookie 的形式返回令牌(我假设您首先执行 GET),然后您将在 AXIOS 配置文件中读取它。

【讨论】:

我正在使用 .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) 但问题是这仅适用于 GET 操作......为什么它不适用于 PUT、POST 和 DELETE?跨度> 【参考方案4】:

默认情况下,Axios 会正确处理 X-XSRF-TOKEN。

所以唯一的操作就是配置服务器,就像 JeanValjean 解释的那样:

@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 会自动在请求头中发送正确的令牌,因此无需更改前端。

【讨论】:

以上是关于使用 Spring Boot(VueJS 和 Axios 前端)禁止发布 403 帖子的主要内容,如果未能解决你的问题,请参考以下文章

WebTestClient - 带有 Spring Boot 和 Webflux 的 CORS

Spring-Boot 如何正确注入 javax.validation.Validator

Spring Boot前后端分离跨域问题

Dockerize vue js前端和spring boot后端并部署在kubernetes集群上

Vuejs Axios POST 请求从 Laravel 后端获取 HTTP422 错误

Spring boot 梳理 - Spring Boot 属性配置和使用(转)