An error will occur when upgrading Spring Security in an application that includes JSESSIONID in the URL. I investigated the cause, so I will summarize it.
Well, why not include JSESSIONID in the URL now?
Some preparation is required to reproduce the event.
In the case of a browser that can use cookies, JSESSIONID is managed by using cookies. Change the settings of the Servlet container to force management by URL.
When using Spring Boot, it can be set by defining ServletContextInitializer
as Bean as follows.
@Bean
public ServletContextInitializer servletContextInitializer() {
return servletContext -> servletContext.setSessionTrackingModes(EnumSet.of(SessionTrackingMode.URL));
}
Spring Security has a default feature that disables URL Rewriting.
Change the settings in the class that inherits WebSecurityConfigurerAdapter
as shown below.
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.sessionManagement().enableSessionUrlRewriting(true);
}
}
URL Rewriting is not available on the login page provided by Spring Security by default, so create a login page.
login.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>login</title>
</head>
<body>
<form th:action="@{/login}" method="post">
<div>
<label>username: <input type="text" name="username"/></label>
</div>
<div>
<label>password: <input type="password" name="password"/></label>
</div>
<input type="submit" value="login"/>
</form>
</body>
</html>
In addition, create a Controller method to display this page.
@Controller
public class HelloController {
@GetMapping("/login")
public String login() {
return "login";
}
}
Finally, add the settings. By the way, the user name and password used for login are specified.
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/login").permitAll().anyRequest().authenticated()
.and()
.formLogin().loginPage("/login")
.and()
.sessionManagement().enableSessionUrlRewriting(true);
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("user").password("{noop}user").roles("ADMIN");
}
}
By default, DelegatingPasswordEncoder
is used, so the password must have a prefix. Since it is plaintext this time, {noop}
is added.
The research on DelegatingPasswordEncoder
is summarized below.
https://qiita.com/d-yosh/items/bb52152318391e5e07aa
This also creates an html and Controller method.
hello.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>Hello</title>
</head>
<body>
<h1>I was able to log in</h1>
</body>
</html>
@Controller
public class HelloController {
@GetMapping("/")
public String index() {
return "hello";
}
@GetMapping("/login")
public String login() {
return "login";
}
}
An error occurs when I start the application and access it. (To be exact, an error occurs and redirects to the error screen, but an error also occurs and the redirect loops.)
If you look at the log, you can see that RequestRefectedException
has occurred.
org.springframework.security.web.firewall.RequestRejectedException: The request was rejected because the URL contained a potentially malicious String ";"
Spring Security uses HttpFirewall
to check for requests, and one of its implementations, StrictHttpFirewall
, rejects URLs that contain semicolons.
If you include the JSESSIONID in the URL, a semicolon is added to the URL, which is rejected by StrictHttpFirewall
and throws an exception.
The HttpFirewall
used by default differs depending on the version of Spring Security, and in the past, DefaultHttpFirewall
was used.
This class does not reject a request if the URL contains a semicolon.
This time, when I listed the version, the error started to occur because the default HttpFirewall
changed.
You can change the setting of StrictHttpFirewall
to allow semicolons.
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
//Various omissions ...
@Override
public void configure(WebSecurity web) throws Exception {
StrictHttpFirewall firewall = new StrictHttpFirewall();
firewall.setAllowSemicolon(true);
web.httpFirewall(firewall);
}
}
Change the setting to use DefaultHttpFirewall
.
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
//Various omissions ...
@Override
public void configure(WebSecurity web) throws Exception {
DefaultHttpFirewall firewall = new DefaultHttpFirewall();
web.httpFirewall(firewall);
}
}
Action 1 or 2 can be performed before accessing, and the login screen can be displayed.
You can log in with username: user and password: user.
By the way, the JSESSIONID changes before and after login because Spring Security's session fixation attack countermeasures are enabled. https://docs.spring.io/spring-security/site/docs/5.1.6.RELEASE/reference/htmlsingle/#ns-session-fixation
Sessions should not be managed by URL.
Recommended Posts