前提・実現したいこと
プログラミング初学者です。Spring Boot と Vueで、検索結果をaxiosの非同期通信で返す機能を作成しています。
発生している問題・エラーメッセージ
axiosでpostメソッドを実行する時だけ405エラーとなってしまいます。
Resolved [org.springframework.web.HttpRequestMethodNotSupportedException: Request method 'POST' not supported]
試したこと
web上で原因を調べ、axiosのpostメソッド時にcsrfトークンが入っていないことが原因と考えました。現在作成中のアプリではSpring Securityを使用しており、CSRF対策が有効になっているためです。
htmlにformタグ<form th:action="@{/api/search}">を追加し、vueのコンポーネントを削除してpostしたところ、jsonが返ってくることを確認しました。
Controllerクラスが正しくレスポンスしたことを確認したあと、htmlのmeta属性からcsrfトークンの値を取得してaxiosのヘッダーに追加してpostしましたが、変わらず405エラーとなりました。
axiosのヘッダーにcsrfトークンを設定する方法をご教示いただければと思います。
フロント側でなくControllerクラスに問題がある場合もご指摘いただけますと幸いです。
該当のソースコード
PostIndex.html
1<!DOCTYPE HTML> 2<html xmlns:th="https://thymeleaf.org"> 3<head> 4 <title>投稿一覧</title> 5 <meta charset="UTF-8"> 6 <meta name="_csrf" th:content="${_csrf.token}"/> 7 <meta name="_csrf_header" th:content="${_csrf.headerName}"/> 8 <script src="https://unpkg.com/vue@next"></script> 9 <script src="https://unpkg.com/axios/dist/axios.min.js"></script> 10</head> 11 12<body> 13<h1>投稿一覧</h1> 14<div id="testApp"> 15 <div> 16 <select id="category" name="category" v-model="category"> 17 <option th:each="list:${lists}" 18 th:value="${list.postId}" th:text="${list.postName}"></option> 19 </select> 20 <select id="status" name="status" v-model="status"> 21 <option th:each="status:${statusList}" 22 th:value="${status.statusId}" th:text="${status.statusName}"></option> 23 </select> 24 <input type="text" id="keyword" name="keyword" v-model="keyword"> 25 <button v-on:click="search">検索</button> 26 </div> 27 <table> 28 <tr v-for="(value,key) in PostRecords"> 29 <td> {{value.nickName}} </td> 30 <td> {{value.content}} </td> 31 <td> {{value.createAt}} </td> 32 </tr> 33 </table> 34</div> 35<script th:src="@{/testApp.js}"></script> 36</body> 37</html>
testApp.js
1var testApp = { 2 data(){ 3 return{ 4 category:[], 5 status:[], 6 keyword:"", 7 PostRecords:[] 8 } 9 }, 10 methods: { 11 getAll(){ 12 axios.get('/api/search') 13 .then(response => { 14 this.PostRecords = response.data 15 }) 16 .catch(error =>{ 17 console.log(error) 18 }) 19 }, 20 search(){ 21 var tokenValue = document.head.querySelector("[name=_csrf]").content; 22 var headerName = document.head.querySelector("[name=_csrf_header]").content; 23 var header={ 24 'token': tokenValue, 25 'headerName':headerName 26 } 27 let params = new FormData() 28 params.append('category', this.category) 29 params.append('status',this.status) 30 params.append('keyword',this.keyword) 31 32 axios.post('/api/search',params,{header:header}) 33 .then(response => { 34 this.PostRecords = response.data 35 }) 36 .catch(error =>{ 37 console.log(error) 38 }) 39 } 40 }, 41 mounted(){ 42 this.getAll() 43 } 44} 45Vue.createApp(testApp).mount('#testApp');
Controllerクラス
1@Controller 2public class PostController { 3 4 private final PostService postService; 5 6 public PostController(PostService postService) { 7 this.postService = postService; 8 } 9 10 @GetMapping("/index/post") 11 String showPostIndex(Model model) { 12 model.addAttribute("lists", PostCategory.values()); 13 model.addAttribute("statusList", StatusList.values()); 14 return "Post/PostIndex"; 15 } 16 17}
RestControllerクラス
1@RestController 2public class RestPostRecordController { 3 4 private final PostService postService; 5 6 public RestPostRecordController(PostService postService) { 7 this.postService = postService; 8 } 9 10 @RequestMapping(value = "/api/search",method= {RequestMethod.GET,RequestMethod.POST}, 11 produces = "application/json; charset=utf-8") 12 @ResponseBody 13 public String getSearchPostRecord(@RequestParam(value="category",required=false) int[]category, 14 @RequestParam(value="status",required=false) int[]status, 15 @RequestParam(value="keyword",required=false) String text) 16 throws JsonProcessingException{ 17 String jsonMsg = null; 18 List<PostRecord>records = postService.searchPostRecord(category,status,text); 19 20 ObjectMapper mapper = new ObjectMapper(); 21 jsonMsg = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(records); 22 23 return jsonMsg; 24 } 25 26} 27
Configクラス
1@Configuration 2@EnableWebSecurity 3@EnableGlobalMethodSecurity(prePostEnabled=true) 4public class WebSecurityConfig extends WebSecurityConfigurerAdapter { 5 6 private final SuccessHandler successHandler; 7 8 private final AccountUserDetailsService accountUserDetailsService; 9 10 private final PasswordEncoder passwordEncoder; 11 12 public WebSecurityConfig(SuccessHandler successHandler, 13 AccountUserDetailsService accountUserDetailsService, 14 PasswordEncoder passwordEncoder) { 15 this.successHandler = successHandler; 16 this.accountUserDetailsService = accountUserDetailsService; 17 this.passwordEncoder = passwordEncoder; 18 } 19 20 @Override //全体に対するセキュリティ設定を行う 21 public void configure(WebSecurity web) throws Exception { 22 web.ignoring().antMatchers("/resources/**"); 23 } 24 25 @Override //URLごとに異なるセキュリティ設定を行う 26 protected void configure(HttpSecurity http) throws Exception { 27 http.formLogin() 28 .loginPage("/login") 29 .loginProcessingUrl("/authenticate") 30 .usernameParameter("username") 31 .passwordParameter("password") 32 .successHandler(successHandler) 33 .failureUrl("/login?error") 34 .permitAll(); 35 http.logout() 36 .logoutUrl("/logout") 37 .logoutSuccessUrl("/login?logout") 38 .permitAll(); 39 http.authorizeRequests()// アクセス権限の設定 40 .antMatchers("/registration","/regist").permitAll() 41 .antMatchers("/resetpassword","/updatePassword").permitAll() 42 .antMatchers("/admin/**").hasAuthority("ROLE_ADMIN") 43 .antMatchers("/index/**").hasAuthority("ROLE_USER") 44 .anyRequest().authenticated() 45 .and() 46 .exceptionHandling().accessDeniedPage("/accessdenied"); 47 } 48 49 @Override //認証方法の実装の設定を行う 50 protected void configure(AuthenticationManagerBuilder auth) throws Exception { 51 auth.userDetailsService(accountUserDetailsService).passwordEncoder(passwordEncoder); 52 } 53 54}
補足情報(FW/ツールのバージョンなど)
Spring Boot 2.6.3

回答1件
あなたの回答
tips
プレビュー
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。