Senthuran
Senthuran

Reputation: 1837

@PreAuthorize returns 403

I have a following method in the controller

@GetMapping("/hello")
@PreAuthorize("hasRole('ADMIN')")
public String hello() {
    return "Hello " + JWTRequestFilter.UserClaim;
}

When a user who has the ADMIN role tries to access the /hello, 403 is returned. I have enabled the following in the websecurity class.

@EnableGlobalMethodSecurity(prePostEnabled = true)

Below is the JWT token.

eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJzZW50aHVyYW4iLCJSb2xlcyI6WyJBRE1JTiIsIlVTRVIiXSwiZXhwIjoxNTkzMDE0NDE5LCJpYXQiOjE1OTI5Nzg0MTl9.-7lTav3Nux8WVafUBGXjOxtXcE-r0fpfjb7wM7hrg6w

Even the JWT token has the role but still i'm getting 403. Does this preauthorize annotation see the role from the JWT or does it make a DB call and check the role of a user.Even I have used the @PreAuthrize annotation but still getting the same behaviour. How to resolve this 403. Below I have attached the JWTRequestFilter class.

public class JWTRequestFilter extends OncePerRequestFilter {

    @Autowired
    private MyUserDetailService userDetailService;

    @Autowired
    private JWTUtil jwtUtil;

    public static String UserClaim = "";

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
            throws ServletException, IOException {
        final String authorizationHeader = request.getHeader("Authorization");

        String username = null;
        String jwt = null;

        if(authorizationHeader != null && authorizationHeader.startsWith("Bearer ")){
            jwt = authorizationHeader.substring(7);
            username =  jwtUtil.extractUsername(jwt);
            UserClaim = username;
        }

        if(username != null && SecurityContextHolder.getContext().getAuthentication() == null){
            UserDetails userDetails= this.userDetailService.loadUserByUsername(username);
            if (jwtUtil.validateToken(jwt, userDetails)) {

                UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
                        userDetails, null, userDetails.getAuthorities());
                usernamePasswordAuthenticationToken
                        .setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
            }

        }
        chain.doFilter(request, response);
    }

}

This is how I'm generating the JWT token and how I set the roles.

public String generateToken(UserDetails userDetails) {
    Map<String, Object> claims = new HashMap<>();
    Set<String> Userroles = new HashSet<>();
    User user = userRepository.findByUsername(userDetails.getUsername());
    for(Role role:user.getRoles()){
        Userroles.add(role.getName());
    }
    claims.put("Roles",Userroles.toArray());
    return createToken(claims, userDetails.getUsername());
}

Upvotes: 1

Views: 3439

Answers (2)

Amine Aouffen
Amine Aouffen

Reputation: 176

Spring adds the prefix ROLE_ to the authorities. You can either implement a setter that appends the role prefix. Or another much simple way to do it would be to have a separate classe that implements GrantedAuthority interface

public class UserRole implements GrantedAuthority {
    private MyRole role;
    @Override
    public String getAuthority() {
        return "ROLE_" + role.toString();
    }
}
//MyRole is the enum with the different roles ADMIN,VIEWER,...

Upvotes: 1

Suggested Approach to identify the issue

        if(username != null && SecurityContextHolder.getContext().getAuthentication() == null){
            UserDetails userDetails= this.userDetailService.loadUserByUsername(username);
            if (jwtUtil.validateToken(jwt, userDetails)) {

                UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
                        userDetails, null, userDetails.getAuthorities());
                String authorities = userDetails.getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.joining());
                System.out.println("Authorities granted : " + authorities);
                usernamePasswordAuthenticationToken
                        .setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
            } else {
                System.out.println("Not Valid Token);
            }

        } else {
            System.out.println("No Token);
        }

Outcome: Token was valid but authorities were not loaded

Authorities granted : 

Suggested Solution Fix the MyUserDetailService to load Authorities in userDetails

Upvotes: 1

Related Questions