BAEL-4792 Stateless REST API and CSRF (#11398)

* Scan package for controller
Migrate deprecated Spring config

* BAEL-4792: enable CSRF + sample REST API request

* Adjust CSRF logs
This commit is contained in:
Benjamin Caure 2021-11-04 21:13:22 +01:00 committed by GitHub
parent 41298ffcf7
commit 502372a1de
8 changed files with 96 additions and 19 deletions

View File

@ -1,18 +1,20 @@
package com.baeldung.spring;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
import org.springframework.web.servlet.view.JstlView;
@EnableWebMvc
@Configuration
public class MvcConfig extends WebMvcConfigurerAdapter {
@ComponentScan(basePackages = { "com.baeldung.spring" })
public class MvcConfig implements WebMvcConfigurer {
public MvcConfig() {
super();
@ -22,8 +24,6 @@ public class MvcConfig extends WebMvcConfigurerAdapter {
@Override
public void addViewControllers(final ViewControllerRegistry registry) {
super.addViewControllers(registry);
registry.addViewController("/anonymous.html");
registry.addViewController("/login.html");

View File

@ -0,0 +1,31 @@
package com.baeldung.spring;
import javax.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.ResponseEntity;
import org.springframework.security.web.csrf.CsrfToken;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/rest")
public class RestController {
private static final Logger LOGGER = LoggerFactory.getLogger(RestController.class);
@GetMapping
public ResponseEntity<Void> get(HttpServletRequest request) {
CsrfToken token = (CsrfToken) request.getAttribute("_csrf");
LOGGER.info("{}={}", token.getHeaderName(), token.getToken());
return ResponseEntity.ok().build();
}
@PostMapping
public ResponseEntity<Void> post(HttpServletRequest request) {
// Same impl as GET for testing purpose
return this.get(request);
}
}

View File

@ -7,6 +7,7 @@ import org.springframework.security.config.annotation.authentication.builders.Au
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;
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
@Configuration
@EnableWebSecurity
@ -21,11 +22,11 @@ public class SecSecurityConfig extends WebSecurityConfigurerAdapter {
protected void configure(final AuthenticationManagerBuilder auth) throws Exception {
// @formatter:off
auth.inMemoryAuthentication()
.withUser("user1").password("user1Pass").roles("USER")
.withUser("user1").password("{noop}user1Pass").roles("USER")
.and()
.withUser("user2").password("user2Pass").roles("USER")
.withUser("user2").password("{noop}user2Pass").roles("USER")
.and()
.withUser("admin").password("admin0Pass").roles("ADMIN");
.withUser("admin").password("{noop}admin0Pass").roles("ADMIN");
// @formatter:on
}
@ -33,11 +34,11 @@ public class SecSecurityConfig extends WebSecurityConfigurerAdapter {
protected void configure(final HttpSecurity http) throws Exception {
// @formatter:off
http
.csrf().disable()
.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()).and()
.authorizeRequests()
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/anonymous*").anonymous()
.antMatchers(HttpMethod.GET, "/index*", "/static/**", "/*.js", "/*.json", "/*.ico").permitAll()
.antMatchers(HttpMethod.GET, "/index*", "/static/**", "/*.js", "/*.json", "/*.ico", "/rest").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()

View File

@ -12,7 +12,7 @@
<!-- in order to debug some marshalling issues, this needs to be TRACE -->
<logger name="org.springframework.web.servlet.mvc" level="INFO" />
<logger name="org.springframework.security.web.csrf" level="DEBUG" />
<root level="INFO">
<appender-ref ref="STDOUT" />
</root>

View File

@ -1,11 +1,30 @@
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="security" uri="http://www.springframework.org/security/tags" %>
<html>
<head></head>
<head>
<script src="csrf.js"></script>
</head>
<body>
<h1>This is the body of the sample view</h1>
<div>
<h2>CSRF Testing</h2>
<div>
<span>CSRF Token: </span>
<span id="csrf-token"></span>
<input type="checkbox" id="include-csrf" name="include-csrf" checked /><label for="include-csrf">Include token</label>
</div>
<br/>
<div>
<button type="button" onclick="test('GET')">Test GET Request</button>&nbsp;
<button type="button" onclick="test('POST')">Test POST Request</button>
</div>
<br/>
<div><span>Request Result: </span><span id="csrf-result"></span></div><br/>
</div>
<h2>Roles</h2>
<security:authorize access="hasRole('ROLE_USER')">
This text is only visible to a user
<br/> <br/>
@ -22,5 +41,26 @@
<a href="<c:url value="/perform_logout" />">Logout</a>
<script language="javascript">
function test(method) {
const includeCsrfCheckbox = document.querySelector('#include-csrf');
const csrfResultDiv = document.querySelector('#csrf-result');
const request = includeCsrfCheckbox.checked === true ? csrfRequest(method) : noCsrfRequest(method);
request
.then((res) => csrfResultDiv.innerText = res.ok ? method + ' Success' : method + ' Failure: ' + res.status)
.catch((err) => csrfResultDiv.innerText = method + ' Failure: ' + err.toString());
}
function csrfRequest(method) {
return fetch('/rest', { headers: { 'X-XSRF-TOKEN': window.getCsrfToken() }, method });
}
function noCsrfRequest(method) {
return fetch('/rest', { method });
}
document.querySelector('#csrf-token').innerText = window.getCsrfToken();
</script>
</body>
</html>

View File

@ -0,0 +1,3 @@
window.getCsrfToken = () => {
return document.cookie.replace(/(?:(?:^|.*;\s*)XSRF-TOKEN\s*\=\s*([^;]*).*$)|^.*$/, '$1');
}

View File

@ -6,6 +6,7 @@
<meta name="theme-color" content="#000000">
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
<script src="%PUBLIC_URL%/csrf.js"></script>
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.

View File

@ -19,7 +19,8 @@ class Form extends Component {
const data = new FormData(this.form)
fetch(this.form.action, {
method: this.form.method,
body: new URLSearchParams(data)
body: new URLSearchParams(data),
headers: { 'X-XSRF-TOKEN': window.getCsrfToken() },
}).then(v => {
if(v.redirected) window.location = v.url
})