Spring boot security add custom filter in which chain? - spring

I create a custom filter and use addFilterBefore add the filter in configure method.
#Component
public class JsonWebTokenFilter extends OncePerRequestFilter { ... }
configure method in SecurityConfig which extends WebSecurityConfigurerAdapter
#Autowired
private JsonWebTokenFilter jsonWebTokenFilter;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.antMatcher("/api/**")
.addFilterBefore(jsonWebTokenFilter, UsernamePasswordAuthenticationFilter.class)
.authorizeRequests()
.antMatchers("/api/auth/**").permitAll()
.anyRequest().authenticated();
}
Above setting add the jsonWebTokenFilter in both springSecurityChain(internal, additionFilter) and originalChain, is it the correct way or I should add FilterRegistrationBean to avoid the filter add in originalChain?
I need to handler different authication filters with different urls. Without FilterRegistrationBean, all custom filters add in originalChain. I set the different configuration with #Order and separate the url path, but still the requests should pass all custom filters.
#Configuration
#Order(1)
public static class PrivateSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired private PrivateTokenFilter privateTokenFilter;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable()
//...
.antMatcher("/api/private/**")
.addFilterBefore(privateTokenFilter, UsernamePasswordAuthenticationFilter.class)
.authorizeRequests()
.anyRequest().authenticated();
}
}
#Configuration
#Order(2)
public static class PublicSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired private JsonWebTokenFilter jsonWebTokenFilter;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable()
//...
.antMatcher("/api/**")
.addFilterBefore(jsonWebTokenFilter, UsernamePasswordAuthenticationFilter.class)
.authorizeRequests()
.anyRequest().authenticated();
}
}
To solve the problem, I should add FilterRegistrationBean setting for both custom filters, is that the right way?
spring-boot version: 2.6.6
spring-security : 5.6.2

Related

how to configure a Content Security Policy in a SecurityConfig class

In my java/spring-boot application, I have a SecurityConfig class as follows:
#EnableWebSecurity
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final BasicAuthenticationProvider basicAuthenticationProvider;
#Autowired
public SecurityConfig(BasicAuthenticationProvider basicAuthenticationProvider) {
this.basicAuthenticationProvider = basicAuthenticationProvider;
}
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.antMatcher(PATH_NAME)
.httpBasic()
.and().authorizeRequests().anyRequest().authenticated()
.and().csrf().disable();
}
}
I need to configure a ContentSecurityPolicy by using the code .and().headers().contentSecurityPolicy("script-src 'self'") within the configure method, but I've tried in the possible places and keep getting errors (e.g. cannot resolve method .and() when trying the below code)
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.antMatcher(PATH_NAME)
.httpBasic()
.and().authorizeRequests().anyRequest().authenticated()
.and().csrf().disable()
.and().headers().contentSecurityPolicy("script-src 'self'");
}
Would anyone know how to properly do this?
You simply don't have to call .and() after csrf().disable(), since disable() already returns the HttpSecurity object that you have to configure.
So something like
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.antMatcher(PATH_NAME)
.httpBasic()
.and().authorizeRequests().anyRequest().authenticated()
.and().csrf().disable()
// notice that .and() is removed from the next line
.headers().contentSecurityPolicy("script-src 'self'");
}

Cannot access to unsecured endpoints in Spring Boot

In my controller I have two endpoints where one is secured and one is public:
#GetMapping("/public")
public String getPublic() {
return "public";
}
#PreAuthorize("hasRole('USER')")
#GetMapping("/private")
public String getPrivate() {
return "public";
}
Secured endpoint works only when I am logged and token with right role is placed in request header. But when I want access to public endpoint without token I always got status 401 with error
Full authentication is required to access this resource
Here is my security configuration:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests().anyRequest().authenticated()
.and()
.csrf().disable();
}
}
and authorization server config:
#Configuration
#EnableAuthorizationServer
public class OAuth2AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
private final UserDetailsService appUserDetailService;
private final AuthenticationManager authenticationManager;
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints
.tokenStore(tokenStore())
.tokenEnhancer(tokenEnhancer())
.authenticationManager(authenticationManager)
.userDetailsService(appUserDetailService);
}
}
I also tried change .authorizeRequests().anyRequest().authenticated() to this : .authorizeRequests().anyRequest().permitAll() with no change. My preferred way is handle security with annotations. Thank you.
You have two options, can go with either.
Option 1: In your endpoint, change like this.
#PreAuthorize("permitAll()")
#GetMapping("/public")
public String getPublic() {
return "public";
}
And change your configure(HttpSecurity http) method, do like this.
#Override
public void configure(HttpSecurity http) throws Exception {
http
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.anyRequest().permitAll()
.and()
.csrf().disable();
}
Option 2: In your configure(HttpSecurity http) method, just do like this.
#Override
public void configure(HttpSecurity http) throws Exception {
http
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/public").permitAll()
.anyRequest().authenticated()
.and()
.csrf().disable();
}
antMatchers() will do the trick. We use it a lot. It is also better to have insecured endpoints in different class and control security on class level through request mapping.
antMatchers("/public").permitAll()
Link to spring security api - https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/config/annotation/web/builders/HttpSecurity.html#antMatcher-java.lang.String-

Spring Security - Filter Ordering and Multiple HttpSecurity

I want two diffrent http configurations to come in depending on the url i am entering. For example, when i type in "localhost:8080/HQ/test_web" i want this configuration to come in.
#Configuration
#Order(1)
public static class FirstWaveFilters extends WebSecurityConfigurerAdapter{
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/HQ/test_web/**").anonymous().and().addFilterBefore(new CustomFilter(),BasicAuthenticationFilter.class);
}
}
But, if its anything else, i want this configuration to come in:
#Configuration
#Order(2)
public static class SecondWaveFilters extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable().authorizeRequests()
.antMatchers("/h2-console/**").permitAll()
.antMatchers("/webjars/**").permitAll()
.antMatchers(HttpMethod.POST, SIGN_UP_URL).permitAll()
.anyRequest().authenticated()
.and()
.addFilter(new JWTAuthenticationFilter(authenticationManager()))
.addFilter(new JWTAuthorizationFilter(authenticationManager()))
// this disables session creation on Spring Security
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.authorizeRequests().antMatchers("/**").permitAll();
http.csrf().disable();
http.headers().frameOptions().disable();
}
}
I have them set up in the same class as the Spring Security doc suggested:
#EnableWebSecurity
public class WebSecurity extends WebSecurityConfigurerAdapter {
private UserDetailsServiceImpl userDetailsService;
private BCryptPasswordEncoder bCryptPasswordEncoder;
public WebSecurity(UserDetailsServiceImpl userDetailsService, BCryptPasswordEncoder bCryptPasswordEncoder) {
this.userDetailsService = userDetailsService;
this.bCryptPasswordEncoder = bCryptPasswordEncoder;
}
#Configuration
#Order(1)
public static class FirstWaveFilters extends WebSecurityConfigurerAdapter{
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/HQ/test_web/**").anonymous().and().addFilterBefore(new CustomFilter(),BasicAuthenticationFilter.class);
}
}
#Configuration
#Order(2)
public static class SecondWaveFilters extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable().authorizeRequests()
.antMatchers("/h2-console/**").permitAll()
.antMatchers("/webjars/**").permitAll()
.antMatchers(HttpMethod.POST, SIGN_UP_URL).permitAll()
.anyRequest().authenticated()
.and()
.addFilter(new JWTAuthenticationFilter(authenticationManager()))
.addFilter(new JWTAuthorizationFilter(authenticationManager()))
// this disables session creation on Spring Security
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.authorizeRequests().antMatchers("/**").permitAll();
http.csrf().disable();
http.headers().frameOptions().disable();
}
}
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder);
}
#Bean
CorsConfigurationSource corsConfigurationSource() {
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", new CorsConfiguration().applyPermitDefaultValues());
return source;
}
}
But it does not seem to work. Whatever url i enter, only the CUSTOMFILTER() gets called, so only the first configuration. Basically what i am trying to achieve is, if the user enters the first url i want that customfilter() to be the filter the request has to go through, if its any other url, i want it to go the second configuration and the two filters defined there to be the ones the request must go through. Why is this not working ?
http.antMatcher(...) - means, apply this http and all what is configured here when pattern in antMatcher is met.
http.authorizeRequests()... - defines your permissions, if user hit that endpoint he should has "ADMIN", "logged" etc.
In your FirstWaveFilters you have to start your http with http.antMatcher():
http.antMatcher("/HQ/test_web/**");
http.authorizeRequests().antMatchers("/HQ/test_web/**").anonymous()
.and()
.addFilterBefore(new CustomFilter(),BasicAuthenticationFilter.class);
If you are not add http.antMatcher(...); than that http will intercept all urls and SecondWaveFilters never will be reached.
http.authorizeRequests().antMatchers("/HQ/test_web/**").anonymous() - means that any anonymous user may hit /HQ/test_web/**, but it doesn't say "apply FirstWaveFilters when /HQ/test_web/**" it just mean anyone who hists /HQ/test_web/** may be anonymous.

Spring multiple authentication methods for different api endpoints

I want to check for different authentication methods for different endpoints. Methods i want to use are x509 and jwt. I need to use only x509 for certain endpoint and use JWT for all other requests.
Here's my web security configuration:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Configuration
#Order(1)
public static class ApiWebSecurityConfig extends WebSecurityConfigurerAdapter{
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/api/transaction/testf").authenticated().and()
.x509()
.subjectPrincipalRegex("CN=(.*?)(?:,|$)")
.userDetailsService(new X509UserDetailsService())
;
}
}
#Configuration
#Order(2)
public static class ApiTokenSecurityConfig extends WebSecurityConfigurerAdapter{
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/oauth/token", "/api/dealer/login").permitAll()
.and()
.authorizeRequests()
.anyRequest()
.authenticated()
;
}
}
}
This configuration only checks /api/transaction/testf endpoint for x509 certificate and allows all other endpoints to respond. I need other endpoints to return 503 without a jwt token.
You have two filter chains. Neither of them have an entry point pattern properly configured http.antMatcher. That means they are configured to use /** as their entry point pattern.
For example
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().fullyAuthenticated()
is the same thing as saying:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/**")
.authorizeRequests()
.anyRequest().fullyAuthenticated()
What we are saying here is
http - the security filter chain
http.antMatcher - the entry point to the security filter chain
http.authorizeRequests - start of my endpoint access restrictions
http.authorizeRequests.antMatchers - list of URLs with specific access
So what you need to do is change your #Order(1) filter chain to narrow down the pattern. For example: http.antMatcher("/api/transaction/**")
Your configuration will now look like
#Configuration
#Order(1)
public static class ApiWebSecurityConfig extends WebSecurityConfigurerAdapter{
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/api/transaction/**") //customized entry point
.authorizeRequests()
.antMatchers("/api/transaction/testf").authenticated().and()
.x509()
.subjectPrincipalRegex("CN=(.*?)(?:,|$)")
.userDetailsService(new X509UserDetailsService())
;
}
}
#Configuration
#Order(2)
public static class ApiTokenSecurityConfig extends WebSecurityConfigurerAdapter{
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/**") //this is default
.authorizeRequests()
.antMatchers("/oauth/token", "/api/dealer/login").permitAll()
.and()
.authorizeRequests()
.anyRequest()
.authenticated()
;
}
With your existing configuration the filter chain named ApiWebSecurityConfig will trap all calls. The other filter chain, ApiTokenSecurityConfig, is never used.
Since WebSecurityConfigurerAdapter is #Deprecated, preferable use multiple SecurityFilterChain (see updated Multiple HttpSecurity):
#Bean
#Order(1)
public SecurityFilterChain apiFilterChain(HttpSecurity http) throws Exception {
http
.antMatcher("/api/**")
.authorizeHttpRequests(authorize -> authorize
.anyRequest().hasRole("ADMIN")
)
.httpBasic(withDefaults());
return http.build();
}
#Bean
public SecurityFilterChain formLoginFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorize -> authorize
.anyRequest().authenticated()
)
.formLogin(withDefaults());
return http.build();
}

What is implication of adding #Component to custom Spring Security filter

I have a custom Spring Security filter extending GenericFilterBean.
To do automatic dependency and bean creation I added a #Component annotation.
In my Security config I also register the filter like:
#Autowired
private RestAuthenticationFilter restAuthenticationFilter;
protected void configure(HttpSecurity http) throws Exception {
// #formatter:off
http
.addFilterBefore(restAuthenticationFilter, LogoutFilter.class)
Everything works well except that my filter is called twice...
It seems Spring adds filters also automatically to standard filters.
What should be the best approach here?
UPDATE
#Dave is this what you mean? It seems to work.
#Configuration
#ComponentScan
#EnableAutoConfiguration
public class Application extends WebMvcConfigurerAdapter {
#Autowired
private RestAuthenticationFilter restAuthenticationFilter;
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Bean
public ApplicationSecurity applicationSecurity() {
return new ApplicationSecurity();
}
#Bean
public FilterRegistrationBean filterRegistrationBean() {
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
filterRegistrationBean.setEnabled(false);
filterRegistrationBean.setFilter(restAuthenticationFilter);
return filterRegistrationBean;
}
#Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
protected static class ApplicationSecurity extends WebSecurityConfigurerAdapter {
#Autowired
private RestAuthenticationFilter restAuthenticationFilter;
#Override
protected void configure(HttpSecurity http) throws Exception {
// #formatter:off
http
.addFilterBefore(restAuthenticationFilter, LogoutFilter.class)
.authorizeRequests()
.anyRequest().authenticated()
.and()
.csrf()
.disable()
.exceptionHandling()
.authenticationEntryPoint(new Http403ForbiddenEntryPoint())
.and()
.requestCache()
.requestCache(new NullRequestCache())
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
// #formatter:on
}
}
}
You need to explicitly register the filter and mark it as "enabled=false" using the FilterRegistrationBean API. Then Spring Security will use it in its chain, but Boot will not try and register it as well.

Resources