Error) Spring Security @PreAuthorize/@Secured NPE
This post was migrated from Tistory. You can find the original here.
Environment
Spring Boot 3.3.3
Spring Security 6.3.3
Problem
@PreAuthorize/@Secured - NullPointerException
1
2
3
4
5
6
7
8
//WebSecurityConfig.java
@Configuration
@EnableWebSecurity
@EnableMethodSecurity(prePostEnabled = true, securedEnabled = true)
@RequiredArgsConstructor
public class WebSecurityConfig {
....
}
1
2
3
4
5
6
7
8
9
10
11
//UserController.java
...userService injected..
@PostMapping(value = "/master/create", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
@PreAuthorize("hasRole(T(~~.UserRoleEnum).MASTER.getAuthority())")
private ResponseEntity<String> createUserByMaster(
@ModelAttribute @Valid UserCreateDto requestDto
) {
userService.createUserByMaster(requestDto);
return ResponseEntity.ok().body("success");
}
When trying to restrict access in a controller based on user role,
if you set up EnableMethodSecurity in the config roughly as shown above and apply the @PreAuthorize annotation, the injected userService turns out to be null.
+)
Whether you use an Enum object in hasRole or something else, as long as a String ends up going in:
- hasRole - A shortcut for hasAuthority that prefixes ROLE_ or whatever is configured as the default prefix
For example, if the value is “ROLE_MASTER”:
- hasRole(‘MASTER’) : O
- hasRole(‘ROLE_MASTER’) : O
- hasAuthority(‘MASTER’) : X
- hasAuthority(‘ROLE_MASTER’) : O
See Expressing Authorization with SpEL.
Solution
https://github.com/spring-projects/spring-security/issues/8879
1
2
3
4
5
6
7
8
9
10
11
12
//UserController.java
...userService injected..
@PostMapping(value = "/master/create", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
@PreAuthorize("hasRole(T(~~.UserRoleEnum).MASTER)")
public ResponseEntity<String> createUserByMaster( //<---public
@ModelAttribute @Valid UserCreateDto requestDto
) {
System.out.println("pass");
userService.createUserByMaster(requestDto);
return ResponseEntity.ok().body("success");
}
Security intercepts method calls by creating a proxy.
The controller method’s access modifier needs to be public for the security proxy to be able to reach it. Changing it from private to public fixes the problem.
If the method is private, proxying doesn’t work and the access control simply can’t be enforced.
So even a non-master user ends up passing through, and on top of that, an NPE occurs as well.
As mentioned in the issue thread discussion too, it would be much better if there were an error message that actually explained this case.
Looking at the stack trace, you can spot the security proxy in there, but there are so many other places an NPE could point to that it’s very easy to go chasing the wrong lead.