当前位置: 首页 > 知识库问答 >
问题:

提交JavaScript请求时,Spring Boot中的请求参数为空,但适用于正常请求

孙昂然
2023-03-14

我已经浪费了10个小时试图弄明白一件简单的事情。

我有一个反应前端和一个Spring Boot后端在端口8080上运行。

我在前端有自己的登录表单。

在一个理想的世界里,我所希望的是让Spring Boot像往常一样进行表单验证,而没有那个丑陋的引导表单。

我设置了一个基本的内存身份验证只是为了练习,但它根本不起作用

我有两个选择:

>

在Restendpoint中处理身份验证,然后将安全上下文设置为Restendpoint,而不是过滤器

以下是代码:

配置文件:

@Configuration
@EnableWebSecurity
public class WebSecurity extends WebSecurityConfigurerAdapter {

    @Autowired
    private TestFilter testFilter;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .withUser("mama")
                .password("mama")
                .roles("AUTH");
    }


    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //Request authorization
        http
                .authorizeRequests()
                .mvcMatchers("/api/vault").hasRole("AUTH")
                .mvcMatchers("/api/vault/**").hasRole("AUTH")
                .mvcMatchers("/**").permitAll();

        //Disable default authentication behaviour:
        //http.httpBasic().disable();

       http.formLogin()
                .loginPage("/")
                .loginProcessingUrl("/login")
                .defaultSuccessUrl("/auth-success")
                .failureUrl("/auth-failure");

        //CSRF Protection:
        HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository();
        repository.setHeaderName("X-CSRF-TOKEN");
        repository.setParameterName("_csrf");
        http.csrf()
                .csrfTokenRepository(repository);

        http.addFilterBefore(testFilter, UsernamePasswordAuthenticationFilter.class);

    }

    @Bean
    public PasswordEncoder passwordEncoder(){
        return NoOpPasswordEncoder.getInstance();
    }
}

默认控制器:

@Controller
public class App {

    /**
     * 
     */
    @GetMapping(value = "/{path:[^\\.]*}")
    public String index() {
        return "index";
    }

}

简单测试API:

@RestController
public class TestApi {

    @GetMapping(value = "/api/get-csrf", consumes = {"application/json"})
    public CsrfTestResponse getCsrf(HttpServletRequest request){

        TestingLombokWorks testingLombokWorks = new TestingLombokWorks("Mama", "Mama is the best!");

        CsrfToken token = new HttpSessionCsrfTokenRepository().loadToken(request);
        CsrfTestResponse testResponse = new CsrfTestResponse();
        testResponse.setCsrfHeaderName(token.getHeaderName());
        testResponse.setCsrfParameterName(token.getParameterName());
        testResponse.setCsrfToken(token.getToken());
        testResponse.setTestingLombokWorks(testingLombokWorks);
        return testResponse;
    }

    @GetMapping("/api/welcome")
    public String publicResource(){
        return "Welcome API!";
    }

    @GetMapping("/api/login-success")
    public String loginSuccess() {
        return "Login Successful!";
    }

    @PostMapping("/api/post-test")
    public CsrfTestResponse postTest(HttpServletRequest request){

        CsrfToken token = new HttpSessionCsrfTokenRepository().loadToken(request);

        CsrfTestResponse testResponse = new CsrfTestResponse();
        testResponse.setCsrfHeaderName(token.getHeaderName());
        testResponse.setCsrfParameterName(token.getParameterName());
        testResponse.setCsrfToken(token.getToken());
        return testResponse;
    }

    @PostMapping("/login")
    public String testLogin(){
        return "Login Works?!";
    }

    @GetMapping("/auth-success")
    public String authSuccess(){
        return "Auth Success!";
    }

    @GetMapping("/auth-failure")
    public String authFailure(){
        return "Auth Failure!";
    }

}

要测试的过滤器

@Component
public class TestFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        System.out.println("Username?: " + request.getParameter("username"));
        System.out.println("Password?: " + request.getParameter("password"));
        filterChain.doFilter(request, response);
    }
}

反应前端:

import React from 'react'
import { useForm } from 'react-hook-form'

export default function Login(props) {

    const {register, handleSubmit, formState} = useForm();

    return (
        <form onSubmit={handleSubmit((data, event) => {

                event.preventDefault();

                let formData = new FormData();
                formData.append("username", data.username);
                formData.append("password", data.password);

                let body = new URLSearchParams(formData);

                let str = `username=${data.username}&password=${data.password}`;

                console.log(str);

                fetch('/login', {
                    method: 'POST',
                    credentials: 'include',
                    headers: {
                        'Content-Type': 'application/json',
                        Accept: '*/*',
                        [props.csrfHeaderName]: props.csrfToken
                    },
                    body: str
                })
                .then(response => response.text())
                .then(data => console.log(data))
                .catch(error => console.log(error));
               
            })}
        >
            <legend>Login</legend>
            <div>
                <input 
                    {
                        ...register("username", {
                            value: "",
                            required: true,
                        })
                    } 
                    type="text"
                    placeholder='Username: '
                />
            </div>
            <div>
                <input 
                    {
                        ...register("password", {
                            value: "",
                            required: true,
                        })
                    } 
                    type="password"
                    placeholder='Password: '
                />
            </div>
            <button type='submit'>Login</button>
        </form>
    )
}

我创建了一个测试过滤器只是为了测试,它证明了我的观点。当使用正常的服务器端呈现的默认Spring Boot表单提交表单时,请求可以工作,但当使用JavaScript发送请求时,请求失败。

当我看到登录请求的日志时,我根本没有看到它到达那个endpoint,而是直接转到 /auth-failure:

Username?: null
Password?: null
Username?: null
Password?: null
2022-04-04 15:53:45.960 DEBUG 20327 --- [nio-8080-exec-5] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to com.example.springreact.api.TestApi#authFailure()
2022-04-04 15:53:45.960 DEBUG 20327 --- [nio-8080-exec-5] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to com.example.springreact.api.TestApi#authFailure()
2022-04-04 15:53:45.960 DEBUG 20327 --- [nio-8080-exec-5] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to com.example.springreact.api.TestApi#authFailure()
2022-04-04 15:53:45.961 DEBUG 20327 --- [nio-8080-exec-5] o.s.web.servlet.DispatcherServlet        : GET "/auth-failure", parameters={}
2022-04-04 15:53:45.961 DEBUG 20327 --- [nio-8080-exec-5] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to com.example.springreact.api.TestApi#authFailure()
2022-04-04 15:53:45.961 DEBUG 20327 --- [nio-8080-exec-5] m.m.a.RequestResponseBodyMethodProcessor : Using 'text/plain', given [*/*] and supported [text/plain, */*, text/plain, */*, application/json, application/*+json, application/json, application/*+json]
2022-04-04 15:53:45.961 DEBUG 20327 --- [nio-8080-exec-5] m.m.a.RequestResponseBodyMethodProcessor : Writing ["Auth Failure!"]
2022-04-04 15:53:45.962 DEBUG 20327 --- [nio-8080-exec-5] o.s.web.servlet.DispatcherServlet        : Completed 200 OK

我想知道进行这种简单身份验证的最佳方法是什么。

我尝试了一切可以想象的方法,我用ObjectMapper尝试了JSON post body,它抛出了IOException,没有任何有意义的错误消息,

Java有时会令人沮丧,甚至过时,当你最终花10个小时去做简单的事情时。

共有1个答案

曹恩
2023-03-14

Java有时会令人沮丧,甚至过时,当你最终花10个小时去做简单的事情时。

这不能说。只是你错过了很多重要的信息来理解它为什么会失败。

(1)

http.formLogin()
                .loginPage("/")
                .loginProcessingUrl("/login")
                .defaultSuccessUrl("/auth-success")
                .failureUrl("/auth-failure");

这是为了与Spring MVC一起使用,不在使用@RestController所指示的Rest Spring Boot应用范围内。Spring登录表单旨在交付html表单,系统使用MVC模式进行授权。它不符合您的需求,因为您有另一个前端和另一个框架来执行简单的ajax调用。

第二)

在你明白为什么(1)失败后,你必须决定你可以走哪条路。您可以切换到基本身份验证,也可以尝试实现更复杂的东西,比如JWT身份验证令牌。如果你搜索得足够多,可以找到更多的解决方案,但我想说最常见的就是我在这里提到的那些。

调整应用程序以使用其中一种安全结构,然后更新前端以进行此类调用,您应该会没事。

 类似资料:
  • 我看到这个错误在chrome控制台" 加载http://localhost:4001/api/v1/sessions/new失败:对预试请求的响应没有通过权限改造检查:请求的资源上没有“访问-控制-允许-起源”标头。因此不允许访问“起源http://localhost:4044”。响应有HTTP状态代码405。 如果我只是尝试和卷曲完全相同的API调用它工作正常: 为什么它与CURL一起工作,而不

  • 当我试图通过邮递员发送时,我有一个请求正在正常工作。我试图实现相同的使用代码,我面临一个错误。 我正在使用的代码- 我得到的错误是- org.springframework.web.client.HttpClientErr异常$未授权: 401未授权 但《邮递员》中同样的作品也有同样的细节。 我想说明一下 后端API不在我手里。 clientid,clientSecret,access_token

  • 请问大佬们,如何通过axios,去实现post请求,并且请求参数为JOSN格式传入body内?我这样的写法有什么错误吗?请求就提示跨域报错

  • 问题内容: 我在用JavaScript抓取API挣扎。当我尝试通过获取将某些内容发布到服务器时,请求正文为空数组。但是,当我使用Postman时,它可以工作…这是我在NodeJS中的服务器代码: 这是我的客户: 问题是在服务器端,req.body为空。有人能帮我吗?谢谢 ! 问题答案: 问题是 从文档中 … 防止该方法成为HEAD,GET或POST之外的任何其他内容, 并且 阻止 标头成为 简单标

  • 每当页面尝试提交2MB或更大的文件时,都会发生此错误。但是参数在帖子里!我已经检查了Chrome开发工具。有人知道这个错误吗?Springboot 2.0.3。释放

  • 可以在模板中直接使用$Request对象,直接输入它的属性或调用它的大部分方法,但只支持方法的第一个参数; // 调用Request对象的get方法 传入参数为id {$Request.get.id} // 调用Request对象的param方法 传入参数为name {$Request.param.name} // 调用Request对象的param方法 传入参数为post.post_title