Home > Software engineering >  JWT (java) : roles in my token is not considered by HTTP-Request with error 403
JWT (java) : roles in my token is not considered by HTTP-Request with error 403

Time:08-10

I 'm coding a back-end/APIRest, part of an personal app. I'm trying to set an authentication by JWT, with roles : ADMIN and USER. because i want to filter the following endpoint only for role ADMIN : /users/all But when i send my http request from Insomnia App, with localhost:7777/users/all i have an error 403 forbidden. I don't know why. My code repository is : here (Current branch : ImplementingJWTSecurity) .

Just bellow, this is a generated JWT by my sended request, you can check it on jwt.io :

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJrZW50MSIsInJvbGVzIjoiQURNSU4iLCJleHAiOjE2NjExNTUxNDUyOTUsImlhdCI6MTY2MDA3NTE0NTI5NX0.RGJzJVkM6bB0g6YlK6FFMzbjjTZ8qPqGf9pfHMeKHfyDV_OM9lF1w8SDzys3SC-iNdBMgXMjVP972URcISwJ_A

/CONCERNED ENDPOINT CONTROLLER/

@RestController
@RequestMapping("/users")
public class AppUserController {

    @RolesAllowed("ADMIN")
    @GetMapping("/all")
    public ResponseEntity<List<AppUserListDto>> getAllAppUsers() {
        logger.info("GET /users/all");
            return new ResponseEntity<List<AppUserListDto>>(appUserServiceImpl.getAllUsers(), HttpStatus.OK);
        }

    }

/SECURITY CONFIGURATION/

@EnableWebSecurity
@Configuration
@EnableGlobalMethodSecurity(
        prePostEnabled = true,
        securedEnabled = true,
        jsr250Enabled = true)
public class SecurityConfiguration {

    @Autowired
    RestAuthenticationEntryPoint restAuthenticationEntryPoint;

    @Autowired
    JwtFilter jwtFilter;    

    @Bean
    SecurityFilterChain web(HttpSecurity http) throws Exception {
        http.csrf().disable().exceptionHandling().authenticationEntryPoint(restAuthenticationEntryPoint).and()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
                .authorizeRequests()
                .antMatchers("/login").permitAll()
                .antMatchers("/users/add").permitAll()
                .antMatchers("/users/all").hasRole("ADMIN")
                .anyRequest().authenticated();

        http.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);
        return http.build();
    }

    @Bean
    PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public RoleHierarchy roleHierarchy() {
        RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
        String hierarchy = "ADMIN > USER";
        roleHierarchy.setHierarchy(hierarchy);
        return roleHierarchy;
    }
    @Bean
    public DefaultWebSecurityExpressionHandler webSecurityExpressionHandler() {
        DefaultWebSecurityExpressionHandler expressionHandler = new DefaultWebSecurityExpressionHandler();
        expressionHandler.setRoleHierarchy(roleHierarchy());
        return expressionHandler;
    }

}

/MY USER DETAILS SERVICE/

@Service
public class MyUserDetailService implements UserDetailsService  {

    @Autowired
    AppUserRepository appUserRepository;

    @Autowired
    RoleRepository roleRepository;

    @Override
    public UserDetails loadUserByUsername(String appUsername) throws UsernameNotFoundException {
        AppUserEntity appUser = appUserRepository.findByAppUsername(appUsername);
        if (appUser == null) {
            return new org.springframework.security.core.userdetails.User(
              " ", " ", true, true, true, true, 
              getAuthorities(Arrays.asList(
                roleRepository.findByRoleName("USER"))));
        }
        return new org.springframework.security.core.userdetails.User(
            appUser.getAppUsername(), appUser.getPassword(), true, true, true, 
            true, getAuthorities(appUser.getRoles()));
    }

    private Collection<? extends GrantedAuthority> getAuthorities(
            Collection<Role> roles) {

        return getGrantedAuthorities(getPrivileges(roles));
    }

    private List<String> getPrivileges(Collection<Role> roles) {

        List<String> privileges = new ArrayList<>();
        List<Privilege> collection = new ArrayList<>();
        for (Role role : roles) {
            privileges.add(role.getRoleName());
            collection.addAll(role.getPrivileges());
        }
        for (Privilege item : collection) {
            privileges.add(item.getName());
        }
        return privileges;
    }

    private List<GrantedAuthority> getGrantedAuthorities(List<String> privileges) {
        List<GrantedAuthority> authorities = new ArrayList<>();
        for (String privilege : privileges) {
            authorities.add(new SimpleGrantedAuthority(privilege));
        }
        return authorities;
    }
}

/JWT UTILS/

@Component
public class JwtUtils {

    @Autowired
    AppUserRepository appUserRepository;

    long JWT_VALIDITY = 5 * 60 * 60 * 60;

    @Value("${jwt.secret}")
    String secret;

    private final Logger logger = LoggerFactory.getLogger(JwtUtils.class);

    public String generateToken(Authentication authentication) {

        Map<String, Object> claims = new HashMap<>();
        claims.put("roles",authentication.getAuthorities().stream().map(role -> role.getAuthority()).findFirst().orElseThrow(NoSuchElementException::new));
        claims.put("iat",new Date(System.currentTimeMillis()));
        claims.put("exp", new Date(System.currentTimeMillis()   JWT_VALIDITY * 1000));
        claims.put("sub", authentication.getName());

        Map<String, Object> headerJwt = new HashMap<>();
        headerJwt.put("alg", "HS512");
        headerJwt.put("typ", "JWT");

        //TODO Later : Header, claims, jwt... will be Base64urlEncoded
        return Jwts.builder()
                .setHeader(headerJwt)
                .setClaims(claims)
                .signWith(SignatureAlgorithm.HS512, secret).compact();
    }

    public Authentication getAuthentication(String token) {
        Claims claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
        AppUserEntity appUser = appUserRepository.findByAppUsername(claims.getSubject());
        logger.info("THIS IS THE SUBJECT FROM CLAIMS : {}", claims.getSubject());

        Collection<? extends GrantedAuthority> authorities = getAuthorities(appUser.getRoles());

        User principal = new User(claims.getSubject(), "", authorities);

        return new UsernamePasswordAuthenticationToken(principal, token, authorities);
    }

    private Collection<? extends GrantedAuthority> getAuthorities(
            Collection<Role> roles) {

        return getGrantedAuthorities(getPrivileges(roles));
    }

    private List<String> getPrivileges(Collection<Role> roles) {

        List<String> privileges = new ArrayList<>();
        List<Privilege> collection = new ArrayList<>();
        for (Role role : roles) {
            privileges.add(role.getRoleName());
            collection.addAll(role.getPrivileges());
        }
        for (Privilege item : collection) {
            privileges.add(item.getName());
        }
        return privileges;
    }

    private List<GrantedAuthority> getGrantedAuthorities(List<String> privileges) {
        List<GrantedAuthority> authorities = new ArrayList<>();
        for (String privilege : privileges) {
            authorities.add(new SimpleGrantedAuthority(privilege));
        }
        return authorities;
    }

}

/JWT CONTROLLER/

@RestController
public class JwtController {

    @Autowired
    JwtUtils jwtUtils;
    
    @Autowired
    AuthenticationManagerBuilder authenticationManagerBuilder;

    @PostMapping("/login")
    public ResponseEntity<?> createAuthToken(@RequestBody JwtRequest jwtRequest) {
        Authentication authentication = logUser(jwtRequest.getAppUsername(), jwtRequest.getPassword());
        String jwt = jwtUtils.generateToken(authentication);
        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.add(AUTHORIZATION_HEADER, "Bearer "   jwt);
        Object principal = authentication.getPrincipal();
         return new ResponseEntity<>(new JwtResponse(((User) principal).getUsername()), httpHeaders, HttpStatus.OK);
    }

    public Authentication logUser(String appUsername, String password) {
        Authentication authentication = authenticationManagerBuilder.getObject()
                .authenticate(new UsernamePasswordAuthenticationToken(appUsername, password));

        SecurityContextHolder.getContext().setAuthentication(authentication);
        return authentication;
    }
}

/JWT FILTER/

@Component
public class JwtFilter extends OncePerRequestFilter {

    @Autowired
    JwtUtils jwtUtils;

    public static final String AUTHORIZATION_HEADER = "Authorization";

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
            throws ServletException, IOException {
        String jwt = resolveToken(request);
        if (StringUtils.hasText(jwt)) {
            Authentication authentication = jwtUtils.getAuthentication(jwt);
            SecurityContextHolder.getContext().setAuthentication(authentication);
        }
        chain.doFilter(request, response);
    }

    private String resolveToken(HttpServletRequest request) {
        String bearerToken = request.getHeader(AUTHORIZATION_HEADER);
        if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
            return bearerToken.substring(7);
        }
        return null;
    }
}

CodePudding user response:

Your decoded token looks like this :

{
  "sub": "kent1",
  "roles": "ADMIN",
  "exp": 1661155145295,
  "iat": 1660075145295
}

As far as I can see, the problem is in the user roles. Your roles should be in this format:

ROLE_ADMIN, or ROLE_CUSTOMER, etc.. (notice prefix ROLE_)

With @RolesAllowed("ADMIN")annotation being set, this part :

 .antMatchers("/users/all").hasRole("ADMIN")

is not needed.

BTW, in your JwtUtils class I noticed this line of code:

claims.put("roles",authentication.getAuthorities());

With this line of code, you are setting this

Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();

list as a value for your "roles" key in map. Since this list will contain whole GrantedAuthority objects, that's not what Spring expects. Assuming that your role table looks like this:

 --------- ------------ 
| id_role | role_name  |
 --------- ------------ 
|       1 | ROLE_ADMIN |
|       2 | ROLE_USER  |
 --------- ------------ 

You can do something like this :

Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
        List<String> roles = new ArrayList<>();
        for(GrantedAuthority authority: authorities){
            if(authority.getAuthority().startsWith("ROLE")){
                roles.add(authority.getAuthority());
            }
        }
        claims.put("roles",roles);

and that will resolve problem that I just described.

  • Related