[JAVA] How to make CsrfRequestDataValueProcessor and original RequestDataValueProcessor coexist on Spring Boot

When Spring Security is used on Spring Boot, the CsrfRequestDataValueProcessor (class for outputting the CSRF token value to the hidden of the form) provided by Spring Security is applied, and the project-specific RequestDataValueProcessor cannot be applied. This is ... Spring Boot Issue (gh-4676) seems to be discussing how to deal with it, but it's still a conclusion. Does not seem to appear.

Workaround

In Issue ...

Is introduced as a workaround.

Create your own AutoConfigure class and try to solve it

This time, I'll try to create my own AutoConfigure class and use @ AutoConfigureAfter to override the Bean definition in SecurityAutoConfiguration.class. In addition, the project-specific RequestDataValueProcessor outputs a common parameter (_s = system date and time epoch milliseconds) to avoid caching in the URL in the form and ʻa` elements.

Create your own AutoConfigure class

The class created this time does not consider versatility at all, and adopts an implementation method that applies the project-specific RequestDataValueProcessor implementation to the application while retaining the functions provided by CsrfRequestDataValueProcessor.

Note:

If you consider versatility ... It is better to create a Composite class of RequestDataValueProcessor that delegates processing to multiple RequestDataValueProcessors.

package com.example;

import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.web.servlet.support.csrf.CsrfRequestDataValueProcessor;
import org.springframework.web.servlet.support.RequestDataValueProcessor;
import org.springframework.web.util.UriComponentsBuilder;

@Configuration
@AutoConfigureAfter(SecurityAutoConfiguration.class) //★★★ Here points[1]
public class MyRequestDataValueProcessorAutoConfiguration {
	@Bean
	RequestDataValueProcessor requestDataValueProcessor() { //★★★ Here points[2]
		CsrfRequestDataValueProcessor csrfRequestDataValueProcessor = new CsrfRequestDataValueProcessor();
		return new RequestDataValueProcessor() {
			@Override
			public String processAction(HttpServletRequest request, String action, String httpMethod) {
				return addCachePreventQueryParameter(csrfRequestDataValueProcessor.processAction(request, action, httpMethod));
			}

			@Override
			public String processFormFieldValue(HttpServletRequest request, String name, String value, String type) {
				return csrfRequestDataValueProcessor.processFormFieldValue(request, name, value, type);
			}

			@Override
			public Map<String, String> getExtraHiddenFields(HttpServletRequest request) {
				return csrfRequestDataValueProcessor.getExtraHiddenFields(request);
			}

			@Override
			public String processUrl(HttpServletRequest request, String url) {
				return addCachePreventQueryParameter(csrfRequestDataValueProcessor.processUrl(request, url));
			}

			private String addCachePreventQueryParameter(String url) {
				return UriComponentsBuilder.fromUriString(url).queryParam("_s", System.currentTimeMillis()).build().encode()
						.toUriString();
			}
		};
	}

}

[1] By allowing your own AutoConfigure class to be called after SecurityAutoConfiguration, you can overwrite the Bean definition provided by Spring Boot (Spring Security) with your own AutoConfigure class. [2] In order to override the bean definition, the bean name must be " requestDataValueProcessor ".


Basically, it delegates the process to CsrfRequestDataValueProcessor and implements it to add a cache avoidance parameter to the return values of processAction and processUrl.

In order to recognize the created configuration class as AutoConfigure class, create src / main / resources / META-INF / spring.factories and set the property key value of ʻorg.springframework.boot.autoconfigure.EnableAutoConfiguration`. You need to specify the class you created in.

src/main/resources/META-INF/spring.factories


# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.MyRequestDataValueProcessorAutoConfiguration

Creating a View (Thymeleaf)

Let's create a simple View for operation verification.

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:th="http://www.thymeleaf.org">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
    <title>Demo</title>
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.css" type="text/css"/>
</head>
<body>

<div class="container">

    <h1>Demo</h1>


    <div id="demoForm">
        <form action="index.html" method="post" class="form-horizontal"
              th:action="@{/demo}" th:object="${demoForm}"> <!--★★★ Here points[3] -->
            <div class="form-group">
                <label for="title" class="col-sm-1 control-label">Title</label>
                <div class="col-sm-10">
                    <input type="text" class="form-control" id="title" th:field="*{title}"/>
                    <span class="text-error" th:errors="*{title}">error message</span>
                </div>
            </div>
            <div class="form-group">
                <div class="col-sm-offset-1 col-sm-10">
                    <button type="submit" class="btn btn-default">Create</button>
                </div>
            </div>
        </form>
    </div>

    <a href="index.html" th:href="@{/demo}">Reload</a> <!--★★★ Here points[4] -->

</div>
</body>
</html>

[3] The URL specified in th: action is processed by the RequestDataValueProcessor # processAction method. [4] The URL specified in th: href is processed by the RequestDataValueProcessor # processUrl method.


When the above HTML is executed on Spring Boot, the following HTML will be generated. The URL of the " form element's ʻaction attribute" and the "ʻa element's href attribute "has a cache avoidance parameter, and the hidden for linking the CSRF token value in the form element. You can see that the element is output.

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <title>Demo</title>
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.css" type="text/css" />
</head>
<body>

<div class="container">

    <h1>Demo</h1>


    <div id="demoForm">
        <form action="/demo?_s=1488222896002" method="post" class="form-horizontal"> <!--★★★ Cache avoidance parameter is given-->
            <div class="form-group">
                <label for="title" class="col-sm-1 control-label">Title</label>
                <div class="col-sm-10">
                    <input type="text" class="form-control" id="title" name="title" value="" />
                    
                </div>
            </div>
            <div class="form-group">
                <div class="col-sm-offset-1 col-sm-10">
                    <button type="submit" class="btn btn-default">Create</button>
                </div>
            </div>
        <input type="hidden" name="_csrf" value="b952a1bf-222a-4a99-b889-6878935a5784" /></form> <!--★★★ Hidden of CSRF token value linkage is given-->
    </div>

    <a href="/demo?_s=1488222896002">Reload</a> <!--★★★ Cache avoidance parameter is given-->

</div>
</body>
</html>

Creating a Controller

RequestDataValueProcessor # processUrl is linked with the redirect function of Spring MVC, so let's check the operation there as well.

package com.example;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;

@RequestMapping("/demo")
@Controller
public class DemoController {

	@ModelAttribute
	DemoForm setUpForm() {
		return new DemoForm();
	}

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

	@PostMapping
	String submit(DemoForm form) {
		return "redirect:/demo"; //★★★ Here points[5]
	}

	static class DemoForm {
		private String title;

		public String getTitle() {
			return title;
		}

		public void setTitle(String title) {
			this.title = title;
		}
	}

}

[5] The URL specified when using the Spring MVC redirect function is processed by the RequestDataValueProcessor # processUrl method.


If you check the redirect URL with your browser's developer tools, you can see that the cache avoidance parameter is properly assigned.

HTTP/1.1 302
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
Location: http://localhost:8080/demo?_s=1488223437196 #★★★ Cache avoidance parameter is given
Content-Language: en-US
Content-Length: 0
Date: Mon, 27 Feb 2017 19:23:57 GMT

Summary

By creating my own AutoConfigure class, I was able to make CsrfRequestDataValueProcessor and my own RequestDataValueProcessor coexist. Also, by using RequestDataValueProcessor, it was confirmed that common parameters can be added to URLs handled under Spring MVC. However ... When handling URLs outside of Spring MVC management (for example, when handling the method of HttpServletResponse directly), it is not under the jurisdiction of RequestDataValueProcessor, so common parameters cannot be assigned. If you want to add common parameters to URLs not managed by Spring MVC, you need to create HttpServletResponseWrapper to add common parameters, wrap it in the class created by Servlet Filter, and call the subsequent processing. I will.

Recommended Posts

How to make CsrfRequestDataValueProcessor and original RequestDataValueProcessor coexist on Spring Boot
How to make Spring Boot Docker Image smaller
How to call and use API in Java (Spring Boot)
How to set Spring Boot + PostgreSQL
How to use ModelMapper (Spring boot)
How to make a hinadan for a Spring Boot project using SPRING INITIALIZR
How to split Spring Boot message file
Add spring boot and gradle to eclipse
How to display characters entered in Spring Boot on a browser and reference links [Introduction to Spring Boot / For beginners]
How to use MyBatis2 (iBatis) with Spring Boot 1.4 (Spring 4)
How to make rbenv recognize OpenSSL on WSL
How to use built-in h2db with spring boot
How to use Spring Boot session attributes (@SessionAttributes)
How to add a classpath in Spring Boot
How to bind to property file in Spring Boot
[Spring Boot] How to refer to the property file
Spring Boot --How to set session timeout time
Plans to support JDK 11 for Eclipse and Spring Boot
How to set Dependency Injection (DI) for Spring Boot
How to write a unit test for Spring Boot 2
How to implement authentication process by specifying user name and password in Spring Boot
How to create a Spring Boot project in IntelliJ
[Spring Boot] How to create a project (for beginners)
How to make JavaScript work on a specific page
How to hover and click on Selenium DOM elements
How to use CommandLineRunner in Spring Batch of Spring Boot
Deploy the Spring Boot project to Tomcat on XAMPP
How to boot by environment with Spring Boot of Maven
Attempt to SSR Vue.js with Spring Boot and GraalJS
(Ruby on Rails6) How to create models and tables
How to use OpenCV 4 on Android and view camera live view
How to change application.properties settings at boot time in Spring boot
How to Install Elixir and Phoenix Framework on Ubuntu 20.04 LTS
Until INSERT and SELECT to Postgres with Spring boot and thymeleaf
I tried to make Java Optional and guard clause coexist
Connect to database with spring boot + spring jpa and CRUD operation
How to run React and Rails on the same server
8 things to insert into DB using Spring Boot and JPA
How to control transactions in Spring Boot without using @Transactional
Try Spring Boot from 0 to 100.
Spring Boot on Microsoft Azure
Java --How to make JTable
How to deploy on heroku
Introduction to Spring Boot ① ~ DI ~
Introduction to Spring Boot Part 1
Try Spring Boot on Mac
[Rails] How to make seed
How to install and use Composer on an ECS instance on Ubuntu 16.04
Minimal configuration to run Spring Boot application on Azure Web Apps
How to create your own Controller corresponding to / error with Spring Boot
How to set and use profile in annotation-based Configuration in Spring framework
How to install and configure the monitoring tool "Graphite" on Ubuntu
How to load a Spring upload file and view its contents
How to place and share SwiftLint config files on the server
How to use Lombok in Spring
How to use StringBurrer and Arrays.toString.
How to deploy jQuery on Rails
How to unit test Spring AOP
gRPC on Spring Boot with grpc-spring-boot-starter
How to "hollow" View on Android
How to use Spring Data JDBC