'Getting 401 Unauthorized Even when the user is authenticated (Spring Security)

I am working on a simple project with 2 defined roles Admin and User,The admin role has authority to view users by thier usernames but when I login as an admin and i hit the /admin/users/usernames/{username} I get 401 Unauthenticated you can see the request here postman request

WHATS THE PROBLEM WITH THE FOLLOWING IMPLEMENTATION?

**The login Method **

    @Override
    public ResponseEntity<User> login(String username, String password) {
        authenticate(username,password);
        User loginUser = findUserByUsername(username);
        UserPrinciple userPrinciple = new UserPrinciple(loginUser);
        HttpHeaders tokenHeader = getJwtToken(userPrinciple);
        return new ResponseEntity<>(loginUser,tokenHeader, HttpStatus.OK);
    }
    private void authenticate(String username, String password) {
        authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username,password));
    }

    private HttpHeaders getJwtToken(UserPrinciple userPrinciple) {
        String token= jwtTokenProvider.generateJWTToken(userPrinciple);
        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.add(SecurityConsts.JWT_TOKEN_HEADER,token);
        return httpHeaders;
    }

The User ReST Controlle

@RestController
@RequestMapping("/admin/users")
public class UserController {
    private UserService userService;
    @Autowired
    public UserController(UserService userService) {
        this.userService = userService;
    }

    @GetMapping("/")
    public List<User> getUsers() {
        return userService.getUsers();
    }

    @GetMapping("/emails/{email}")
    public User findUserByEmail(@PathVariable String email) {
        return userService.findUserByEmail(email);
    }

    @GetMapping("/usernames/{username}")
    public User findUserByUsername(@PathVariable String username) {
        return userService.findUserByUsername(username);
    }

    @JsonView(BodyView.BasicUser.class)
    @PostMapping("/register")
    public User addUser(@RequestBody User user) throws UsernameExistsException {
        return userService.addUser(user);
    }
    @JsonView(BodyView.BasicUser.class)
    @PostMapping("/login")
    public ResponseEntity<User> login(@RequestBody User user) {
        return userService.login(user.getUsername(), user.getPassword());
    }
}

POM.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.1</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>FM6-App</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>FM6-App</name>
    <description>FM6-App</description>
    <properties>
        <java.version>11</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.4</version>
        </dependency>
        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.19.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi</artifactId>
            <version>3.17</version>
        </dependency>

        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml</artifactId>
            <version>3.17</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

The Web Security Configurer Adapter
The public URLS:

public static final String[] PUBLIC_URLS ={"/users/admin/login/**","/admin/users/register/**","/demandes/**","/criteres/**"};
@Configuration
@EnableWebSecurity
@ComponentScan
@EnableGlobalMethodSecurity(
        prePostEnabled = true,
        securedEnabled = true,
        jsr250Enabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private JWTAuthorizationFilter jwtAuthorizationFilter;
    @Autowired
    private JWTAuthenticationEntryPoint jwtAuthenticationEntryPoint;
    @Autowired
    private JWTAccessDenielHandler jwtAccessDenielHandler;
    @Autowired
    private UserService userService;
    @Autowired
    private PasswordEncoder passwordEncoder;


    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .cors()
                .and()
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
                .antMatchers("/admin/users/login/**").permitAll()
                .antMatchers(SecurityConsts.PUBLIC_URLS).permitAll()
                .antMatchers("/admin/**").hasAnyAuthority("ROLE_ADMIN")
                .antMatchers("/utilisateur/**").hasAnyAuthority("ROLE_USER")

//                .anyRequest().authenticated()
                .and()
                .exceptionHandling().accessDeniedHandler(jwtAccessDenielHandler)
                .authenticationEntryPoint(jwtAuthenticationEntryPoint)
                .and()
                .addFilterBefore(jwtAuthorizationFilter, UsernamePasswordAuthenticationFilter.class);
    }
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userService).passwordEncoder(passwordEncoder);
    }

    @Bean(name = "authenticationManager")
    @Override
    public  AuthenticationManager authenticationManagerBean() throws Exception{
        return super.authenticationManagerBean();
    }

JWT Authorization Filter

@Component
public class JWTAuthorizationFilter extends OncePerRequestFilter {
    @Autowired
    private JWTTokenProvider jwtTokenProvider;
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        if(request.getMethod().equalsIgnoreCase(SecurityConsts.OPTION_HTTP_METHOD)){
            response.setStatus(SecurityConsts.OK.value());
        }else if(request.getRequestURI().equals("/users/login")){
            filterChain.doFilter(request,response);
        }
        else {
            String autorizationHeader =  request.getHeader(HttpHeaders.AUTHORIZATION);
            if(autorizationHeader == null || !autorizationHeader.startsWith(SecurityConsts.TOKEN_PREFIX)){
                filterChain.doFilter(request,response);
                return;
            }
            String token = autorizationHeader.substring(SecurityConsts.TOKEN_PREFIX.length());
            String username = jwtTokenProvider.getSubject(token);
            if(jwtTokenProvider.isTokenValid(username,token) && SecurityContextHolder.getContext().getAuthentication() == null){

                List<GrantedAuthority> authorities = jwtTokenProvider.getAuthorities(token);
                Authentication authentication = jwtTokenProvider.getAuthentication(username,authorities,request);
                SecurityContextHolder.getContext().setAuthentication(authentication);
            }else {
                SecurityContextHolder.clearContext();
            }

        }
        filterChain.doFilter(request,response);
    }
}

The Cross-Origin Configuration class

@Configuration
public class CorsConfig {
    @Bean
    public WebMvcConfigurer corsConfigurer() {
        return new WebMvcConfigurer() {
            @Override
            public void addCorsMappings(CorsRegistry registry) {
                registry.addMapping("/**")
                        .allowedMethods("GET", "POST", "PUT", "DELETE", "PATCH")
                        .allowedHeaders("*")
                        .allowedOrigins("*");
            }
        };
    }
}


Solution 1:[1]

Can you debug the application? The error you mentioned in the comments javax.management.InstanceNotFoundException: org.springframework.boot:type=Admin,name=SpringApplication is IDE-related, there is stackoverflow post about it, so this is not the issue.

Try to debug code and check:

  1. If you, actually, get to JWTAuthorizationFilter
  2. If your user is identified as admin user here: List<GrantedAuthority> authorities = jwtTokenProvider.getAuthorities(token);
  3. Check if you pass a correct and the newest token to Postman in Authorization header; check url (in the screenshot you have provided url is incorrect, it is useErnames)
  4. What is the value of SecurityConsts.TOKEN_PREFIX? It should include space, i.e. 'Bearer '

UPDATE Based on the comment below regarding my 2nd question (although I meant to check the values during runtime in debug mode, but anyway..)

String[] ADMIN_AUTHORITIES = {"ADMIN_ROLE"};

In the security config you are looking for hasAnyAuthority("ROLE_ADMIN"), but assigned your admin with "ADMIN_ROLE". Wording is different: ROLE_ADMIN vs ADMIN_ROLE. When you update one of them, be aware that Spring adds word "ROLE" to the authority name by itself in some cases. So try with and without this prefix.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1