'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:
- If you, actually, get to
JWTAuthorizationFilter
- If your user is identified as admin user here:
List<GrantedAuthority> authorities = jwtTokenProvider.getAuthorities(token);
- 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 isuseErnames
) - 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 |