SecurityContextHolder.getContext().getAuthentication() is null

ghz 9months ago ⋅ 104 views

I am working on the Spring boot rest API with JWT Token to authenticate the requests. I have made multiple entries of Admin in DB and passing name as username, password as password in Basic Auth, but ends up getting SecurityContextHolder.getContext().getAuthentication() as null.

@PostMapping("/admins/login")
    public ResponseEntity<Admin> getLoggedInAdminDetailsHandler(Authentication auth) {
        Admin admin = adminDao.findByEmail(auth.getName())
                .orElseThrow(() -> new BadCredentialsException("Invalid Username or password"));
        return new ResponseEntity<>(admin, HttpStatus.ACCEPTED);
    }

with Configuration as :

package com.olivestays.config;


import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;

@Configuration
public class AppConfig {

    @Bean
    public SecurityFilterChain springSecurityConfiguration(HttpSecurity http) throws Exception {

        http
        .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
        .and()
        .csrf().disable()
        .authorizeHttpRequests()
        .requestMatchers(HttpMethod.POST, "/staywell/customers/register").permitAll()
        .requestMatchers(HttpMethod.POST, "/staywell/admins/register").permitAll()
        .requestMatchers(HttpMethod.POST, "/staywell/hotels/register").permitAll()
        .requestMatchers(HttpMethod.POST, "/staywell/customers/login").permitAll()
        .requestMatchers(HttpMethod.POST, "/staywell/admins/login").permitAll()
        .requestMatchers(HttpMethod.POST, "/staywell/hotels/login").permitAll()
        .requestMatchers(HttpMethod.POST, "/staywell/rooms/add").hasRole("HOTEL")
        .requestMatchers(HttpMethod.PUT, "/staywell/customers/update").hasRole("CUSTOMER")
        .requestMatchers(HttpMethod.GET, "/staywell/customers/getToBeDeletedAccounts").hasRole("ADMIN")
        .anyRequest()
        .authenticated()
        .and()
        .addFilterAfter(new JwtTokenGeneratorFilter(), BasicAuthenticationFilter.class)
        .addFilterBefore(new JwtTokenValidatorFilter(), BasicAuthenticationFilter.class)
        .formLogin()
        .and()
        .httpBasic();

        return http.build();

    }

    @Bean
    public PasswordEncoder passwordEncoder() {

        return new BCryptPasswordEncoder();

    }

}

I have made multiple entries in DB for Admin, and when I am passing Username and Password in Basic Auth, it is showing 401 Unauthorized.

Admin request:

package com.olivestays.model;



import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonProperty.Access;
import com.olivestays.enums.Role;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity
public class Admin {

    @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @JsonProperty(access = Access.READ_ONLY)
    private Integer adminId;

    @NotNull @NotEmpty @NotBlank
    private String name;

    @NotNull @NotEmpty @NotBlank
    @Column(unique = true) @Email
    private String email;

    @NotNull @NotEmpty @NotBlank
    private String mobile;

    @NotNull @NotEmpty @NotBlank
    @JsonProperty(access = Access.WRITE_ONLY)
    private String password;

    @JsonIgnore
    @Enumerated(EnumType.STRING)
    private Role role;
    
}

What should I do to resolve this issue?

Answers

It seems like you are trying to authenticate users using Basic Authentication and then retrieve the authenticated user details in your /admins/login endpoint. However, the issue you're facing is that SecurityContextHolder.getContext().getAuthentication() is returning null in your controller method.

The reason for this is that your /admins/login endpoint is not protected by Spring Security, so the Authentication object is not populated with the authenticated user's details.

To resolve this issue, you need to make sure that your /admins/login endpoint is protected by Spring Security so that the Authentication object is populated after successful authentication. You can do this by removing the .permitAll() configuration for this endpoint in your security configuration and ensuring that Basic Authentication is enforced.

Here's the updated security configuration:

@Bean
public SecurityFilterChain springSecurityConfiguration(HttpSecurity http) throws Exception {

    http
    .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
    .and()
    .csrf().disable()
    .authorizeHttpRequests()
    .requestMatchers(HttpMethod.POST, "/staywell/customers/register").permitAll()
    .requestMatchers(HttpMethod.POST, "/staywell/admins/register").permitAll()
    .requestMatchers(HttpMethod.POST, "/staywell/hotels/register").permitAll()
    .requestMatchers(HttpMethod.POST, "/staywell/customers/login").permitAll()
    .requestMatchers(HttpMethod.POST, "/staywell/admins/login").authenticated() // Require authentication
    .requestMatchers(HttpMethod.POST, "/staywell/hotels/login").permitAll()
    .requestMatchers(HttpMethod.POST, "/staywell/rooms/add").hasRole("HOTEL")
    .requestMatchers(HttpMethod.PUT, "/staywell/customers/update").hasRole("CUSTOMER")
    .requestMatchers(HttpMethod.GET, "/staywell/customers/getToBeDeletedAccounts").hasRole("ADMIN")
    .anyRequest()
    .authenticated()
    .and()
    .addFilterAfter(new JwtTokenGeneratorFilter(), BasicAuthenticationFilter.class)
    .addFilterBefore(new JwtTokenValidatorFilter(), BasicAuthenticationFilter.class)
    .formLogin()
    .and()
    .httpBasic();

    return http.build();
}

With this configuration, your /admins/login endpoint will require authentication, and the Authentication object should be populated with the authenticated user's details when the request is made.

Additionally, ensure that your Basic Authentication credentials (username and password) are being passed correctly in the request headers.