· Prerequisites
· Overview
∘ What is Digest Authentication?
∘ Why use Digest Authentication?
∘ Basic Authentication vs. Digest Authentication
· Getting Started
∘ Creating entities
∘ The UserDetailsService
∘ Spring Security configuration
∘ REST Controller
∘ Project structure
· Testing
· Conclusion
· References
Prerequisites
This is the list of all the prerequisites:
Overview
What is Digest Authentication?
Digest Authentication tries to overcome some weaknesses of Basic authentication, specifically by ensuring credentials are never sent in clear text across the wire. The standard governing HTTP Digest Authentication is defined by RFC 2617, which updates an earlier version of the Digest Authentication standard prescribed by RFC 2069.
It works in a challenge/response mode without sending the password over the wire. Because the password is never sent over the wire with the request, TLS isn’t a must. Anyone intercepting the traffic won’t be able to discover the password in cleartext.
Why use Digest Authentication?
Digest authentication provides secure authorization over HTTP because the clear-text password is never transmitted between the client and server. It uses a hash function before transferring the login and password over the network. The use of nonce values in the client challenge also ensures that Digest authentication is resistant to replay attacks.
Basic Authentication vs. Digest Authentication

You should not use Digest Authentication in modern applications, because it is not considered to be secure. The most obvious problem is that you must store your passwords in plaintext or an encrypted or MD5 format. All of these storage formats are considered insecure. Instead, you should store credentials by using a one way adaptive password hash (bCrypt, PBKDF2, SCrypt, and others), which is not supported by Digest Authentication. — https://docs.spring.io/spring-security/reference/servlet/authentication/passwords/digest.html
Getting Started
We will start by creating a simple Spring project from start.spring.io, with the following dependencies: Spring Web, Spring Security, Spring Data JPA, H2 Database, Lombok, and Validation

Creating entities
Let’s define the entities. In the entity package, we’ll create User and Roleclasses.
- User.java
The user class implements the UserDetails interface that provides security core user information.
@Builder
@Data
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode
@JsonIgnoreProperties(ignoreUnknown = true)
@ToString
@Entity(name = "app_user")
public class User implements UserDetails {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
@SequenceGenerator(name = "sequenceGenerator")
private Long id;
@NotNull
@Size(min = 1, max = 50)
private String username;
@NotBlank
@NotNull
private String password;
private boolean enabled = true;
private boolean accountNonExpired;
private boolean credentialsNonExpired;
private boolean accountNonLocked;
@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(name = "role_user", joinColumns = {
@JoinColumn(name = "user_id", referencedColumnName = "id") }, inverseJoinColumns = {
@JoinColumn(name = "role_id", referencedColumnName = "id") })
private List<Role> roles;
@Override
public boolean isEnabled() {
return enabled;
}
@Override
public boolean isAccountNonExpired() {
return !accountNonExpired;
}
@Override
public boolean isCredentialsNonExpired() {
return !credentialsNonExpired;
}
@Override
public boolean isAccountNonLocked() {
return !accountNonLocked;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return roles.stream().map(role -> new SimpleGrantedAuthority(role.getCode().name())).collect(Collectors.toSet());
}
}
- GroupRole.java
@Builder
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
@EqualsAndHashCode
@Entity(name = "app_role")
public class Role implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NotNull
@Enumerated(EnumType.STRING)
@Column(length = 15)
private RoleEnum code;
}
The UserDetailsService
DaoAuthenticationProvider uses UserDetailsService to retrieve a username, a password, and other attributes for authenticating with a username and password.
We will need to implement the UserDetailsService interface to override the interface’s loadUserByUsername(String username)method.
public interface UserService extends UserDetailsService {
/**
* @return list of User
*/
List<User> getUsers();
/**
* @param user ussr object
* @return user saved or updated
*/
User save(User user);
}
Let’s define the UserDetailsServiceImpl class as follows:
@Service
@RequiredArgsConstructor
@Transactional
public class UserDetailsServiceImpl implements UserService {
private final UserRepository userRepository;
private final AccountStatusUserDetailsChecker detailsChecker = new AccountStatusUserDetailsChecker();
/**
* Load user info by credential
*
* @param usernameValue username or email
* @return UserDetails object
*/
@Override
public UserDetails loadUserByUsername(String usernameValue) {
Optional<User> user = userRepository.findActiveByUsername(usernameValue);
if (user.isEmpty()) {
throw new UsernameNotFoundException("Invalid username or password.");
}
detailsChecker.check(user.get());
return user.get();
}
/**
* {@inheritDoc}
*/
@Override
public List<User> getUsers() {
return userRepository.findAll();
}
/**
* {@inheritDoc}
*/
@Override
public User save(User user) {
return userRepository.save(user);
}
}
Spring Security configuration
We need to create a new class in a config package called SecurityConfig.java.
SecurityConfig class will have the following configuration:
@Configuration
@RequiredArgsConstructor
@EnableWebSecurity
public class SecurityConfig {
private final UserDetailsService userDetailsService;
@Bean // (1)
public DigestAuthenticationEntryPoint digestEntryPoint() {
DigestAuthenticationEntryPoint entryPoint = new DigestAuthenticationEntryPoint();
entryPoint.setRealmName("demoDigestAuth");
entryPoint.setKey("571b264a-6868-49e6-9e43-ce80a5749b8f");
return entryPoint;
}
// (2)
public DigestAuthenticationFilter digestAuthenticationFilter() {
DigestAuthenticationFilter authenticationFilter = new DigestAuthenticationFilter();
authenticationFilter.setUserDetailsService(userDetailsService);
authenticationFilter.setCreateAuthenticatedToken(true);
authenticationFilter.setAuthenticationEntryPoint(digestEntryPoint());
return authenticationFilter;
}
@Bean // (3)
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(AbstractHttpConfigurer::disable)
.cors(AbstractHttpConfigurer::disable)
.httpBasic(AbstractHttpConfigurer::disable)
.headers(headers -> headers.frameOptions(HeadersConfigurer.FrameOptionsConfig::sameOrigin))
.sessionManagement(s -> s.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.exceptionHandling(e -> e.authenticationEntryPoint(digestEntryPoint()))
.addFilterBefore(digestAuthenticationFilter(),DigestAuthenticationFilter.class)
.authorizeHttpRequests(authorize -> authorize
.requestMatchers(toH2Console()).permitAll()
.requestMatchers("/api/**").hasAnyRole("ADMIN","USER")
.anyRequest().authenticated()
);
return http.build();
}
}
DigestAuthenticationEntryPointis to send the valid nonce back to the user if authentication fails or to enforce the authentication. It needs a key and a realm name.DigestAuthenticationFilterprocesses an HTTP request’s Digest authorization headers, putting the result into theSecurityContextHolder. It requiresDigestAuthenticationEntryPointandUserDetailsServiceto authenticate the user. If authentication is successful, the resultingAuthenticationobject will be placed into theSecurityContextHolder. If authentication fails, anAuthenticationEntryPointimplementation is called. This must always beDigestAuthenticationEntryPoint, which will prompt the user to authenticate again via Digest authentication.- Defines all filter chains which is capable of being matched against an HttpServletRequest.
REST Controller
Here is an account REST controller that is accessible to connected users and returns the username and role.
@RestController
@RequestMapping("/api")
public class AccountController {
@GetMapping("/account")
public ResponseEntity<String> getUserInfo() {
SecurityContext context = SecurityContextHolder.getContext();
Authentication authentication = context.getAuthentication();
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
var userData = MessageFormat.format("username:{0} - authorities:{1}", authentication.getName() ,authorities);
return ResponseEntity.ok(userData);
}
}
Project structure
This is the final folders & file structure for our project:

Testing
Now we are all done with our code. We can run our application and test it.
First, we initialize the database with some data by loading a data.sql file at startup.
INSERT INTO app_role(id, code) VALUES (1, 'ROLE_ADMIN');
INSERT INTO app_role (id, code) VALUES (2, 'ROLE_GUEST');
INSERT INTO app_role (id, code) VALUES (3, 'ROLE_USER');
INSERT INTO app_user(id, username, password, enabled, account_non_expired, account_non_locked, credentials_non_expired) VALUES (1, 'userdemo', 'jSN&9veq', true, false,false, false);
INSERT INTO app_user(id, username, password, enabled, account_non_expired, account_non_locked, credentials_non_expired) VALUES (2, 'admin', 'B6=]ZHvb', true, false,false, false);
INSERT INTO role_user(role_id, user_id) VALUES (3, 1);
INSERT INTO role_user(role_id, user_id) VALUES (1, 2);
INSERT INTO role_user(role_id, user_id) VALUES (3, 2);
As we see, the user’s password is stored in clear text in the database. This is a major drawback for modern applications.

Authorization header:
Authorization: Digest username="userdemo", realm="demoDigestAuth",
nonce="MTcyNjc0NzQxNTc1ODplYmE2ZTZmMjAzODU0Mjc2MDgzNmQ0MjQxZDRiMjE3MQ==",
uri="/api/account", algorithm="MD5", qop=auth, nc=00000001,
cnonce="s9oecnte", response="fdea90209f2e9fcc5e584d5e07bf95e2"
Conclusion
Well done !!. In this post, we have seen how to secure a Spring Boot API using Spring Security Digest Authentication.
Although the goal is to replace Basic Authentication, Digest Authentication has many drawbacks. It remedies some, but not all, weaknesses of Basic Authentication.
The complete source code of this series is available on GitHub.
You can reach out to me and follow me on Medium, Twitter, GitHub, Linkedln
Support me through GitHub Sponsors.
Thank you for reading !! See you in the next post.