Reputation: 151
I have upgraded my Spring Boot Project to Spring Boot 3.
I've also updated the WebSecurityConfig, it now looks like that:
// imports...
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class CustomWebSecurityConfig {
final UserDetailsServiceImpl userDetailsService;
private final AuthEntryPointJwt unauthorizedHandler;
private final PasswordEncoder passwordEncoder;
@Bean
public AuthTokenFilter authenticationJwtTokenFilter() {
return new AuthTokenFilter();
}
@Bean
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
authProvider.setUserDetailsService(userDetailsService);
authProvider.setPasswordEncoder(passwordEncoder);
return authProvider;
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception {
return authConfig.getAuthenticationManager();
}
/**
* Sets up a chain of antmatchers specifying what permissions and roles have access to which resources.
*
* @param http Injected HttpSecurity object
* @return Chain of Security filters
* @throws Exception Currently throws general exception
*/
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable()
// https://stackoverflow.com/questions/74447778/spring-security-in-spring-boot-3
.authorizeHttpRequests(requests -> requests.requestMatchers("/api/auth/**").permitAll()
.requestMatchers("/api/test/**").permitAll()
.requestMatchers("/").permitAll()
.requestMatchers("/index.html").permitAll()
.requestMatchers("/favicon.ico").permitAll()
.requestMatchers("/main.js").permitAll()
.requestMatchers("/polyfills.js").permitAll()
.requestMatchers("/runtime.js").permitAll()
.requestMatchers("/styles.css").permitAll()
.requestMatchers("/vendor.css").permitAll()
.requestMatchers("/assets/**").permitAll()
.requestMatchers("/error").permitAll()
.requestMatchers("/**").permitAll()
.anyRequest().authenticated());
http.exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.authenticationProvider(authenticationProvider());
http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
This is a sample Endpoint with @PreAuthorize:
// imports...
@RestController
@RequestMapping("/api/test")
public class TestController {
@GetMapping("/all")
public String allAccess() {
return "Public Content.";
}
@GetMapping("/user")
@PreAuthorize("hasRole('USER') or hasRole('MODERATOR') or hasRole('ADMIN')")
public String userAccess() {
return "User Content.";
}
@GetMapping("/mod")
@PreAuthorize("hasRole('MODERATOR')")
public String moderatorAccess() {
return "Moderator Board.";
}
@GetMapping("/admin")
@PreAuthorize("hasRole('ADMIN')")
public String adminAccess() {
return "Admin Board.";
}
}
The tests i have written for this usecase fail partially, because a logged in user has access to all the Endpoint, but only has the "USER"-Role per default. These 2 tests fail:
@Test
@DisplayName("Give user no token and forbid access")
@WithMockUser(roles = "USER")
void givenUserToken_whenGetSecureRequest_thenForbidden() throws Exception {
mockMvc.perform(get("/api/test/mod"))
.andExpect(status().isForbidden());
}
@Test
@DisplayName("Give user no token and forbid access v.2")
@WithMockUser(roles = "USER")
void givenUserToken_whenGetSecureRequest_thenForbidden2() throws Exception {
mockMvc.perform(get("/api/test/admin"))
.andExpect(status().isForbidden());
}
I read something about @EnableMethodSecurity, but i haven't found a way to use it and fix @PreAuthorize not working
Upvotes: 13
Views: 13633
Reputation: 8383
According to the documentation:
EnableMethodSecurity
In Spring Security 5.6, we can enable annotation-based security using the @EnableMethodSecurity annotation on any @Configuration instance.
Simply add this annotation on top of your @Configuration
class and it should work.
Example:
@Configuration
@EnableMethodSecurity
public class SecurityConfig {
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeHttpRequests((authorize) -> authorize
.anyRequest().authenticated()
).httpBasic(withDefaults());
return http.build();
}
}
This improves upon @EnableGlobalMethodSecurity in a number of ways. @EnableMethodSecurity:
Uses the simplified AuthorizationManager API instead of metadata sources, config attributes, decision managers, and voters. This simplifies reuse and customization.
Favors direct bean-based configuration, instead of requiring extending GlobalMethodSecurityConfiguration to customize beans
Is built using native Spring AOP, removing abstractions and allowing you to use Spring AOP building blocks to customize
Checks for conflicting annotations to ensure an unambiguous security configuration
Complies with JSR-250
Enables @PreAuthorize, @PostAuthorize, @PreFilter, and @PostFilter by default
https://docs.spring.io/spring-security/reference/servlet/authorization/method-security.html
Upvotes: 21