-1

I am creating a REST API using Spring REST, and I'm trying to secure it with Spring Security for basic authentication. It's backed by a MariaDB database.

I've used this guide for setting up basic authentication. I've gotten to where I can make a POST request to create new user credentials in the DB, but when I then turn around and try to use those credentials to access a protected endpoint I get a 401 Unauthorized response.

Can anyone point out where I'm going wrong? Thank you!

Here is my entity:

@Entity
@Table(name="credentials")
public class Credential {

public static final PasswordEncoder PASSWORD_ENCODER = new BCryptPasswordEncoder();

@Id
@GenericGenerator(name = "uuid", strategy = "uuid2")
@GeneratedValue(generator="uuid")
@Column(name="user_id")
private UUID userId;
@Column(name="username")
private String username;
@Column(name="password")
private String password;
@Column(name="updated_at")
private LocalDateTime updatedAt;
@Column(name="created_at")
private LocalDateTime createdAt;
@OneToOne(fetch=FetchType.EAGER)
@JoinColumn(name="user_id")
private User user;

public Credential(UUID userId, String username, String password, LocalDateTime updatedAt, LocalDateTime createdAt) {
    this.userId = userId;
    this.username = username;
    setPassword(password);
    this.updatedAt = updatedAt;
    this.createdAt = createdAt;
}

public Credential() {}
...and standard getters and setters

Here is my repository:

public interface CredentialRepository extends CrudRepository<Credential, UUID>{

Optional<Credential> findByUsername(String username);

}

My class extending UserDetailsService:

@Component
public class DetailsService implements UserDetailsService {

@Autowired
private CredentialRepository credentials;

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    Optional<Credential> credential = credentials.findByUsername(username);
    if (!credential.isPresent()) {
        throw new UsernameNotFoundException(username + " not found");
    }
    return new User(credential.get().getUsername(), credential.get().getPassword(),
            AuthorityUtils.createAuthorityList("ROLE_USER"));
}

@Transactional
public Credential signupNewAccount(Credential credential) throws DuplicateUsernameException {
    if(usernameExists(credential.getUsername())) {
        throw new DuplicateUsernameException("That username is not available: " + credential.getUsername());
    }
    Credential registered = new Credential();
    registered.setUsername(credential.getUsername());
    registered.setPassword(credential.getPassword());
    return credentials.save(registered);
}

private boolean usernameExists(String username) {
    Optional<Credential> candidate = credentials.findByUsername(username);
    if(candidate.isPresent()) {
        return true;
    } else {
        return false;
    }
}

}

And my WebSecurityConfigurerAdapter:

@Component
@EnableWebSecurity
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {

@Autowired
private DetailsService detailsService;

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.userDetailsService(detailsService).passwordEncoder(Credential.PASSWORD_ENCODER);
}

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
    .csrf().disable()
    .authorizeRequests()
    .antMatchers(HttpMethod.POST, "/v1/login").permitAll()
    .antMatchers(HttpMethod.POST, "/v1/signup").permitAll()
    .anyRequest().authenticated()
    .and().httpBasic()
    .and().sessionManagement().disable();
}
}

And the HTTP GET request I'm sending to another endpoint:

GET /v1/boards HTTP/1.1
Host: localhost:8443
Authorization: Basic dGVzdDp0ZXN0
Cache-Control: no-cache

And the response from the server:

HTTP/1.1 401
WWW-Authenticate: Basic realm="Realm"
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
Strict-Transport-Security: max-age=31536000 ; includeSubDomains
X-Frame-Options: DENY
Set-Cookie: JSESSIONID=0FE1E2736168B8C24980059A2546CA95; Path=/; Secure; HttpOnly
WWW-Authenticate: Basic realm="Realm"
Content-Length: 0
Date: Sat, 28 Jul 2018 20:36:37 GMT

1 Answers1

0

After spending the day reading what felt like every article there is on Spring Security and Basic Authentication, I found the issue. Turns out when I was registering credentials in the database I was encrypting them twice. Changing the signup method in my UserDetailsService implementation fixed it:

@Transactional
public Credential signupNewAccount(Credential credential) throws DuplicateUsernameException {
    System.out.println("Enter signupNewAccount: " + credential.getUsername() + " / " + credential.getPassword());
    if(usernameExists(credential.getUsername())) {
        throw new DuplicateUsernameException("That username is not available: " + credential.getUsername());
    }
    return credentials.save(credential);
}