Spring Security fundamentally operates as a filter chain
This filter chain follows the responsibility chain design pattern
- HttpSecurity
In earlier versions of Spring Security, configuration was done through XML files using the <http> tag to define HTTP request security settings like user permissions. However, with Spring Boot projects, we've moved away from XML configuration files. Spring Security now provides tools like HttpSecurity for this purpose.
1.1. SecurityBuilder
This interface defines the contract for building Spring Security objects. It's a generic interface where the type parameter specifies the object to be created.
public interface SecurityBuilder<O> {
/**
* Constructs and returns the object or null.
* @return the constructed object or null if the implementation permits it.
* @throws Exception if an error occurs during object construction
*/
O build() throws Exception;
}
1.2. AbstractSecurityBuilder
An abstract implementation that ensures the object is created only once. It uses the AtomicBoolean class to maintain correctness in concurrent scenarios.
public abstract class AbstractSecurityBuilder<O> implements SecurityBuilder<O> {
private AtomicBoolean building = new AtomicBoolean();
private O object;
@Override
public final O build() throws Exception {
if (this.building.compareAndSet(false, true)) {
this.object = doBuild();
return this.object;
}
throw new AlreadyBuiltException("This object has already been built");
}
public final O getObject() {
if (!this.building.get()) {
throw new IllegalStateException("This object has not been built");
}
return this.object;
}
protected abstract O doBuild() throws Exception;
}
1.3. AbstractConfiguredSecurityBuilder
Internally, this class defines an enumeration that divides the entire construction process into 5 states, representing the five phases of the build lifecycle:
private enum BuildState {
UNBUILT(0),
INITIALIZING(1),
CONFIGURING(2),
BUILDING(3),
BUILT(4);
private final int order;
BuildState(int order) {
this.order = order;
}
public boolean isInitializing() {
return INITIALIZING.order == this.order;
}
/**
* Determines if the state is CONFIGURING or later
* @return
*/
public boolean isConfigured() {
return this.order >= CONFIGURING.order;
}
}
The construction process:
@Override
protected final O doBuild() throws Exception {
synchronized (this.configurers) {
this.buildState = BuildState.INITIALIZING;
beforeInit(); // A placeholder method with no implementation
init(); // Finds all xxxConfigure instances and calls their init methods
this.buildState = BuildState.CONFIGURING;
beforeConfigure(); // A placeholder method with no implementation
configure(); // Finds all xxxConfigure instances and calls their configure methods
this.buildState = BuildState.BUILDING;
// The actual filter chain construction method
// However, in AbstractConfiguredSecurityBuilder, performBuild is just an abstract method
// The concrete implementation is in HttpSecurity
O result = performBuild();
this.buildState = BuildState.BUILT;
return result;
}
}
The AbstractConfiguredSecurityBuilder class maintains a configuration list stored in a HashMap, which maps configuration classes to their corresponding configuration lists. It provides methods to add and remove configurations.
private final LinkedHashMap<Class<? extends SecurityConfigurer<O, B>>, List<SecurityConfigurer<O, B>>> configurers = new LinkedHashMap<>();
- SecurityConfigurer
public interface SecurityConfigurer<O, B extends SecurityBuilder<O>> {
void init(B builder) throws Exception;
void configure(B builder) throws Exception;
}
SecurityConfigurer<O, B>: This interface declaration specifies two generic parameters.
- O: Represents the target object of configuration (e.g., the object to which security rules are applied). This paramter indicates what type of object you're configuring, such as web security rules (HttpSecurity) or authentication managers (AuthenticationManager).
- B: Represents the builder used for configuration. This parameter indicates the type of builder you're using, typically a class implementing the SecurityBuilder interface for constructing and configuring the target object.
The SecurityConfigurer interface defines a generic configuration pattern for various security objects. The generic parameters O and B make it flexible and adaptable to different configuration scenarios. When implementing this interface, you provide specific implementations for different security configurations, where O represents the type of security object being configured, and B represents the builder type being used.