Search code examples
javaspringspring-bootapispring-security

401 unauthorized even with correct username and password


I'm creating an user API and trying to implement an authentication method through Spring Boot security.

Even using the correct password and the default Spring Security user user, my Postman still gives me an authorization error. I can't see where the problem is in this code.

Security config:

package com.api.business_products_management.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@EnableWebSecurity
public class SecurityConfig {
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .anyRequest().authenticated()
                .and()
            .httpBasic();
        return http.build();
    }
}

Controller:

package com.api.business_products_management.controllers;

import com.api.business_products_management.dtos.UserDto;
import com.api.business_products_management.models.UserModel;
import com.api.business_products_management.services.UserService;
import jakarta.validation.Valid;
import org.springframework.beans.BeanUtils;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.web.bind.annotation.*;

@RestController
@CrossOrigin(origins = "*", maxAge = 3600)
@RequestMapping("/user")
public class UserController {

    private BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
    final UserService userService;

    public UserController(UserService userService) {
        this.userService = userService;
    }

    @PostMapping
    public ResponseEntity<Object> saveUser(@RequestBody @Valid UserDto userDto) {
        var userModel = new UserModel();
        BeanUtils.copyProperties(userDto, userModel);
        userModel.setPassword(passwordEncoder().encode(userModel.getPassword()));
        return ResponseEntity.status(HttpStatus.CREATED).body(userService.save(userModel));
    }
}

Console:

2023-02-17T18:24:13.152-03:00  INFO 8418 --- [nio-8099-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
2023-02-17T18:24:13.152-03:00  INFO 8418 --- [nio-8099-exec-1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2023-02-17T18:24:13.154-03:00  INFO 8418 --- [nio-8099-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 1 ms

Solution

  • Without seeing your Postman request, my initial guess would be that it's the lack of CSRF (from your Postman request) which is causing the 401. You can read more about CSRF within Spring here: https://docs.spring.io/spring-security/site/docs/5.0.x/reference/html/csrf.html

    From that document:

    As of Spring Security 4.0, CSRF protection is enabled by default

    You can test this theory by temporarily disabling CSRF as shown in the example configuration here:

    https://docs.spring.io/spring-security/site/docs/5.0.x/reference/html/csrf.html#csrf-configure

    i.e.:

    @Configuration
    @EnableWebSecurity
    public class SecurityConfig {
        @Bean
        public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
            http
                .csrf().disable()
                .authorizeRequests()
                    .anyRequest().authenticated()
                    .and()
                .httpBasic();
            return http.build();
        }
    }
    

    I'd highly recommend reading the rest of the documentation to familiarize yourself with what CSRF protects against and whether it's an acceptable risk to turn it off (or selectively turn disable for certain paths) prior to disabling it in a production environment though.