質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

89.13%

Spring securityでログイン認証の機能を使うと@PutMappingのアノテーションが反応しなくなる

解決済

回答 1

投稿 編集

  • 評価
  • クリップ 1
  • VIEW 2,306

spring_boot

score 12

SpringでCRUD機能を作り、Spring securityのデフォルト機能を使ってログイン認証機能を取り付けていました。すると、それまでは@PutMappingにきっちりと分岐していたのに急にエラーを発生するようになりました。

エラー内容:

There was an unexpected error (type=Method Not Allowed, status=405).
Request method 'POST' not supported

@PutMappingをつけたメソッドを通っていないです。アノテーションを@PostMappingに変えると反応します。
hiddenタグで

<input type="hidden" name="_method" value="PUT">


を持たせておけば@PutMappingへ分岐されると書籍には書かれていますが違うのでしょうか。
それともSpringSecurityをフィルターに通すことで条件が変わるのでしょうか。
ドキュメントを読み漁りましたがそもそも@PutMappingの使用法すら書かれておりませんでした。

以下、該当するファイルです。
DemoSecurityConfig.java

package com.example.demo.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configuration
@EnableWebSecurity
public class DemoSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication().withUser("john").password("{noop}test123").roles("EMPLOYEE");
        auth.inMemoryAuthentication().withUser("mary").password("{noop}test123").roles("MANAGER");
        auth.inMemoryAuthentication().withUser("susan").password("{noop}test123").roles("ADMIN");
    }    

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http.authorizeRequests()
                .anyRequest().authenticated()
            .and()
            .formLogin()
                .loginPage("/showMyLoginPage")
                .loginProcessingUrl("/authenticateTheUser")
                .permitAll()
            .and()
            .logout().permitAll();

    }

}

ログインは成功します。POSTで新規インサートも行えます。

TestController.java

package com.example.demo.controllers;

import java.util.List;
import java.util.Optional;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.servlet.ModelAndView;

import com.example.demo.Instructor;
import com.example.demo.InstructorDetail;
import com.example.demo.InstructorForm;
import com.example.demo.service.InstructorService;

@Controller
@RequestMapping("/test")
public class TestController {

    @Autowired
    InstructorService service;

    //INDEX
    @GetMapping
    public ModelAndView test(
            //@ModelAttributeでInstructorFormは自動的にインスタンス化される 
            //htmlに反映させるにはmav.addObjectが必須
            @ModelAttribute("InstructorForm") InstructorForm instructorForm,
            ModelAndView mav) {

        instructorForm.setNewInstructor(true);
        mav.addObject("instructorForm", instructorForm);
        List<Instructor> list = service.findAll();
        mav.addObject("list", list);
        mav.addObject("title", "メンバー一覧");

        mav.setViewName("test");
        return mav;
    }

    //Before UPDATE
    @GetMapping(value = "/{id}")//編集ページ
    public ModelAndView showUpdate(
            @ModelAttribute("InstructorForm") InstructorForm instructorForm,
            @PathVariable Integer id,
            ModelAndView mav) {
        Optional<InstructorForm> form = service.getInstructorForm(id);

        if(!form.isPresent()) {
            mav.setViewName("redirect:/test");
            return mav;
            //return new ModelAndView("redirect:/test")
        }

        mav.addObject("instructorId", id);
        mav.addObject("instructorForm", form.get());
        List<Instructor> list = service.findAll();
        mav.addObject("list", list);
        mav.addObject("title", "更新フォーム");

        mav.setViewName("test");
        return mav;
    }


    //INSERT
    @PostMapping
    @Transactional(readOnly = false)
    public ModelAndView insert2(
            @Validated InstructorForm instructorForm,//ヴァリデーションはフォームクラスに対して行う
            BindingResult result,
            ModelAndView mav) {
        Instructor instructor = makeInstructor(instructorForm);
        //redirect、失敗したらそのままHTML表示
        if(!result.hasErrors()) {
            service.save(instructor);
            mav.setViewName("redirect:/test");
        }else {
            instructorForm.setNewInstructor(true);
            mav.addObject("instructorForm", instructorForm);
            List<Instructor> list = service.findAll();
            mav.addObject("list", list);
            mav.addObject("title", "メンバー一覧2");
            mav.setViewName("test");
        }
        return mav;
    }

    //UPDATE
    @PutMapping("/{id}")
    @Transactional(readOnly = false)
    public ModelAndView update(
            @PathVariable Integer id,
            @ModelAttribute("InstructorForm") InstructorForm instructorForm,
            ModelAndView mav) {
        Instructor instructor = makeInstructor(id, instructorForm);
        System.out.println(instructor);
        service.save(instructor);
        mav.setViewName("redirect:/test" + "/" + id);
        return mav;
    }

    //DELETE
    @DeleteMapping("/{id}")
    @ResponseStatus(HttpStatus.NO_CONTENT)
    @Transactional(readOnly = false)//削除の場合必須
    public ModelAndView delete(
            @PathVariable Integer id,
            ModelAndView mav) {
        System.out.println("けすよ");
        service.deleteById(id);
        mav.setViewName("redirect:/test");
        return mav;
    }


    private Instructor makeInstructor(InstructorForm iForm) {
        InstructorDetail iD = new InstructorDetail(iForm.getYoutubeChannel(), iForm.getHobby());
        Instructor i = new Instructor(iForm.getFirstName(), iForm.getLastName(), iForm.getEmail());
        i.setInstructorDetail(iD);
        return i;
    }

    private Instructor makeInstructor(int id,InstructorForm iForm) {
        InstructorDetail iD = new InstructorDetail(iForm.getYoutubeChannel(), iForm.getHobby());
        Instructor i = new Instructor(id, iForm.getFirstName(), iForm.getLastName(), iForm.getEmail());
        i.setInstructorDetail(iD);
        return i;
    }


}


PutMappingとDeleteMappingが反応しません。
There was an unexpected error (type=Method Not Allowed, status=405).
Request method 'POST' not supported
上記エラーを返します。これはSpring Securityを取り付ける前は動作してました。

Test.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1 th:text="${title}">ようこそ</h1>
<ul>
    <li th:each="error : ${#fields.detailedErrors()}"
    th:text="${error.message}" />
</ul>
<form method="post" action="#" th:action="@{/test}" th:if="${instructorForm.isNewInstructor}" th:object="${instructorForm}">
    <label>FirstName</label>
    <input type="text" name="firstName" th:value="*{firstName}" />
    <label>LastName</label>
    <input type="text" name="lastName" th:value="*{lastName}" />
    <label>Email</label>
    <div th:if="${#fields.hasErrors('email')}" 
    th:errors="*{email}"></div>
    <div th:if="${#fields.hasErrors('validEmail')}" 
    th:errors="*{validEmail}"></div>
    <input type="text" name="email" th:value="*{email}" />
    <div th:if="${#fields.hasErrors('youtubeChannel')}" 
    th:errors="*{youtubeChannel}"></div>
    <label>YoutubeChannel</label>
    <input type="text" name="youtubeChannel" th:value="*{youtubeChannel}" />
    <label>Hobby</label>
    <input type="text" name="hobby" th:value="*{hobby}" />
    <input type="submit" value="送信">
</form>
<form method="POST" th:action="@{/test/{id}(id=${instructorId})}" th:unless="${instructorForm.isNewInstructor}" th:object="${instructorForm}">
    <label>FirstName</label>
    <input type="text" name="firstName" th:value="*{firstName}" />
    <label>LastName</label>
    <input type="text" name="lastName" th:value="*{lastName}" />
    <label>Email</label>
    <input type="text" name="email" th:value="*{email}" />
    <label>YoutubeChannel</label>
    <input type="text" name="youtubeChannel" th:value="*{youtubeChannel}" />
    <label>Hobby</label>
    <input type="text" name="hobby" th:value="*{hobby}" />
    <input type="hidden" name="_method" value="PUT">
    <input type="submit" value="変更">
</form>
<table>
<tr th:each="obj : ${list}">
    <td th:text=${obj.id}></td>
    <td th:text=${obj.firstName}></td>
    <td th:text=${obj.email}></td>
    <td th:text=${obj.instructorDetail.hobby}></td>
    <td><a type="button" th:href="@{/test/{id}(id=${obj.id})}">編集</a></td>
    <td><form method="POST" th:action="@{/test/{id}(id=${obj.id})}">
        <input type="hidden" name="_method" value="DELETE">
        <input type="submit" value="削除">
    </form>
</tr>
</table>
<form action="#" th:action="@{/logout}" method="POST">
    <input type="submit" value="Logout" />
</form>
</body>
</html>

このエラーから抜け出せずに困っています。
どなたかわかる方はいらっしゃいますでしょうか。
また、該当のドキュメント(@PutMapping)時にhtmlから送信するhiddenの内容などが記載されたドキュメントの場所をご存じであればそれも知りたいです。どこを探しても見つかりませんでした。
よろしくお願いいたします

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

checkベストアンサー

+1

CORSにより、PUT,DELETEのメソッドが許可されていないためではないでしょうか?

【参考】

直接は関係ないですが @PostMapping,@PutMapping,@DeleteMappingをつけるメソッドは、呼び出し側をAJAX呼び出しにして、APIにするほうがviewの呼び出しやそれに伴うModelへの値セットが少なくなって良い気がします。(もちろん仕様によるものなので、APIが正解ということはないですが)

//INSERT
@PostMapping
@ResponseBody
@Transactional(readOnly = false)
public List<Instructor> insert2(@Validated InstructorForm instructorForm,
         BindingResult result) {
  // ... 省略
  return service.findAll();
}
//UPDATE
@PutMapping("/{id}")
@ResponseBody
@Transactional(readOnly = false)
public Instructor update(@PathVariable Integer id,
         @ModelAttribute("InstructorForm") InstructorForm instructorForm) {
  // ... 省略
  return service.save(instructor);
}
//DELETE
@DeleteMapping("/{id}")
@ResponseBody
@ResponseStatus(HttpStatus.NO_CONTENT)
@Transactional(readOnly = false)//削除の場合必須
public void delete(@PathVariable Integer id) {
  // ... 省略
  service.deleteById(id);
}

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/09/24 23:22

    なんと!
    大変貴重なご指摘ありがとうございます!

    〉呼び出し側をAJAX呼び出しにして、APIにする

    そうなんですね。参考書やDBpressの記事を基準にしていたので自身ではたどり着けませんでした。ありがとうございました!

    キャンセル

  • 2018/09/24 23:34

    じゃあついでに、上記に書いた@ResponseBodyについて解説された記事のURL貼っておきます。

    http://javatechnology.net/spring/responsebody-object-json/

    キャンセル

  • 2018/09/25 09:19

    確認しました。ありがとうございます。
    知り合いのエンジニアがWebAPIとしてSpringを使ったと言っていたので
    このことかと納得しました。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 89.13%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

同じタグがついた質問を見る