I'm using spring-security-saml2-service-provider for authentication in one of my spring boot applications and I'm using a custom JwtAuthorizationFilter (via a http Authentication header) in a different spring boot application.
They both work perfectly on their own.
Now I need to write a spring boot application that uses both of them. If the JWT token is available (Authentication header), then use the JwtAuthorizationFilter, otherwise use saml2Login.
The SAML2 configuration looks like this: (There is no filter, just the saml2Login)
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()).and()
.antMatcher("/**").authorizeRequests()
.antMatchers("/saml2/service-provider-metadata/**").permitAll()
.antMatchers("/**").authenticated().and()
// use SAML2
.saml2Login()
.addObjectPostProcessor(new ObjectPostProcessor<OpenSamlAuthenticationProvider>() {
public <O extends OpenSamlAuthenticationProvider> O postProcess(O samlAuthProvider) {
samlAuthProvider.setAuthoritiesExtractor(authoritiesExtractor());
samlAuthProvider.setAuthoritiesMapper(authoritiesMapper());
return samlAuthProvider;
}
})
;
}
The JWT configuration looks like this:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()).and()
.antMatcher("/**").authorizeRequests()
.antMatchers("/**").authenticated().and()
// use JWT
.addFilter(new JwtAuthorizationFilter(authenticationManager(), jwtUtil))
;
}
I think I need something like a JwtOrSaml2AuthenticationFilter but don't know how to do that.
The solution is to
Duplicate the configuration with #Order and
Set a header based requestMatcher before the addFilter
#EnableWebSecurity
public class SecurityConfiguration {
#Order(100) // lower number = higher priority
#Configuration
#RequiredArgsConstructor
public static class AppSecurityJWT extends WebSecurityConfigurerAdapter {
final JWTUtil jwtUtil;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()).and()
.antMatcher("/**").authorizeRequests()
.antMatchers("/saml2/service-provider-metadata/**", "/idm-app/**").permitAll()
.antMatchers("/**").authenticated().and()
// This configuration will only be active if the Authorization header is present in the request
.requestMatcher(new RequestHeaderRequestMatcher("Authorization")).addFilter(new JwtAuthorizationFilter(authenticationManager(), jwtUtil))
;
}
}
#Order(101)
#Configuration
#RequiredArgsConstructor
public static class AppSecuritySAML2 extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()).and()
.antMatcher("/**").authorizeRequests()
.antMatchers("/saml2/service-provider-metadata/**", "/idm-app/**").permitAll()
.antMatchers("/**").authenticated().and()
// This whole configuration will only be active, if the previous (100) didn't match
.saml2Login()
//...
;
}
}
Related
I need to add a basic auth in only one of my controller method
Other method don't have an auth, but still have a #PreAuthorize in order to check if the parameters are valid.
My issue is , httpbasic transform all the exception throwed by preAuthorize in 401 ( some should be 403, etc).
I have the feeling that all my endpoint are under a basic auth, not just the ones I have use #Secured
How can I avoid that?
My code:
#Controller
class MyController {
#Secured("BASIC_AUTHENTIFIED")
public void someMethodOnlyForAuthentified(){...}
#PreAuthorize("check(parameters)")
public void someMethodForEveryone(List<String> parameters){...}
}
And in my security config:
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser(login).password(password).roles("BASIC_AUTHENTIFIED");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.authorizeRequests().antMatchers("/**").permitAll()
.anyRequest().authenticated()
.and().httpBasic()
.and().csrf().disable();
}
My Spring Boot Actuator healthCheck is blocked because of a (pre_authenticated) token is missing.
There are many answers available, BUT this is question has interference with pre-authenticated security. As far as I searched, this is NOT a duplicate.
How can I allow the health check in a pre-authenticated security environment?
My question is also, do I need more settings (in e.g. the application.properties)?
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private static final Logger logger = LoggerFactory.getLogger(SecurityConfig.class);
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) {
PreAuthenticatedAuthenticationProvider provider = new PreAuthenticatedAuthenticationProvider();
provider.setPreAuthenticatedUserDetailsService(new XyzPreAuthenticatedGrantedAuthoritiesUserDetailsService());
auth.authenticationProvider(provider);
}
// Try-1, see below
#Override
protected void configure(HttpSecurity http) throws Exception {
http.addFilterBefore(xyzTokenRequestHeaderAuthenticationFilter(), RequestHeaderAuthenticationFilter.class)
.csrf().disable()
.authorizeRequests()
.antMatchers("/actuator/**").permitAll()
.anyRequest().authenticated();
}
#Bean
public XyzTokenRequestHeaderAuthenticationFilter xyzTokenRequestHeaderAuthenticationFilter() throws Exception {
XyzTokenRequestHeaderAuthenticationFilter filter = new XyzTokenRequestHeaderAuthenticationFilter();
filter.setAuthenticationManager(authenticationManager());
return filter;
}
}
My second try was:
#Override
protected void configure(HttpSecurity http) throws Exception {
http.addFilterBefore(xyzTokenRequestHeaderAuthenticationFilter(), RequestHeaderAuthenticationFilter.class)
.csrf().disable()
.authorizeRequests()
.antMatchers("/actuator/**").permitAll();
}
It looks like the xyz filter is not implemented in the 'perfect' way.
This way will help you get things workin':
1 - use the management port:
management.server.port=8081
management.security.enabled=false
management.server.address=127.0.0.1
management.server.ssl.enabled=false
management.endpoints.health.sensitive=false
management.endpoint.health.show-details=always
2 - configure both ways web and api. Use this beyond the standard parts:
#Override
protected void configure(HttpSecurity http) throws Exception {
http.sessionManagement().sessionCreationPolicy(STATELESS);
http.csrf().disable();
http.authorizeRequests()
.antMatchers("/actuator/**").permitAll()
.antMatchers("/**").authenticated();
http.addFilterBefore(xyzTokenRequestHeaderAuthenticationFilter(), AbstractPreAuthenticatedProcessingFilter.class);
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/actuator/**");
}
3 - Inside the Docker container, use the 8081 port for the healthCheck.
Try to add in .ignoring() and add #EnableGlobalMethodSecurity(prePostEnabled = true), #Configuration at class
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter implements WebMvcConfigurer{
#Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/actuator/**");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/actuator/**").permitAll()
.anyRequest().authenticated()
.and()
.addFilterBefore(xyzTokenRequestHeaderAuthenticationFilter(), RequestHeaderAuthenticationFilter.class);
}
}
The problem seems to be with your XyzTokenRequestHeaderAuthenticationFilter implementation. If you wrote that by extending RequestHeaderAuthenticationFilter, then you must set the property exceptionIfHeaderMissing to false.
If you didn't extend that Spring Security pre auth core class then you need to show the implementation.
I would like to enable POST request for a certain "/interface" URL on my site. I have successfully loaded the class via Class<?>[] getRootConfigClasses(), the specified CSP header for the HttpSecurity http exist.
Whenever I make a request to /interface, I get HTTP 403.
#Configuration
#EnableWebSecurity
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter
{
#Override
protected void configure(HttpSecurity http) throws Exception
{
http
.authorizeRequests().antMatchers(HttpMethod.POST, "/interface").permitAll().and()
.headers()
.contentTypeOptions().and()
.cacheControl().and()
.httpStrictTransportSecurity().and()
.frameOptions().and()
.contentSecurityPolicy("the csp header. it is present on every response.");
}
}
Try overriding another configuration method: configure(WebSecurity webSecurity).
#Override
public void configure(WebSecurity webSecurity) throws Exception {
webSecurity.ignoring().antMatchers(POST, "/interface");
}
I have a spring-boot application using spring-security. The security configuration is split into multiple instances of WebSecurityConfigurerAdapter.
I have one where I configure logout in general:
#Override
protected void configure(HttpSecurity http) throws Exception {
// configure logout
http
.logout()
.logoutUrl("/logout")
.invalidateHttpSession(true)
.addLogoutHandler((request, response, authentication) -> {
System.out.println("logged out 1!");
})
.permitAll();
// ... more security configuration, e.g. login, CSRF, rememberme
}
And there is another WebSecurityConfigurerAdapter, where I want to do almost nothing, except adding another LogoutHandler:
#Override
protected void configure(HttpSecurity http) throws Exception {
// configure logout
http
.logout()
.logoutUrl("/logout")
.addLogoutHandler((request, response, authentication) -> {
System.out.println("logged out 2!");
});
}
Both configure() methods are called. However, if I do log out, only the first LogoutHandler is called. Changing the #Order of both configurations does not change the result.
What is missing in my configuration?
When you create several security configurations Spring Boot will create a separate SecurityFilterChain for each of them. See WebSecurity:
#Override
protected Filter performBuild() throws Exception {
// ...
for (SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder : securityFilterChainBuilders) {
securityFilterChains.add(securityFilterChainBuilder.build());
}
// ...
}
When application gets logout request FilterChainProxy will return only one SecurityFilterChain:
private List<Filter> getFilters(HttpServletRequest request) {
for (SecurityFilterChain chain : filterChains) {
// Only the first chain that matches logout request will be used:
if (chain.matches(request)) {
return chain.getFilters();
}
}
return null;
}
If you really need modular security configuration I would suggest to create a separate security configuration for logout and other realms. You can define logout handlers as beans (using #Bean annotation) in different configuration classes and collect these handlers in logout configuration:
WebSecurityLogoutConfiguration.java
#Configuration
#Order(99)
public class WebSecurityLogoutConfiguration extends WebSecurityConfigurerAdapter {
// ALL YOUR LOGOUT HANDLERS WILL BE IN THIS LIST
#Autowired
private List<LogoutHandler> logoutHandlers;
#Override
protected void configure(HttpSecurity http) throws Exception {
// configure only logout
http
.logout()
.logoutUrl("/logout")
.invalidateHttpSession(true)
// USE CompositeLogoutHandler
.addLogoutHandler(new CompositeLogoutHandler(logoutHandlers));
http.csrf().disable(); // for demo purposes
}
}
WebSecurity1Configuration.java
#Configuration
#Order(101)
public class WebSecurity1Configuration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
// ... more security configuration, e.g. login, CSRF, rememberme
http.authorizeRequests()
.antMatchers("/secured/**")
.authenticated();
}
// LOGOUT HANDLER 1
#Bean
public LogoutHandler logoutHandler1() {
return (request, response, authentication) -> {
System.out.println("logged out 1!");
};
}
}
WebSecurity2Configuration.java
#Configuration
#Order(102)
public class WebSecurity2Configuration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/api/**")
.permitAll();
}
// LOGOUT HANDLER 2
#Bean
public LogoutHandler logoutHandler2() {
return (request, response, authentication) -> {
System.out.println("logged out 2!");
};
}
}
You should be solving this problem with the CompositeLogoutHandler on your single /logout operation endpoint.
You can still keep two WebSecurityConfigurerAdapter's as desired, but you'll be conglomerating the logout functionality for two LogoutHandlers into a single composite action:
new CompositeLogoutHandler(loggedOutHandler1, loggedOutHandler2);
The keypoint is you should create separated instance of AuthenticationManger.
Here is an sample for multiples WebSecurityAdapter
The Spring Security Tutorial has an example of configuring an LDAP Server:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().fullyAuthenticated()
.and()
.formLogin();
}
#Configuration
protected static class AuthenticationConfiguration extends
GlobalAuthenticationConfigurerAdapter {
#Override
public void init(AuthenticationManagerBuilder auth) throws Exception {
auth
.ldapAuthentication()
.userDnPatterns("uid={0},ou=people")
.groupSearchBase("ou=groups")
.contextSource().ldif("classpath:test-server.ldif");
}
}
}
However, I'm looking for a way to initialize the the LDAP server dynamically, not in the configuration file. I can't seem to find any examples. The purpose of this is to implement SSO for a login form.
I found it easier to work with a lower-level directory, ldaptive, to solve this problem.