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

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

ただいまの
回答率

87.48%

spring securityを使用してファイルアップロード

解決済

回答 2

投稿

  • 評価
  • クリップ 1
  • VIEW 1,766

score 20

SprngBootを使用してWebアプリケーションを作成しています。
ファイルアップロード機能を実装したいのですが、SpringSecurityを使用していることで403エラーとなってしまいます。

エラーメッセージ:
There was an unexpected error (type=Forbidden, status=403).

MultipartFilterを登録することで解決できるとドキュメントにはあるのですが解決できませんでした…
https://spring.pleiades.io/spring-security/site/docs/current/reference/html5/#servlet-csrf-considerations-multipart

問題解決のためにサンプルアプリを作成しなおして動作を確認してみましたが、SpringSecurityを導入するとやはりエラーになります。

わかるかたいらっしゃれば回答よろしくお願いいたします。

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org/">
<head>
<meta charset="UTF-8">
<title>ファイルアップロードのサンプル</title>
</head>
<body>
<p th:if="${error}" th:text="${error}">エラーメッセージ</p>
<form action="/upload" method="post" enctype="multipart/form-data">
   アップロードするJPEGファイルを選択してください<br>
   <input type="file" name="file" accept="image/jpeg">
   <input type="submit">
</form>
<p>
   <img src="/images/picture.jpg">
</p>
<p><a href="/test">テストページへ</a></p>
</body>
</html>
package com.example.demo;

import java.io.File;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

@SpringBootApplication.java
public class Sample05Application {

    public static void main(String[] args) {
        SpringApplication.run(Sample05Application.class, args);
    }

    @Bean
       public AppConfig appConfig() {
           // Webアプリケーションはアプリケーション実行ディレクトリとは別のディレクトリで実行される
           // 起動時のディレクトリをAppConfigのimageDirフィールドに保持しておく
           File imageDir = new File("images");
           imageDir = imageDir.getAbsoluteFile();

            // imagesフォルダがなかったら作成する
            if (!imageDir.exists()) {
               imageDir.mkdir();
           }

            AppConfig appConfig = new AppConfig();
           appConfig.setImageDir(imageDir);
           return appConfig;
       }
}
package com.example.demo;

import java.io.File;
import java.io.IOException;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;

@Controller
public class Sample05Controller {

    @Autowired
       private AppConfig appConfig;

        @GetMapping("/")
       public String index() {
           return "index";
       }

        @PostMapping("/upload")
       public String upload(@RequestParam MultipartFile file, Model model) {
           // ファイルを選択せずにフォームを送信したかの確認
           if (file.isEmpty()) {
               model.addAttribute("error", "ファイルを指定してください");
               return "index";
           }

            // アップロードされたファイルを指定の名前で保存
           File dest = new File(appConfig.getImageDir(), "picture.jpg");
           try {
            file.transferTo(dest);
        } catch (IllegalStateException e) {
            // TODO 自動生成された catch ブロック
            e.printStackTrace();
        } catch (IOException e) {
            // TODO 自動生成された catch ブロック
            e.printStackTrace();
        }
           return "index";
       }

        @GetMapping("/test")
        public String test() {
            return "testpage";
        }

}
package com.example.demo;

import java.io.File;

import lombok.Data;

@Data
public class AppConfig {
    private File imageDir;
}
package com.example.demo;

import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    public void configure(WebSecurity web) {
        web.ignoring().antMatchers("/css/**", "/html/**", "/images/**");
    }

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

        http.authorizeRequests().antMatchers("/").permitAll();
    }
}


■以下、multipartfilter設定。
②も試しましたがダメでした…

package com.example.demo;

import java.util.EnumSet;

import javax.servlet.DispatcherType;
import javax.servlet.ServletContext;

import org.springframework.core.annotation.Order;
import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;
import org.springframework.web.multipart.support.MultipartFilter;

public class SecurityWebApplicationInitializer extends AbstractSecurityWebApplicationInitializer{
//    @Override …⓶
//    protected void beforeSpringSecurityFilterChain(ServletContext servletContext) {
//        System.out.println("secチェック");
//        final MultipartFilter multipartFilter = new MultipartFilter();
//        servletContext.addFilter("multipartFilter", multipartFilter)
//                .addMappingForUrlPatterns(
//                        EnumSet.allOf(DispatcherType.class), false, "/*");
//    }
    @Override
    protected void beforeSpringSecurityFilterChain(ServletContext servletContext) {
        insertFilters(servletContext, new MultipartFilter());
    }
}


■spring.web を使用しているためweb.xmlがないので以下を記述
参考:http://taktos.hatenablog.com/entry/2014/05/16/Spring_Security%E3%81%AECSRF%E5%AF%BE%E7%AD%96%E3%81%A8Servlet_3.0%E3%81%AEMultipart%E3%82%92Java_Config%E3%81%A7%E8%A8%AD%E5%AE%9A%E3%81%99%E3%82%8B

package com.example.demo;

import javax.servlet.MultipartConfigElement;
import javax.servlet.ServletRegistration.Dynamic;

import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

public class WebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    @Override
    protected void customizeRegistration(Dynamic registration) {
        System.out.println("webチェック");
        registration.setMultipartConfig(new MultipartConfigElement(
                System.getProperty("java.io.tmpdir"), -1, -1, 1024 * 1024));
    }

    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[] {AppConfig.class};
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class[] {WebConfig.class};
    }

    @Override
    protected String[] getServletMappings() {
        return new String[] {"/"};
    }
}
  • 気になる質問をクリップする

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

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

    クリップを取り消します

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

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

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

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

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

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

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

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

    質問の評価を下げる

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

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

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

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

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

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

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

    詳細な説明はこちら

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

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

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

回答 2

checkベストアンサー

+1

Controllerの @PostMapping("/upload") に対するSpringSecurityの設定がないので「認証が必要」なURLになります。

SpringSecurityの設定を拝見した限りでは、ログイン(認証)画面の設定がありませんので、/upload も permitAllのURLに追加するか、ログインの設定をしてください。

類似する質問が直近でありますので参考に。

https://teratail.com/questions/293177

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2020/09/28 22:10

    画像アップロードのHTMLテンプレートはindex.htmlになります!(質問ではindexと表示されています)
    wyq2020様の回答のおかげでいったんはアップロードできたのですが、可能であればmultipartfilterの設定方法も教えていただけるとありがたいです…。

    キャンセル

  • 2020/09/28 22:41

    なるほど、CSRFチェックをわざわざ入れないと動作しない原因があります。

    action="/upload" ではなく、th:action="@{/upload}" とすると、自動的にCSRFトークンの隠し入力パラメータが生成されます。これは https://github.com/thymeleaf/thymeleaf-extras-springsecurity/ の機能です。

    multipart-Filterは特に不要だと思いますが(本質問中にも見つかっていませんが…)もしその場合は別途質問を挙げた方が良いかもしれません。

    キャンセル

  • 2020/09/30 22:48

    あぁ…thymeleafの機能が使えてなかったんですね!
    無事アップロードできました。
    multipartfilterは、multipartのデータを送信する際にはspringsecurity filterchainの前に設定する必要があるとドキュメントで読んだのでこの設定とばかり思っていました。
    深読みして深読みした結果、初歩的なお話でお恥ずかしい。
    本当に助かりました!ありがとうございますm(_ _)m

    キャンセル

+1

csrfの設定が必要です。

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org/">
<head>
<meta charset="UTF-8">
<title>ファイルアップロードのサンプル</title>
</head>
<body>
<p th:if="${error}" th:text="${error}">エラーメッセージ</p>
<form action="/upload" method="post" enctype="multipart/form-data">
   アップロードするJPEGファイルを選択してください<br>
   <input type="file" name="file" accept="image/jpeg">
   <input type="hidden"
          th:name="${_csrf.parameterName}"
          th:value="${_csrf.token}"/>
   <input type="submit">
</form>
<p>
   <img src="/images/picture.jpg">
</p>
<p><a href="/test">テストページへ</a></p>
</body>
</html>


spring security

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2020/09/28 22:07

    おぉ…できました。トークンを実際に含めるやり方だとこんなにあっさりできるんですね。
    試していませんでした(汗)
    推奨アプローチとされるMultipartFilterの方は分かりますでしょうか…?

    キャンセル

  • 2020/09/29 00:00

    Spring bootを使用すると、MultipartFilterが必要ないと思います。

    キャンセル

  • 2020/09/29 00:44

    MultipartFilterの設定は何のためでしょうか?

    キャンセル

  • 2020/09/30 22:54

    spring bootを使うとmultipartfilterは必要ないんですね…。調べるポイントが間違っていました。。
    csrfトークンが原因ともっと早く気づけていれば…
    本当に助かりました。この度はありがとうございました!m(_ _)m

    キャンセル

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

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

関連した質問

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