I attempted an upgrade to Spring Security 6 today and after spending the whole day trying to get this working, still no success. Yes, I have read the official documentation, looked at various examples, such as https://docs.spring.io/spring-security/reference/servlet/configuration/java.html#_multiple_httpsecurity_instances or https://github.com/spring-projects/spring-security-samples/blob/main/servlet/java-configuration/authentication/username-password/form/src/main/java/example/SecurityConfiguration.java, but all to no avail. This blog post unfortunately also did not help: https://spring.io/blog/2022/02/21/spring-security-without-the-websecurityconfigureradapter#comment-6027047044.
Now to my question. Before I converted my security configuration, it looked like this:
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
private DbUserService userService;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/admin/**").authenticated()
.antMatchers("/**").permitAll()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.defaultSuccessUrl("/admin")
.and()
.logout()
.permitAll();
}
public DaoAuthenticationProvider authProvider() {
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
authProvider.setUserDetailsService(userService);
authProvider.setPasswordEncoder(passwordEncoder());
return authProvider;
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authProvider());
}
}
After the attempted conversion, it looks like this:
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration {
private AuthenticationManager authenticationManager;
@Autowired
private DbUserService userService;
@Bean
public SecurityFilterChain formLoginFilterChain(HttpSecurity http) throws Exception {
AuthenticationManagerBuilder authenticationManagerBuilder = http.getSharedObject(AuthenticationManagerBuilder.class);
authenticationManagerBuilder.userDetailsService(userService);
authenticationManager = authenticationManagerBuilder.build();
http.authenticationManager(authenticationManager)
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.ALWAYS);
http.securityMatchers((matchers) -> matchers.requestMatchers("/admin/**"))
.authorizeHttpRequests(authorize -> authorize.anyRequest().authenticated())
.formLogin(Customizer.withDefaults());
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationManager authManager(final AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
The problem arises when I try to log in. In the previous version it worked fine, but now I keep on getting the error message:
Method 'POST' is not supported
.
And I can see why. For some reason it is trying to call the post method in my own LoginController, but of course there is no POST request method in there. There is only my GET method for displaying the form.
Controller:
@Controller
public class LoginController {
@GetMapping("/login")
public String login() {
return "login";
}
}
View:
<form th:action="@{/login}" method="post">
<div><label> User Name : <input type="text" name="username"/> </label></div>
<div><label> Password: <input type="password" name="password"/> </label></div>
<div><input type="submit" value="Sign In"/></div>
</form>
Is there a reason why it is going to my controller instead of the spring security one, like it used to? Is this a deliberate change in the new spring security 6?
======================================================
*** Additional information after accepted solution ***
======================================================
Thanks, it works now with the accepted solution. It even works together with my second security configuration for my /api/
endpoints. For completeness, I will post that here incase anyone finds it useful (hopefully it is a valid solution):
@Configuration
@EnableWebSecurity
public class ApiSecurityConfig {
@Value("${security.rest.apikey}")
private String restApiKey;
@Order(2)
@Bean
public SecurityFilterChain apiFilterChain(HttpSecurity http) throws Exception {
http.securityMatcher("/api/**")
.csrf(AbstractHttpConfigurer::disable)
.addFilterBefore(apiKeyAuthFilter(), BasicAuthenticationFilter.class)
.authorizeHttpRequests(request -> request
.requestMatchers("/api/**").authenticated());
return http.build();
}
private Filter apiKeyAuthFilter() {
return new OncePerRequestFilter() {
@Override protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String requestApiKey = request.getHeader("X-Api-Key");
if (!restApiKey.equals(requestApiKey)) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return;
}
Authentication authentication = new UsernamePasswordAuthenticationToken("apiUser", null, new ArrayList<>());
SecurityContextHolder.getContext().setAuthentication(authentication);
try {
filterChain.doFilter(request, response);
} finally {
SecurityContextHolder.clearContext();
}
}
};
}
}
You should to do some changes to get the same configuration before migration:
Current implementation of security layer:
@Configuration
@EnableWebSecurity
public class SecurityConfiguration {
@Autowired
private DbUserService userService;
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {\
http.authenticationManager(authenticationManager());
http.authorizeHttpRequests(request -> {
request.requestMatchers("/admin/**").authenticated();
request.requestMatchers("/**").permitAll();
});
http.formLogin(form -> {
form.loginPage("/login").permitAll();
form.defaultSuccessUrl("/admin");
});
http.logout(LogoutConfigurer::permitAll);
return http.build();
}
@Bean
public AuthenticationManager authenticationManager() {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setPasswordEncoder(passwordEncoder());
provider.setUserDetailsService(userService);
return new ProviderManager(provider);
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
By the way: @EnableGlobalMethodSecurity(prePostEnabled = true)
also was deprecate to @EnableMethodSecurity
, you should replace it but not in SecurityConfiguration
class. But in main
Something like this:
@SpringBootApplication
@EnableMethodSecurity
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
It should solve the problem
The problem arises when I try to log in. In the previous version it worked fine, but now I keep on getting the error message: Method 'POST' is not supported.