Core authentication and authorization framework for Spring applications with comprehensive user management and security context handling
Spring Security Core provides support for checking passwords against databases of compromised passwords (e.g., Have I Been Pwned). This helps prevent users from using passwords that have been exposed in data breaches.
Core Capabilities:
CompromisedPasswordChecker - Interface for checking if a password is compromisedReactiveCompromisedPasswordChecker - Reactive versionCompromisedPasswordDecision - Result of password checkCompromisedPasswordException - Exception thrown when compromised password detectedKey Interfaces and Classes:
CompromisedPasswordChecker - Interface: check(String rawPassword)ReactiveCompromisedPasswordChecker - Interface: check(String rawPassword) returns Mono<CompromisedPasswordDecision>CompromisedPasswordDecision - Class: isCompromised(), getCompromisedCount()CompromisedPasswordException - Exception: thrown when compromised password is usedDefault Behaviors:
CompromisedPasswordDecision defaults to not compromisedMono that may errorThreading Model:
CompromisedPasswordDecision is immutable (thread-safe)MonoLifecycle:
Exceptions:
CompromisedPasswordException - Thrown when compromised password detectedEdge Cases:
getCompromisedCount() may return large numbersInterface for checking if a password has been compromised (e.g., in a data breach).
package org.springframework.security.password;
interface CompromisedPasswordChecker {
CompromisedPasswordDecision check(String rawPassword);
}Key Methods:
CompromisedPasswordDecision check(String rawPassword)CompromisedPasswordDecision indicating the result.Example:
CompromisedPasswordChecker checker = ...;
CompromisedPasswordDecision decision = checker.check("password123");
if (decision.isCompromised()) {
long count = decision.getCompromisedCount();
// Password is compromised, reject it
}Reactive version of CompromisedPasswordChecker for use in reactive applications.
package org.springframework.security.password;
interface ReactiveCompromisedPasswordChecker {
Mono<CompromisedPasswordDecision> check(String rawPassword);
}Key Methods:
Mono<CompromisedPasswordDecision> check(String rawPassword)Mono that emits the decision.Example:
ReactiveCompromisedPasswordChecker checker = ...;
Mono<CompromisedPasswordDecision> decisionMono = checker.check("password123");
decisionMono
.filter(CompromisedPasswordDecision::isCompromised)
.flatMap(decision -> Mono.error(new CompromisedPasswordException(
"Password has been compromised " + decision.getCompromisedCount() + " times"
)))
.block();Represents the result of a compromised password check.
package org.springframework.security.password;
class CompromisedPasswordDecision {
CompromisedPasswordDecision(boolean compromised);
CompromisedPasswordDecision(boolean compromised, long compromisedCount);
boolean isCompromised();
long getCompromisedCount();
}Constructors:
CompromisedPasswordDecision(boolean compromised)CompromisedPasswordDecision(boolean compromised, long compromisedCount)Key Methods:
boolean isCompromised()true if the password is compromised, false otherwise.long getCompromisedCount()Constants:
static final CompromisedPasswordDecision NOT_COMPROMISED =
new CompromisedPasswordDecision(false);Example:
// Password not compromised
CompromisedPasswordDecision safe = new CompromisedPasswordDecision(false);
// safe.isCompromised() == false
// safe.getCompromisedCount() == 0
// Password compromised with count
CompromisedPasswordDecision compromised = new CompromisedPasswordDecision(
true,
1234567L
);
// compromised.isCompromised() == true
// compromised.getCompromisedCount() == 1234567
// Using constant
CompromisedPasswordDecision notCompromised =
CompromisedPasswordDecision.NOT_COMPROMISED;
// Check decision
CompromisedPasswordDecision decision = checker.check("password123");
if (decision.isCompromised()) {
long breachCount = decision.getCompromisedCount();
throw new CompromisedPasswordException(
"Password found in " + breachCount + " data breaches"
);
}Exception thrown when a compromised password is detected.
package org.springframework.security.password;
class CompromisedPasswordException extends RuntimeException {
CompromisedPasswordException(String message);
CompromisedPasswordException(String message, Throwable cause);
}Constructors:
CompromisedPasswordException(String message)CompromisedPasswordException(String message, Throwable cause)Example:
CompromisedPasswordDecision decision = checker.check("password123");
if (decision.isCompromised()) {
throw new CompromisedPasswordException(
"Password has been compromised in " +
decision.getCompromisedCount() + " data breaches. " +
"Please choose a different password."
);
}@Component
public class PasswordValidationService {
private final CompromisedPasswordChecker passwordChecker;
private final PasswordEncoder passwordEncoder;
public PasswordValidationService(
CompromisedPasswordChecker passwordChecker,
PasswordEncoder passwordEncoder) {
this.passwordChecker = passwordChecker;
this.passwordEncoder = passwordEncoder;
}
public void validateAndEncodePassword(String rawPassword) {
// Check if password is compromised
CompromisedPasswordDecision decision = passwordChecker.check(rawPassword);
if (decision.isCompromised()) {
throw new CompromisedPasswordException(
String.format(
"Password has been found in %d data breaches. " +
"Please choose a different password.",
decision.getCompromisedCount()
)
);
}
// Password is safe, encode it
String encodedPassword = passwordEncoder.encode(rawPassword);
// Store encoded password
}
}Reactive Integration:
@Service
public class ReactivePasswordService {
private final ReactiveCompromisedPasswordChecker passwordChecker;
private final PasswordEncoder passwordEncoder;
public Mono<String> validateAndEncodePassword(String rawPassword) {
return passwordChecker.check(rawPassword)
.filter(CompromisedPasswordDecision::isCompromised)
.flatMap(decision -> Mono.error(new CompromisedPasswordException(
"Password compromised in " + decision.getCompromisedCount() + " breaches"
)))
.switchIfEmpty(Mono.fromCallable(() ->
passwordEncoder.encode(rawPassword)
));
}
}@Configuration
public class PasswordSecurityConfiguration {
@Bean
public CompromisedPasswordChecker compromisedPasswordChecker() {
// Implementation would typically integrate with external service
// like Have I Been Pwned API
return new CompromisedPasswordChecker() {
@Override
public CompromisedPasswordDecision check(String rawPassword) {
// Check against compromised password database
// This is a placeholder - real implementation would call API
if (isPasswordCompromised(rawPassword)) {
long count = getCompromisedCount(rawPassword);
return new CompromisedPasswordDecision(true, count);
}
return CompromisedPasswordDecision.NOT_COMPROMISED;
}
private boolean isPasswordCompromised(String password) {
// Implementation checks against database
return false;
}
private long getCompromisedCount(String password) {
// Implementation returns count from database
return 0L;
}
};
}
@Bean
public ReactiveCompromisedPasswordChecker reactiveCompromisedPasswordChecker() {
return new ReactiveCompromisedPasswordChecker() {
@Override
public Mono<CompromisedPasswordDecision> check(String rawPassword) {
return Mono.fromCallable(() -> {
CompromisedPasswordDecision decision =
compromisedPasswordChecker().check(rawPassword);
return decision;
});
}
};
}
}public class PasswordChangeService {
private final CompromisedPasswordChecker passwordChecker;
public void changePassword(String username, String newPassword) {
try {
CompromisedPasswordDecision decision = passwordChecker.check(newPassword);
if (decision.isCompromised()) {
throw new CompromisedPasswordException(
String.format(
"The password you entered has been found in %d data breaches. " +
"For your security, please choose a different password.",
decision.getCompromisedCount()
)
);
}
// Proceed with password change
updatePassword(username, newPassword);
} catch (CompromisedPasswordException e) {
// Log and rethrow
logger.warn("Compromised password attempt for user: {}", username);
throw e;
}
}
}Reactive Error Handling:
public Mono<Void> changePassword(String username, String newPassword) {
return passwordChecker.check(newPassword)
.flatMap(decision -> {
if (decision.isCompromised()) {
return Mono.error(new CompromisedPasswordException(
"Password compromised in " + decision.getCompromisedCount() + " breaches"
));
}
return Mono.empty();
})
.then(updatePasswordReactive(username, newPassword))
.doOnError(CompromisedPasswordException.class, e ->
logger.warn("Compromised password attempt for user: {}", username)
);
}org.springframework.security.passwordInstall with Tessl CLI
npx tessl i tessl/maven-spring-security-coredocs