SpringBoot secuirty first look: Autoconfiguration
Introduction
Java is the first language I learned and have used for a long time. Every time when I want to study a new technology or concept. I always look at Java because it often has the most implemented Libraries with well-structured readable codebase.
This time, I want to learn all about the web securities, JWT, OAuth2 and SAML2. To study web security technologies. I will use SpringBoot’s web security package. I will go through the package source code and alongside with the sample projects from Spring-security‘s github repositary. I am writing this post step by step while I learn, so maybe the overall structure may not be very readable. But I will try.
Step 1: Look at starter package
To use Spring-security, we have to load in the spring-boot-starter-security packge using maven, this can be done automatically if you setup the project using spring.io
1 | <dependency> |
starter autoconfiguration
Nagivating into and see what the starter package contains using Intellij. The package contains spring-boot-starter, spring-aop, spring-security-config, spring-security-web. Among this, we are interested in
- spring-boot-autoconfigure which is in spring-boot-starter,
- spring-security-core and spring-web which is in both spring-security-config and spring-security-web,
I have learned that to study a Spring Boot technology, the first step is always to look at its corresponding autoconfiguration. For spring-security, autoconfiguration is under org.springframework.boot.autoconfigure.security. The folder contains:
1 | org.springframework.boot.autoconfigure.security |
DefaultWebSecurityCondition and ConditionalOnDefaultWebSecurity
At first, the class DefaultWebSecurityCondition catches my eyes because of the “Default” keyword. Looking at the source
1 | /** |
The class is a AllNestedConditions which means that this is a composite condition where:
- @ConditionalOnClass({ SecurityFilterChain.class, HttpSecurity.class }):
Both SecurityFilterChain.class and HttpSecurity.class must exist in classpath. They are both in the spring-web package which is included earlier. - @ConditionalOnMissingBean({ SecurityFilterChain.class })
This condition is also because, A little spoiler. SecurityFilterChain is a bean that should be configured by developer. Since we have not do anything yet, the bean is missing in the Spring container.
And based on the class comment, this condition is used for ConditionalOnDefaultWebSecurity, which is used as a @Document annotation.
Other files
Next, lets look at other files:
- SecurityDataConfiguration:
The class comment saysAutomatically adds Spring Security’s integration with Spring Data.
- StaticResourceLocation:
Defines some constant enums for file pattern paths. For example, “CSS(“/css/**“)", “JAVA_SCRIPT(“/js/**“)”, IMAGES(“/images/**“) etc. - SecurityProperties:
This one is interesting, the class contains two inner class Filter and User and some default valuesThe user class contains some default values and the newed user is not a bean. So I guess it is save to ignore at the moment? And also, I guess that is why you cannot define your own User class because it will causes conflict with the class here.SecurityProperties.java 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55/**
* Default order of Spring Security's Filter in the servlet container (i.e. amongst
* other filters registered with the container). There is no connection between this
* and the {@code @Order} on a {@code SecurityFilterChain}.
*/
public static final int DEFAULT_FILTER_ORDER = OrderedFilter.REQUEST_WRAPPER_FILTER_MAX_ORDER - 100;
private final Filter filter = new Filter();
private final User user = new User();
public User getUser() {
return this.user;
}
public Filter getFilter() {
return this.filter;
}
public static class Filter {
/**
* Security filter chain order for Servlet-based web applications.
*/
private int order = DEFAULT_FILTER_ORDER;
/**
* Security filter chain dispatcher types for Servlet-based web applications.
*/
private Set<DispatcherType> dispatcherTypes = EnumSet.allOf(DispatcherType.class);
// ... getters and setters
}
public static class User {
/**
* Default user name.
*/
private String name = "user";
/**
* Password for the default user name.
*/
private String password = UUID.randomUUID().toString();
/**
* Granted roles for the default user name.
*/
private List<String> roles = new ArrayList<>();
private boolean passwordGenerated = true;
// ... getters and setters
}
servlet/
servlet/ folder contains the autoconfiguration for controllers. It contains
1 | servlet/ |
- AntPathRequestMatcherProvider implements RequestMatcherProvider interface and provide an instance of AntPathRequestMatcher from the spring-web package
- PathRequest delegates some commonly used paths for web resources, like StaticResourceRequest.
- SecurityAutoConfiguration:
The class only contains @ConditionalOnMissingBean AuthenticationEventPublisher. The class must be configured before @AutoConfiguration(before = UserDetailsServiceAutoConfiguration.class). It seems like this is related to the implementation of Authentication. Put this class on hold for now. - SecurityFilterAutoConfiguration:
- Based on the class comment, the class
ensure that the filter’s order is still configured when a user-provided WebSecurityConfiguration exists.
- The class is configured after @AutoConfiguration(after = SecurityAutoConfiguration.class). So this configuration happens last?
- It imports SecurityProperties using @EnableConfigurationProperties(SecurityProperties.class).
- Looking at the source code for this: The configuration creates a new DelegatingFilterProxyRegistrationBean object using the default Filter object in SecurityProperties. The comment in DelegatingFilterProxyRegistrationBean suggests
SecurityFilterAutoConfiguration.java 1
2
3
4
5
6
7
8
9
10
public DelegatingFilterProxyRegistrationBean securityFilterChainRegistration(
SecurityProperties securityProperties) {
DelegatingFilterProxyRegistrationBean registration = new DelegatingFilterProxyRegistrationBean(
DEFAULT_FILTER_NAME);
registration.setOrder(securityProperties.getFilter().getOrder());
registration.setDispatcherTypes(getDispatcherTypes(securityProperties));
return registration;
}When no URL pattern or servlets are specified the filter will be associated to ‘/*’.”. So I can guess that the this filter is responsible for all remaining unmatched URLs.
- Based on the class comment, the class
- SpringBootWebSecurityConfiguration:
Title says it allSpringBootWebSecurityConfiguration.java 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41class SpringBootWebSecurityConfiguration {
/**
* The default configuration for web security. It relies on Spring Security's
* content-negotiation strategy to determine what sort of authentication to use. If
* the user specifies their own {@link SecurityFilterChain} bean, this will back-off
* completely and the users should specify all the bits that they want to configure as
* part of the custom security configuration.
*/
static class SecurityFilterChainConfiguration {
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests((requests) -> requests.anyRequest().authenticated());
http.formLogin(withDefaults());
http.httpBasic(withDefaults());
return http.build();
}
}
/**
* Adds the {@link EnableWebSecurity @EnableWebSecurity} annotation if Spring Security
* is on the classpath. This will make sure that the annotation is present with
* default security auto-configuration and also if the user adds custom security and
* forgets to add the annotation. If {@link EnableWebSecurity @EnableWebSecurity} has
* already been added or if a bean with name
* {@value BeanIds#SPRING_SECURITY_FILTER_CHAIN} has been configured by the user, this
* will back-off.
*/
static class WebSecurityEnablerConfiguration {
}
}- WebSecurityEnablerConfiguration has a @EnableWebSecurity annotation that introduction more specific security configuration for spring-web. More details on next section.
- SecurityFilterChainConfiguration is configured when condition @ConditionalOnDefaultWebSecurity is true, in which is described above when there is no SecurityFilterChain in the bean container. Just by looking
- http.authorizeHttpRequests((requests) -> requests.anyRequest().authenticated());
can be intepreted as all URL resources must be authenticated before access. - http.formLogin(withDefaults()); http.httpBasic(withDefaults());
can be intepreted that this filter will implement a default form authentication and http authentication. - The interesting thing here is the withDefaults() method. The method returns a default argument required to supply the method. The generic type T allows withDefaults() to be used for different methods.
Customizer 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public interface Customizer<T> {
/**
* Performs the customizations on the input argument.
* @param t the input argument
*/
void customize(T t);
/**
* Returns a {@link Customizer} that does not alter the input argument.
* @return a {@link Customizer} that does not alter the input argument.
*/
static <T> Customizer<T> withDefaults() {
return (t) -> {
};
}
}
- http.authorizeHttpRequests((requests) -> requests.anyRequest().authenticated());
- UserDetailsServiceAutoConfiguration
- The configuration provides an implementation of AuthenticationManager (acting as a Service bean) that is in-memory using HashMap.
org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public InMemoryUserDetailsManager inMemoryUserDetailsManager(SecurityProperties properties,
ObjectProvider<PasswordEncoder> passwordEncoder) {
SecurityProperties.User user = properties.getUser();
List<String> roles = user.getRoles();
return new InMemoryUserDetailsManager(User.withUsername(user.getName())
.password(getOrDeducePassword(user, passwordEncoder.getIfAvailable()))
.roles(StringUtils.toStringArray(roles))
.build());
}
private String getOrDeducePassword(SecurityProperties.User user, PasswordEncoder encoder) { ... }
static final class MissingAlternativeOrUserPropertiesConfigured extends AnyNestedCondition {
MissingAlternativeOrUserPropertiesConfigured() {
super(ConfigurationPhase.PARSE_CONFIGURATION);
}
static final class MissingAlternative {
}
static final class NameConfigured {
}
static final class PasswordConfigured {
}
} - It uses the User object created in SecurityProperties and puts it into InMemoryUserDetailsManager. Lets work on details of InMemoryUserDetailsManager later.
- It allows to set custom values for spring.security.user.name and spring.security.user.password in properties.yaml.
- The configuration provides an implementation of AuthenticationManager (acting as a Service bean) that is in-memory using HashMap.
@EnableWebSecurity
Previous section describe the autoconfiguration for the starter package, which is the foundation block of spring security. @EnableWebSecurity describes how to configure web related seucirty. This is like middleware. You know, in older time, we have AOP (Aspect oriented programming) where we can define filters for Http requests that choose to allow/deny a particular request. At least, this is how I understand this at first place.
1 |
|
The annotation import WebSecurityConfiguration, SpringWebMvcImportSelector, OAuth2ImportSelector and EnableGlobalAuthentication which imports AuthenticationConfiguration.
SpringWebMvcImportSelector and OAuth2ImportSelector seems to import other packages when spring-web-mvc or spring-oauth2-client is included in maven.
WebSecurityConfiguration
Based on the comment, the class
uses WebSecurity to reate the FilterChainProxy that performs the web based security for Spring Security. It then exports the necessary beans.
1 | public class WebSecurityConfiguration implements ImportAware, BeanClassLoaderAware { |
Lets look at each bean in detail:
DelegatingApplicationListener: based on the name, the bean is probably related to ApplicationListener in spring-core, which is probably irrelavent to spring-security.
WebInvocationPrivilegeEvaluator() and SecurityExpressionHandler() both depends on springSecurityFilterChain().
1 |
|
In servlet/SpringBootWebSecurityConfiguration, the class created a default SecurityFilterChain object and put into spring container. So here, it guarantees that bool hasFilterChain is always true so the if statement is ignored. The rest of the code is just adding SecurityFilterChain and WebSecurityCustomizer to the webSecurity object.
Debugging using Intellij, we see that at startup, this.securityFilterChains
is only size of 1.
HttpSecurityConfiguration
HttpSecurityConfiguration primarliy implements a HttpSecurity httpSecurity()
bean.
1 |
|
Code has
- prototype scope annotation, meaning every http request will create a HttpSecurity object.
- LazyPasswordEncoder: for encoding password.
class LazyPasswordEncoder {}
is alao in HttpSecurityConfiguration.java - AuthenticationManagerBuilder:
Based on the commentAllows for easily building in memory authentication, LDAP authentication, JDBC based authentication, adding UserDetailsService, and adding AuthenticationProvider’s.
This is related to authentication, which will go deeper later on. - WebAsyncManagerIntegrationFilter: Something related for spring-web’s asynchronous filter.
- The class extends
class WebAsyncManagerIntegrationFilter extends OncePerRequestFilter
. Skimming through, OncePerRequestFilter guarantees a single execution per request dispatch, on any servlet container. Interesting.
- The class extends
Rest of the code specifies defaults for the HttpSecurity object. Looking back, in SpringBootWebSecurityConfiguration, the class has a SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http)
that adds defaults for formLogin() and httpBasic(). So just guessing, I would say the last element of this.securityFilterChains
in WebSecurityConfiguration will include a HttpSecurity will all defaults configured in HttpSecurityConfiguration plus default formLogin() and default httpBasic().
AuthenticationConfiguration
AuthenticationConfiguration provides implementation for DefaultPasswordEncoderAuthenticationManagerBuilder and LazyPasswordEncoder. Name tells it all and nothing special here.
Conclusion
Here, that concludes everything in the autoconfiguration side. Next, I want to dive into the code samples in Github and actually implement some security measures.