Issue
My application is a springboot application (2.4.0) and I am having some issues with spring security. I'm sure its some simple detail but I just cant see it.
Basically whats happening is I read a users "Role" from the database and add it to the GrantedAuthorities list in the UserDetails. I then am securing my method in the controller using @Secured. I have verified that the principal has the GrantedAuthority... but @Secured denies them every time.
Configuration:
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
I am using JWT so I have a filter to create the user based on it:
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
try {
String jwt = parseJwt(request);
if (jwt != null && jwtUtils.validateJwtToken(jwt)) {
String username = jwtUtils.getUserNameFromJwtToken(jwt);
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
} catch (Exception e) {
logger.error("Cannot set user authentication: {}", e);
}
filterChain.doFilter(request, response);
}
My userDetailsService populates the GrantedAuthorities:
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("User Not Found with username: " + username));
Collection<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
authorities.add(new SimpleGrantedAuthority(user.getStatus().name()));
return UserDetailsImpl.builder().username(user.getUsername()).guid(user.getGuid()).name(user.getName())
.password(user.getPassword()).status(user.getStatus()).authorities(authorities).build();
And finally my secured method:
@PutMapping("/auth/activate")
@Secured("ROLE_VALIDATING")
public ResponseEntity<?> activateAccount(Authentication authentication) {
accountService.activateAccount(authentication.getName());
return ResponseEntity.ok().body(new MessageResponse("Account Activated"));
}
So to troubleshoot I went ahead and removed the @Secured and verified the method executes without any problems. I then left @Secured off and added this to the method to confirm it has the grantedauthority:
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null && auth.getAuthorities().stream().anyMatch(a -> a.getAuthority().equals("VALIDATING"))) {
LOG.info("I has the role!");
}
Sure enough when I execute the code it updates my record and logs out "I has the role!". So it would appear that A) User is being populated correctly by the userDetailsService. B) The Authentication in the SecurityContextHolder is being properly set. C) The user does in fact have the right GrantedAuthority.
But @Secured still denies access.
When I enable debug logging I see the following when the request is denied.
Initializing Spring DispatcherServlet 'dispatcherServlet' Initializing Servlet 'dispatcherServlet' Completed initialization in 3 ms Securing PUT /api/private/auth/activate Set SecurityContextHolder to empty SecurityContext Authorized filter invocation [PUT /api/private/auth/activate] with attributes [authenticated] Secured PUT /api/private/auth/activate Failed to authorize ReflectiveMethodInvocation: public org.springframework.http.ResponseEntity net.theblackchamber.map.dashboard.controller.PrivateController.activateAccount(org.springframework.security.core.Authentication); target is of class [net.theblackchamber.map.dashboard.controller.PrivateController] with attributes [ROLE_VALIDATING] Responding with 403 status code Cleared SecurityContextHolder to complete request Securing PUT /error Set SecurityContextHolder to empty SecurityContext Set SecurityContextHolder to anonymous SecurityContext Secured PUT /error Cleared SecurityContextHolder to complete request
Solution
See comment by Ken S.
The GrantedAuthority "VALIDATING" you are testing is just an authority, but you are testing for "ROLE_VALIDATING" in the annotation. I'm not sure if @Secured accepts authorities, all examples I found were roles. You might want to test @PreAuthorize("hasAuthority('VALIDATING')") instead or rename the authority to "ROLE_VALIDATING" if you want to use roles. – Ken S 24 mins ago
For those following the change I made was either switching to hasAuthority and the @PreAuthorize annotation. OR I changed my userDetailsService:
Collection<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
authorities.add(new SimpleGrantedAuthority(String.format("ROLE_%s", user.getStatus())));
Related: Spring Security/Spring Boot - How to set ROLES for users
Answered By - Seamus
Answer Checked By - David Marino (JavaFixing Volunteer)