spring Security Architecture Principles by Daniel Garnier-Moiroux @ Spring I/O 2024
Filter
for security decisions on HTTP requests.Authentication
is the domain language of spring security.AuthenticationProvider
to validate credentials.Filter
+AuthenticationProvider
for custom login.
A spring security configuration that can work for most web applications:
@Configuration
@EnableWebSecurity
class SecurityConfig {
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.authorizeHttpRequests(
authorizeHttp -> {
authorizeHttp.requestMatchers("/").permitAll();
authorizeHttp.requestMatchers("/favicon.svg").permitAll();
authorizeHttp.requestMatchers("/css/*").permitAll();
authorizeHttp.requestMatchers("/error").permitAll();
authorizeHttp.anyRequest().authenticated();
}
)
.formLogin(l -> l.defaultSuccessUrl("/private"))
.logout(l -> l.logoutSuccessUrl("/"))
.oauth2Login(Customizer.withDefaults())
// You can add multiple login types.
.httpBasic(Customizer.withDefaults())
// You can add your custom filters.
// /!\ Order matters!!!
.addFilterBefore(new CustomFilter(), Authorization.class)
// You can add your custom AuthenticationProvider.
.authenticationProvider(new CustomAuthenticationProvider())
.build();
}
}
Filter
- When creating custom
Filter
, implementsOncePerRequestFilter
.- Takes
HttpServletRequest, HttpServletResponse
. - Reads from request to:
- sometimes writes to
Response
, - sometimes does nothing!
- sometimes writes to
- Takes
- You can look at the spring logs on startup to check the order of the
Filter
- To debug any HTTP 401 issue, e.g. to know which
Filter
rejected your HTTP requests, you can downlevel the log level toTRACE
:logging.level.org.springframework.security=TRACE
- execute your HTTP request, and look at the logs from
FilterChainProxy
Authentication objects
spring Security produces Authentication
objects, used for:
- Authentication: who is the user?
- Authorization: is the user allowed to perform XYZ?
Vocabulary:
Principal
: user “identity” (name, email, …)GrantedAuthorities
: “permissions” (roles, …).isAuthenticated()
: almost alwaystrue
details
: details about the request(Credentials)
: “password”, oftennull
Use SecurityContextHolder.getContext().getAuthentication()
to get your Authentication
object. The SecurityContext
is:
- Thread-local,
- not propagated to child threads,
- cleared after requests is processed.
Example of setting your custom Authentication
in a custom Filter
:
class RoboAuthenticationFilter extends OncePerRequestFilter {
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) {
// 1. Decide whether to apply the filter or not.
// ...
// 2. Check credentials and [authenticate | reject ], e.g. by checking the request headers.
// ...
// 3. Create custom Authentication.
var auth = new RoboAuthenticationToken();
var newContext = SecurityContextHolder.createEmptyContext();
newContext.setAuthentication(auth);
SecurityContextHolder.setContext(newContext);
filterChain.doFilter(request, response);
}
}
Some Filter
produce an Authentication
:
- read the request and convert to “domain”
Authentication
object, - authenticate (are the credentials valid?),
- save the
Authentication
in theSecurityContext
- or reject the request when credentials are invalid.
AuthenticationProvider
Implement your custom authentication provider by implementing the interface AuthenticationProvider
:
class CustomAuthenticationProvider implements AuthenticationProvider {
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
if (Objects.equals(authentication.getName(), "foobar")) {
var user = User.withUsername("foobar").password("ignored").roles("user", "admin").build();
return UsernamePasswordAuthenticationToken.authenticated(
user, null, user.getAuthorities()
);
}
return null;
}
@Overrde
public boolean supports(Class authentication) {
return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentcation);
}
}
Then add it to your SecurityConfig
.
You can add an ApplicationListener
to audit who logged in your application:
@Bean
ApplicationListener<AuthenticationSuccessEvent> successListener() {
return event -> {
System.out.println("🎉 [%s] %s".formatted(
event.getAuthentication().getClass().getSimpleName(),
event.getAuthentication().getName()
));
};
}
Authentication
is both an auth request and a successful auth result.AuthenticationProvider
validates credentials.- Operates only within the “auth” domain (no HTTP, HTML, …).
AuthenticationProvider
leverages spring security infrastructure.